Click here to Skip to main content
15,868,016 members
Articles / Desktop Programming / WPF

the Explorer Imperative - Partie Deux

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
9 Sep 2011CPOL39 min read 17.4K   346   7  
Search function added to the Explorer Imperative

the Explorer Imperative

Partie Deux

This 'Partie' is devoted to the use of 'the Explorer Imperative' in conjuction with Windows Explorer. It applies to both the compiled and script versions. When 'the Explorer Imperative' is invoked, a Window is displayed showing a textbox, a grid splitter and a 'tree' containing Your Computer's file system information, with the Keyboard Focused on Your Starting Directory. The textbox contains some usage instructions. If you Shift Tab(back tab), the focus will move to the Grid Splitter. Pressing the up or down arrows will move the Grid Splitter, up or down as the arrow points, changing the vertical position of the splitter bar and the height of the textbox and tree. Either side can be shrunken until they disappear. If the textbox has disappeared and the keyboard is focused on the tree, you can move the focus to the splitter by backtabbing and then pressing the down arrow. Likewise, if the tree has disappeared and focus is on the textbox you can tab forward to move the focus to the splitter. If the tree has disappeared but in trying to get it back you have tabbed too many times, pressing the up arrow will cause the path displayed in the textbox to change. This indicates that the tree has focus but has not been brought into view. Now you can backtab to the splitter bar and hold down the up arrow until it moves to the position you want.

partiedeux01.JPG

ToolTips for 'Expanded Directory', 'Unexpanded Directory' and 'Unexpanded Filestring'. Note that 'plus' denotes a directory while 'minus' denotes a file in this tooltip.

In the initial view, Your Starting Directory has Keyboard Focus. Pressing the right arrow will expand an 'unexpanded' directory. Hovering over an 'unexpanded directory' with the 'Mouse Pointer' a ToolTip is displayed showing access and write times and the Parent Directory. If the Directory has been 'Expanded' the ToolTip will show the Files and Sib-directories it contains.

partiedeux02.JPG

ToolTip for 'Unexpanded Filestring' showing vertical list of files.

Pressing the right arrow key while on an expanded directory will move down to the first sub-node. With 'the Explorer Imperative', the first sub-node is a string containing the Names of all files in the directory. Hovering over the 'string' will display a ToolTip showing the File Names in a vertically oriented list. This is similar to the 'Expanded Directory' ToolTip but has only the File Names, no sub-directories.

partiedeux03.JPG

The ToolTip for 'Expanded Directory' and the 'Expanded Filestring(not the tooltip)' shown side by side.

Expanding the 'Virtual Folder File String'

If yoy press 'ENTER', the 'string' will be split apart at the separating spaces. Statistics for each file are put into the ToolTip for that file. This allows a visual comparison of the contents of expanded directories. This is something you can't do with the 'listview' in Windows Explorer, which displays the contents of the currently selected 'node'. This also allows each file to be processed as an individual item.

partiedeux04.JPG

The ToolTip for an individual file showing access and write times, attributes and Index.

When Keyboard Focus is on a file, the default action for the 'ENTER KEY' is to 'OPEN' that file. This uses the 'ProcessStart' System function and opens the file the same way that Windows Explorer does. This means executeable types are executed and documents are opened by the executeable defined to open them(exempli gratia(e.g.) '.txt' is opend by 'Notepad', '.bat' is opened by 'cmd.exe', 'the Explorer Imperative.htm' is opened in 'I.E.'(I suppose I could have used "i.e." instead of "e.g." but that woukd be very confusing, wouldn't it?)).

When a file has Keyboard Focus, pressing the right arrow displays a text entry box for parameters to be passed to the program that will open the file. That program must accept the filename followed by the parameters. That is(i.e., 'id est'), by way of example(e.g., 'exempli gratia'), a '.bat' that echoes it's 'command line argument' and then pauses will echo(print) the parameter passed to it and then print "Press any key to continue. ..", then the window will disappear. If the 'Command line argument' is null it will still print out 'echo is off' when it executes an echo statement even if it is preceded by '@'. To complete the interaction with the item 'click' on the margin of the textbox. To cancel the interaction 'click' on another file or folder.

Source code for '.bat' file mentioned above.

BAT
echo off
rem this is a comment in this example batch file.
rem echo off will print 'echo off'
rem unless echo is pewceded by '@'.
echo %1
rem 'echo %1' will print out the first command line parameter. echo %1
echo %1
echo %1
echo %1
rem 'pause' will print out 'press any key...' after which the batch  rem program will end.  pause

'REFRESHING' a Folder.

When a folder has Keyboard Focus, pressing 'ENTER' refreshes the folder contents, restoring the 'string of files' and rebuilding the list of sub-directories.

Keyboard Navigation.

When a file or a folder has Keyboard Focus typing a printable character will 'goto' the next file or folder that starts with that character. This 'Keyboard Navigation' includes digits and any special character that a file system name can begin with. It is case sensitive, so if you type a lower case 'b', it will not find an upper case 'B'. It will NOT expand a folder or file string and look inside it. Windows Explorer does something similar but it is not case sensitive.

partiedeux05.JPG

Right Click Mouse to Expose Menu. If Mouse Pointer is not Over Menu, Context Menu is displayed instead.

Using the MENU System

The MENU System responds to the RIGHT MOUSE BUTTON in two different ways. Pressing the RIGHT MOUSE BUTTON displays the Popup Menu. This Menu has a Horizontal Orientation. The file or folder under the Mouse Pointer is passed to the Menu for processing. When you release the button the Menu will 'Capture' the 'Mouse' if the pointer is over the Menu. There are factors that cause the Mouse Pointer to not be on the Menu. These factors include 'User Gestures'. Moving the Mouse Pointer to another item before releasing the Mouse Button will 'Select' the 'new item' and display a more 'traditional' Context Menu. Both Menues have the same functionality. They work as expected for the individual menu choices.

Using the File Menu

The File Menu contains the following choices; New, Open, Execute, CMD.EXE, Delete, Rename and Exit. Both menues display the sub-menues vertically, the same as regular menues and context menues, respectively.2

File>New Command

The 'File>New' Command provides 4 choices; File, Folder, Shortcut and Startin Directory. The first two provide a text entry box for the 'New' file or folder name. The 'New>Folder' crestes a NEW Folder in the Current Folder. It does not attempt to create a folder in a file.

  • The 'File>New>File' invokes NOTEPAD to create a text file and offers an oppotunity to add text to the file. This is a 'template' method. This technique could be used to create any type of file that would be a correct file of the type. Alternatively you can 'Create' a file system object with whatever characteristics you require.
  • The 'File>New>Folder' invokes CMD.EXE to create a folder using the 'mkdir' command.
  • The 'Create a New Shortcut' puts a 'Desktop Shortcut' to the selected file or folder on the "Owner's" Desktop. This Example uses a wrapper for a COM Object which requires the correct version of the COM Object. You may experience errors if they don't match. If this occurs you should rebuild the wrapper using the COM Object that is present on your file systen. Do not try to Download a wrapper to match the COM Object. There are known to be versions of the Object that contain MALWARE.
  • The 'Set as Start in Directory' allows the user to 'Start' in any Directory they choose. It uses 'Isolated Storage' to persist any information desired. It is not secure storage but it is unique to your program.

File>Open Command

This Menu Command 'Opens' the selected item using the 'EXE' that customerily opens the item. This means a Document is opened by the Word Processor or Editor associated with the Document Type, while a program or script is executed. FOLDERS are 'Opened' by Windows Explorer.

File>Execute Command

The Menu Command 'Execute' provides a field to pass parameters to the selected item. If We select 'temp.bat' and pass the string 'Hello!' to it. It echoes the parameter a number of times and 'pauses'. We press the 'space bar' and the batch program ends. If you click on the left or right margin of the entry box without entering a parameter, the results depend on the program. In this case, 'echo' reports that echo is off.

File>CMD.EXE Command

The 'CMD.EXE' Menu Command presents 2 enty fields, one before the selected item name and one after it. The first is for '.bat' or '.exe' commands or 'CMD.EXE' parameters(e.g. 'type' or 'notepad', or '/t:fc' but NOT both). The Menu Command adds '/K' before the field unless the field begins with a forward slash, in which case, '/K' is added after the field. To CMD, everything after the '/K' is a part of the command to execute. You could pass 'Notepad' to 'CMD.EXE' in the first field and edit the 'temp.bat' file or pass a string to 'temp.bat' in the second field. I suggest you use the System Menu to 'close' CMD instead of using the 'exit' line command.

Note: Here's a neat 'trick' to make using CMD a lot easier. Use the '/F:ON' parameter in the first field. This turns on the 'Directory' and 'File' completion feature in CMD. Now type 'cd c:\<ENTER>'. Now the prompt should be 'C:\'. Now type 'cd<space>' and hold down the control key while typing 'd'. Each time you type 'd' it will show the next 'Directory', enclosing those with embedded spaces in quotes. Control 'D' will show the previous 'Directory'. Control 'f' will cycle thru directories and files. Using 'cd' to navigate to a folder, then type 'copy <ctrl/f>' until a file you want to copy appears, then type a space followed by the name of the copied file, using the path completion feature to specify a place to copy to. For more information on this feature of CMD type 'help cmd' in any active CMD Command line.

File>Delete Command

The 'Delete' Command invokes 'Cmd' with a '/c' parameter to perform the delete.

File>Rename Command

The 'Rename' Command invokes 'Cmd' with a '/c' parameter to perform an 'MOVE' with a 'new name'.

File>Exit Command

The 'Exit' Command calls the regular 'Exit' funcrion.

Using the Edit Menu

The Edit Menu contains the following choices; 'Copy from', 'MOVE' and 'Paste to'. This works in a way that is different from a 'clipboard' copy and paste.

Edit>Copy from Command

The 'Edit>Copy from' Command copies the currently selected item to the 'Paste to' directory. It requiers that the 'Paste to' directory be specified first. Once this is done, any number of files can be copied to that destination, with any other action being executed between copy operations.

Edit>MOVE Command

The 'Edit>MOVE' Command moves the currently selected item to the 'Paste to' directory. It also requiers that the 'Paste to' directory be specified first. Once this is done, any number of files can be moved to that destination, with any other action being executed between move operations. This is similar to a 'COPY' and 'DELETE' in that the object is removed from it's original location.

Edit>Paste to Command

The 'Edit>Paste to' Command stores the currently selected directory in a static memory location to be used by the 'Copy from' and 'MOVE' commands as the 'Paste to' directory. This must be the first of the 'Edit' commands executed. It is last on the list because it will be selected the least often. ERGONOMICS RULES, BROTHER!

Using the View Menu

The View Menu contains the following choices; 'Increase Font Size' and 'Decrease Font Size'. This menu item controls the size of the Menu. These are examples of LAMBDA Functions. I tried to keep this program purely IMPERATIVE but I decided that the menu needed zoom more than I needed purity. (Not that I have anything against functional programming, In fact, I LOVE IT! but this is the 'Explorer Imperative'.(zoom zomm) ). Besides which, the 'mainWindow.Loaded.Add' function is an 'Event Handler' that 'adds' a LAMBDA Function to the mainWindow Event Handler Set when the 'mainWindow' has been loaded by WPF and is ready for display.

View>Increase Font Size Command

The 'View>Increase Font Size' Command adds the Lambda(anonymous) Function to the Menu Item Event Handler Set rather than adding the Event Handler Call. This is done for both the Popup Menu and the Context Menu. This elimanates the need to define a separate, named function as well as the call to that function. It's just easier.

View>Decrease Font Size Command

The 'View>Decrease Font Size' Command is also a pair of Lambda functions, one for the Popup Menu one for the Comtext Menu.

partiedeux06.JPG

Mouse Left Click on 'Search' Menu Item. Search Sub-menu appears.

Using the Search Menu

The Search Menu contains the following four selections; 'For File', 'Start Here', 'For String' and 'Find Next(F3)'. It uses a predefined subset of directories for a 'quick' search but allows you to searsh an entire drive. You can not search the entire Computer though.

Search>For File Command

The 'Search>For File' Command grabs the currently selected File Name. If the current item is a directory it presents the previous filename for editing. The wildcards are '*' and '&'. They work in the normal fashion. To initiate the 'Search For File' click the Margin of the text entry box.

Search>Start Here Command

The 'Search>Start Here' Command grabs the currently selected directory. If the current item is a file it gets the Parent Directory of the file. To initiate the 'Search>Start Here' click anywhere on the menu selection.

partiedeux07.JPG

Click on the text in the 'text box' Over-type file-name or '???' to search for a string.

Search>For String Command

The 'Search>For String' Command keeps the filename used as the argument for the file search function but it will grab the currently selected file-name if you haven't previously searched for a file. You can enter wildcard characters in the file-name entry box of either the 'search for string' or 'search for file' but only the file-name, not the 'string'. Right Clicking on a different file-name does not change the search arguement for the 'string search' but does change it for the 'file search'. The 'Search' function has a default 'Quick Search' list of directories that will be searched if the first search does not have a directory selected. Since the file-name entry box grabs the file-name even if you only right click on the item of the file-name then go to a directory you want to search and select it to search. You can then select either of the search choices except 'search next'. If you wish to limit the 'string search' to a single file, select the directory that contains it. This technique is thwarted by a sub-folder containing a file of the same name. The 'string search' will search for the 'string' in the selected or default directories and if it is found it is copied into the textbox above the splitter bar and the lines containing the 'string' are sent to stdout with the context provided by the preceding and succeeding lines. The very simplest of techniques is used to avoid printing the same line multiple times.

partiedeux08.JPG

Some Console output showing the full paths for the 'file.fsx' files found, followed by the files it 'searches' for the string 'Wide'.

Search>For String Console output

The 'Search>For String' Command produces a list of the the files found in the file search function of the 'Search>For File', 'Search>For String' and 'Search>Start Here' Commands as it queues them up for subsequent searches for the same file-name. Then it attempts to syncronize with it's current position in the TreeView. If you are positioned in a folder containing 'file.fsx' and 'file.fs', an initial search for a file-name will always find the next one but afterwards selecing the other file-name will find it in the same directory. However selecing a file and then changing it in the text entry box will not find it in the same folder.

The 'Search>For String' fumction is keyed off of the change in the 'string' argument, therefore, it will search in the current folder as long as the string argument changes for each search and it is found in the file. If both the file-name and search string are the same then the srarch will go on to the next entry in the queue. If the string is not found in the current file the search will go to the next entry in the queue. To lock the search on a single file, select the file by Right Clicking on it and then choosing the 'Search>Start Here' menu choice. This allows you to 're-search' the same file even if a search fails in that file. To break out of the lock, change the file-name or use the 'Search>Start Here' menu choice after selecing a different directory.

partiedeux09.JPG

Console output from a successful search for the string 'let _ = ' showing the context of it's use.

The 'Search>For String' function prints out the queue entries it searches and a 'string not found' message or the 'string found' message followed by the line containing the string surrounded by the line before it and the line after it. If a line has been printed, it is not printed a second time. The line number of the line is printed before the line.

Search>For String Screen output

partiedeux10.JPG

Screen output of a successful search for a string.

The 'Search>For String' Command prints either a string not found message or a string found message to the Title Bar and the textbox located above the Splitter Bar. Then it copies the file to that textbox after outputting the lines containing the search string. This copy of the file is intended only for browsing. Any inter-action with the Treeview will cause it to be replaced. Since it is not very easy to hit the Splitter Bar, it is best to backtab to the splitter and move it down with the cursor key, rather than moving it with the mouse. I have found the Console output much more useful.

Search>Find Next(F3) Command

The 'Search>Find Next(F3)' Command enqueues the current entry and dequeues the next one. This has the effect of turning the queue into a LOOP or wheel(a 'wheel of dirs' or a 'roll-a-dir'). F3 refers to the F3 Key which performs the same functionality and 'rolls' to the next 'dir'. If you attempt to 'find next' before you 'find' it will appear that nothing has happened. In actuality, it has enqueued the full path name of the current item. Although you have found nothing, the search function is happy. Congratulations! You have found Happiness! Now, when you 'find' something it will be different from your previous 'find'. Therefore it will find it in the same directory.

Description of Code for the 'Search Function'

This sample contains essentially the same ingredients as the original article. That is, an fsx script that can be interpreted by 'FSI.EXE' - F# Interactive or 'save as' an 'fs' file and add it to an F# Project and add references for all of the '#r' lines in the '#IF' directive and compile it. You can do either, your choice. I'm not including a project because it is far easier for those with the resources to create a Project than it is for one who is limited to FSI Interactive to try to get a compiled program to work in interactive mode. I have also incuded the WPF Event Loop Source, Which is the copywrited property of Microsoft and is only needed for the script version. The Windows Script Host Object Model, needed for the 'Set as Startin Directory' menu choice is also included. There are two bug fixes(see change log below) and the 'Search function' has been added. The remainder of this article will discuss the code for the Search.

The Application, 'the Explorer Imperative - Partie Deux'

The Application containing the search function is a file system viewer with ZOOM, and now, search capability. The Application is a WPF Window that is code-only. No XAML. The Window contains a single Grid with 3 Rows and 1 Coloum. The first Row contains a Textbox. The second Row contains only a Grid Splitter. This is used to re-size the height of the other two Rows. The third Row contains a Treeview in a Scrollviewer. The Script version will run with or without a WPF EVENT LOOP. The WPF EVENT LOOP provides a base set of user interaction responses for both MOUSE and KEYBOARD Events. If your application uses these responses then it needs a WPF EVENT LOOP. This application requires it for all of the menu interactions. Since We are discussing the menu, WE need it unless We compile the 'file.fs' version.

We will present the code in the order of discussion, not in the order of occurence in the program source code. This is done here for the sake of simplicity. In the program source the menu is coded after the Event Handler it invokes. The Event Handler is after any fumction it calls. The reasons for this will not be explained yn this article but I may cover them in the future. Suffice it to say that it must be defined before it is called. Therefore, We will first look at the PopUp Menu function.

The Menu System - Popup and Context

Generating and Displaying The Menus

The following Event Handlers are coded in the mainWindow.Loaded.Add Lambda function. The 'menuReq' function(not a lambda, not anonymous) is called when the Right Mouse is pressed. It builds the Popup Menu. If the Mouse Pointer is not on the Popup Menu when the Right Mouse Button is released the Context Menu is built by the contextReq function. This example shows how to code a Popup which, incidentally, contains a menu. The contextReq function is an example of a Context Menu but We won't discuss it much.

F#
treeTrunk.PreviewMouseRightButtonDown.Add(menuReq)
treeTrunk.MouseRightButtonUp.Add(contextReq)

In the next section, the menuReq function, the 'Popup Menu' is explained. Most of it is functionally identical to the 'Context Menu'. Visually, the difference is the 'Context Menu' is vertically oriented while the 'Popup Menu' is horizontally oriented. The context menu is often referred to as a quick menu or a short-cut menu, so I guess you can call the 'Popup Menu' a short-quick menu if you don't want to call it a popup menu. But then you could change the MouseRightButtonUp Evemt Handler to call the popup and then call it a pop-pop menu. Or change them both to call the context menu and call it a pop-con menu(excuse my corny humor). At any rate, You have your choice of menu styles and you don't even need a style sheet. Of course, you could put different functionality in the two or even add a regular menu if you wish.

The first line of code below says "Please allow 'menuReq' to be a delegate function to handle a MouseButtonEvent that accepts a MouseButtonEventArgs parameter named 'e' and let the following lines define the function" in the F Sharp Language. The 'e:' says "e is of type...", in this case 'MouseButtonEventArgs'. The 'try' handles exceptions along with a 'with'. The 'try' can also have a 'finally' clause. Next, We define a Popup by calling it's 'new' function and giving it the name 'menuPopup'. Then tell the popup where to go when it is displayed. The '<-' is F# for 'set the left argument equal to the right argument'. The ':?>' trigram downcasts the source of the argument e to a TreeViewItem. It is saying "Set the menuPopup PlacementTarget equal to the TreeViewItem that was just clicked". Give it a vertical offset of "-40.0" and a horizontal offset of "20". This places it over the treviewitem in most cases. Now define thisItem and a new Menu named popupMenu. Set the popupMenu's initial Fontsize. We define a menuitem and call it 'pmF', which is the first character of 'Popup', 'Menu' and 'File' - the 'Header' for this menuitem. We will use this convention to generate the names for the menu items we will need. The header for the first subitem for the 'File' Menu is 'New' so We name it 'pmFn'. Now We want to add 'pmFn' to 'pmF' but this results in a warning of a type mis-match because F# expects a type of 'MenuItem' but here has a type of 'Unit', which is 'Void' in c#. To prevent the warning We use a 'let binding' to bind the result of the Add function to the 'wildcard' argument '_'. F# doesn't complain if you re-define '_' as a different type than other definitions. Now We will add a menuitem with a more meaningful name to create a 'new File'. So We add 'File' to the prefix already generated. The resulting name is 'pmFnFile, which We add to the 'pmFn' menu item. The next menu item will contain a textbox for the name of the new file and will add the event handler call for the command function. Since We aren't going to add any more sub items to this menu item there is no concern about the name becoming to large and unwieldy, so We call it 'pmFnFileName'. Now We define the textbox and give it a minimum width so that it will be wide enough to click on without clicking on the menu item border, which would result in the Click Event being fired. Now We set the menu item's Header equal to the textbox, add the Full Path Name of the treeviewitem to the item's Tag and add the Click Event Handler to the item and finally, add the 'pmFnFileName' menu item to the 'pmFnFile' menu item. This is the simplest example showing the steps necessary to add a text entry capability to a menu item. When multiple entries are required, it can get a lot more confusing. Now let's skip forward to the 'Search>For String Menu Command.

F#
let menuReq(e:MouseButtonEventArgs) = 
 try 
  let menuPopup = new Popup() 
  menuPopup.PlacementTarget <- e.Source:?>TreeViewItem   
  menuPopup.VerticalOffset <- -40.0 
  menuPopup.HorizontalOffset <- 20.0  
  let thisItem = e.Source:?>TreeViewItem 
  let popupMenu = new Menu()  
  popupMenu.FontSize <- sizeOfFont 
  let pmF = new MenuItem() 
  pmF.Header <- "File" 
  let pmFn = new MenuItem() 
  pmFn.Header <- "New" 
  let _ = pmF.Items.Add(pmFn) 
  let pmFnFile = new MenuItem() 
  pmFnFile.Header <- "File"
  let _ = pmFn.Items.Add(pmFnFile)
  let pmFnFileName = new MenuItem()
  let fNfileBox = new TextBox()
  fNfileBox.MinWidth <- 640.0
  pmFnFileName.Header <- fNfileBox
  pmFnFileName.Tag <- thisItem.Tag
  pmFnFileName.Click.Add(newFileReq)
  let _ = pmFnFile.Items.Add(pmFnFileName)
  let pmFnFolder = new MenuItem()
  pmFnFolder.Header <- "Folder"
  let pmFnFoldName = new MenuItem()
  let fNfoldBox = new TextBox()
  fNfoldBox.MinWidth <- 640.0
  pmFnFoldName.Header <- fNfoldBox
  pmFnFoldName.Tag <- thisItem.Tag
  pmFnFoldName.Click.Add(newFoldReq)
  let _ = pmFnFolder.Items.Add(pmFnFoldName)
  let _ = pmFn.Items.Add(pmFnFolder)
  let pmFnS = new MenuItem()
  pmFnS.Header <- "Shortcut"
  let pmFnSc = new MenuItem()
  pmFnSc.Header <- "Create a Shortcut to this selection"
  pmFnSc.Tag <- thisItem.Tag
  pmFnSc.Click.Add(nShCutReq)
  let _ = pmFnS.Items.Add(pmFnSc)
  let _ = pmFn.Items.Add(pmFnS)
  let pmFnSi = new MenuItem()
  pmFnSi.Header <- "Start In"
  let pmFnSiD = new MenuItem()
  pmFnSiD.Header <- "Set this as Startin Directory"
  pmFnSiD.Tag <- thisItem.Tag
  pmFnSiD.Click.Add(setStartReq)
  let _ = pmFnSi.Items.Add(pmFnSiD)
  let _ = pmFn.Items.Add(pmFnSi)
  let pmFo = new MenuItem()
  pmFo.Header <- "Open"
  pmFo.Tag <- thisItem.Tag
  pmFo.Click.Add(oPenReq)
  let _ = pmF.Items.Add(pmFo)
  let pmFe = new MenuItem()
  pmFe.Header <- "Execute"
  let pmFeP = new MenuItem()
  let mutable filePan = new StackPanel()
  filePan.Orientation <- Orientation.Horizontal
  let mutable fileBox = new TextBox()
  fileBox.MinWidth <- 40.0
  let mutable argBox = new TextBox()
  argBox.Margin <- new Thickness(10.0,2.0,2.0,2.0)
  argBox.MinWidth <- 40.0
  fileBox.Text <- thisItem.Header.ToString()
  fileBox.IsReadOnly <- true
  argBox.IsReadOnly <- false
  let _ = filePan.Children.Add(fileBox)
  let _ = filePan.Children.Add(argBox)
  pmFeP.Header <- filePan 
  pmFeP.Tag <- thisItem.Tag 
  pmFeP.Click.Add(eXecReq) 
  let _ = pmFe.Items.Add(pmFeP) 
  let _ = pmF.Items.Add(pmFe) 
  let pmFce = new MenuItem() 
  pmFce.Header <- "CMD.EXE" 
  let pmFceP = new MenuItem() 
  let mutable cfilePan = new StackPanel()
  cfilePan.Orientation <- Orientation.Horizontal 
  let mutable argBox = new TextBox() 
  argBox.Margin <- new Thickness(10.0,2.0,2.0,2.0) 
  argBox.MinWidth <- 15.0 
  argBox.IsReadOnly <- false 
  let mutable cfileBox = new TextBox() 
  cfileBox.MinWidth <- 40.0 
  let mutable cargBox = new TextBox()
  cargBox.Margin <- new Thickness(10.0,2.0,2.0,2.0) 
  cargBox.MinWidth <- 40.0 
  cfileBox.Text <- thisItem.Header.ToString()
  cfileBox.IsReadOnly <- true 
  cargBox.IsReadOnly <- false 
  let _ = cfilePan.Children.Add(argBox)
  let _ = cfilePan.Children.Add(cfileBox) 
  let _ = cfilePan.Children.Add(cargBox) 
  pmFceP.Header <- cfilePan
  pmFceP.Tag <- thisItem.Tag
  pmFceP.Click.Add(cmdReq)
  let _ = pmFce.Items.Add(pmFceP)
  let _ = pmF.Items.Add(pmFce)
  let pmFd = new MenuItem() 
  pmFd.Header <- "Delete"
  let pmFdf = new MenuItem()
  pmFdf.Header <- thisItem.Header.ToString()
  pmFdf.Tag <- thisItem.Tag.ToString()
  pmFdf.Click.Add(delReq)
  let _ = pmFd.Items.Add(pmFdf)
  let _ = pmF.Items.Add(pmFd)
  let pmFr = new MenuItem()
  pmFr.Header <- "Rename"
  let pmFrN = new MenuItem()
  pmFrN.Header <- thisItem.Header.ToString()
  let pmFrNt = new MenuItem()
  pmFrNt.Header <- "to"
  let pmFrNtN = new MenuItem()
  let mutable rfileBox = new TextBox()
  rfileBox.MinWidth <- 40.0 
  rfileBox.Text <- thisItem.Header.ToString()
  rfileBox.IsReadOnly <- false
  pmFrNtN.Header <- rfileBox
  pmFrNtN.Tag <- thisItem.Tag.ToString()
  pmFrNtN.Click.Add(renReq)
  let _ = pmFrNt.Items.Add(pmFrNtN)
  let _ = pmFrN.Items.Add(pmFrNt)
  let _ = pmFr.Items.Add(pmFrN)
  let _ = pmF.Items.Add(pmFr)
  let pmFx = new MenuItem()
  pmFx.Header <- "Exit"
  pmFx.Click.Add(eXitReq)
  let _ = pmF.Items.Add(pmFx)
  let _ = popupMenu.Items.Add(pmF)
  let pmE = new MenuItem()
  pmE.Header <- "Edit"
  let pmEc = new MenuItem()
  pmEc.Header <- "Copy From(uses paste target)"
  pmEc.Tag <- thisItem.Tag
  pmEc.Click.Add(copyReq)
  let _ = pmE.Items.Add(pmEc)
  let pmEm = new MenuItem()
  pmEm.Header <- "Move(like a copy and delete)"
  pmEm.Tag <- thisItem.Tag
  pmEm.Click.Add(moveReq)
  let _ = pmE.Items.Add(pmEm)
  let pmEp = new MenuItem()
  pmEp.Header <- "Paste To(set the target for copy and move )"
  pmEp.Tag <- thisItem.Tag
  pmEp.Click.Add(PasteReq)
  let _ = pmE.Items.Add(pmEp)
  let _ = popupMenu.Items.Add(pmE)
  let pmV = new MenuItem()
  pmV.Header <- "View"
  let pmViF = new MenuItem()
  pmViF.Header <- "Increase Font Size"
  pmViF.StaysOpenOnClick <- true
  pmViF.Click.Add(fun e ->
   sizeOfFont <- sizeOfFont + 2.0
   popupMenu.FontSize <- sizeOfFont
   ) 
  let _ = pmV.Items.Add(pmViF)
  let pmVdF = new MenuItem()
  pmVdF.Header <- "Decrease Font Size"
  pmVdF.StaysOpenOnClick <- true
  pmVdF.Click.Add(fun e ->
   if sizeOfFont > 10.0 then
    sizeOfFont <- sizeOfFont - 2.0
    popupMenu.FontSize <- sizeOfFont
   )
  let _ = pmV.Items.Add(pmVdF)
  let _ = popupMenu.Items.Add(pmV)
  let pmS = new MenuItem()
  pmS.Header <- "Search"
  let pmSfF = new MenuItem()
  pmSfF.Header <- "For File"
  let pmSfFs = new MenuItem()
  let mutable pmSfFsFb = new TextBox()
  pmSfFsFb.MinWidth <- 40.0
  if Directory.Exists(thisItem.Tag.ToString()) then
   if fname <> "" then
    pmSfFsFb.Text <- fname
   else
    pmSfFsFb.Text <- "???"
  else
   pmSfFsFb.Text <- thisItem.Header.ToString()
  pmSfFsFb.IsReadOnly <- false
  pmSfFs.Header <- pmSfFsFb
  pmSfFs.Tag <- thisItem.Tag.ToString()
  pmSfFs.Click.Add(findFileReq)
  let _ = pmSfF.Items.Add(pmSfFs)
  let _ = pmS.Items.Add(pmSfF)
  let pmSfFsH = new MenuItem()
  pmSfFsH.Header <- "Start Here"
  pmSfFsH.Tag <- thisItem.Tag.ToString()
  pmSfFsH.Click.Add(searchHereReq)
  let _ = pmS.Items.Add(pmSfFsH)

Text Entry for a Menu Item

The 'Search>For String' menu item is a fairly complex section of cose by itself but it shows multiple textboxes in a menu item so it is the next logical candidate for explanation. It also contains some decisional logic as well. We begin with the definition of the 'pmSfs' menu item. We need to add multiple textboxes, so We define a StackPanel. The first textbox We need is for the string 'For String'. We make it read-only because the user should not change it. The second textbox is for the file-name parameter. This parameter is used to store the string used in the GetFiles function and can contain the wildcard characters, '*' and '?'. If fSfname is blank on entry to the menu then the Text of the textbox will be set to fname if a file search has been done, otherwise, it will set fSfname to the treeviewitem's filename unless it is a directory, in which case it is set to '???'. If fSfname is not blank the textbox Text is set to fSfname. Now We define the textbox for the entry of the string to 'search for' parameter, fndStr. If fndStr is blank, We set the Text to '???', otherwise We set it to the the value of fndStr. Next We add the three textboxes to the StackPanel and put the StackPanel into the menu item's Header. Now We put the treeviewitem's Tag into the menu item's Tag and add findStrReq to the Click Event. Finally We add the pmSfs menu item to the pmS menu item.

I hope I have explained this in a way that can be understood by others or at least understood by me in a week's time. Now let's skip back to the findStrReq function and take a look at the code(here it is below but in the program, it is above this point).

F#
 let pmSfS = new MenuItem()
 let cfilePan = new StackPanel()
 cfilePan.Orientation <- Orientation.Horizontal
 let mutable cargBox = new TextBox()
 cargBox.Text <- "For String"
 cargBox.Margin <- new Thickness(10.0,2.0,2.0,2.0)
 cargBox.MinWidth <- 15.0
 cargBox.IsReadOnly <- true
 let mutable cfileBox = new TextBox()
 cfileBox.MinWidth <- 40.0
 if fSfname = "" then
  if Directory.Exists(thisItem.Tag.ToString()) then
   if fname = "" then
    cfileBox.Text <- "???"
   else
    cfileBox.Text <- fname
  else
   cfileBox.Text <- thisItem.Header.ToString()
 else
  cfileBox.Text <- fSfname
 cfileBox.IsReadOnly <- false
 let mutable argBox = new TextBox()
 argBox.Margin <- new Thickness(10.0,2.0,2.0,2.0)
 argBox.MinWidth <- 40.0
 if fndStr = "" then
  argBox.Text <- "???"
 else
  argBox.Text <- fndStr
 argBox.IsReadOnly <- false
 let _ = cfilePan.Children.Add(cargBox)
 let _ = cfilePan.Children.Add(cfileBox)
 let _ = cfilePan.Children.Add(argBox)
 pmSfS.Header <- cfilePan
 pmSfS.Tag <- thisItem.Tag.ToString()
 pmSfS.Click.Add(findStrReq)
 let _ = pmS.Items.Add(pmSfS)
 let pmSfFsN = new MenuItem()
 pmSfFsN.Header <- "Find Next(F3)"
 pmSfFsN.Click.Add(findNextReq)
 let _ = pmS.Items.Add(pmSfFsN)
 let _ = popupMenu.Items.Add(pmS)
 let pmT = new MenuItem()
 pmT.IsEnabled <- false
 pmT.Header <- "Tools"
 let pmTmD = new MenuItem()
 pmTmD.Header <- "Map Network Drive"
 pmTmD.Click.Add(copyReq)
 let _ = pmT.Items.Add(pmTmD)
 let pmTuD = new MenuItem()
 pmTuD.Header <- "Unmap Network Drive"
 let _ = pmT.Items.Add(pmTuD)
 let _ = popupMenu.Items.Add(pmT)
 let pmH = new MenuItem()
 pmH.Header <- "Help"
 let pmHc = new MenuItem()
 pmHc.Header <- "Contents"
 pmHc.Click.Add(cHelpReq)
 let _ = pmH.Items.Add(pmHc)
 let pmHa = new MenuItem()
 pmHa.Header <- "About"
 pmHa.Click.Add(aHelpReq)
 let _ = pmH.Items.Add(pmHa)
 let _ = popupMenu.Items.Add(pmH)
 menuPopup.Child <- popupMenu
 let menuToolTip = new ToolTip()
 menuToolTip.FontSize <- 20.0
 menuToolTip.FontWeight <- FontWeights.Bold
 menuToolTip.Content <- "Menu has KEYBOARD FOCUS! It will " +
  "remain OPEN\nuntil You Click on A FOLDER or FILE"
 popupMenu.ToolTip <- menuToolTip
 menuPopup.IsOpen <- true
 menuPopup.StaysOpen <- false
with
 |e -> eprintf "\n\n Error: %O\n" e

Before We begin the next section, let us look at the last ten lines above. Here We have defined all all of the menu items and now We add the popupMenu to the Popup as a child element. After defining and putting it in the popupMenu We open the Popup and tell it not to stay open if it loses Mouse Focus. This is followed by the 'with' clause of the preceding 'try'. This will catch any errors and send the message to stderr, which is the Console.

The 'Find String' Function

The findStrReq function is defined in the same way as the menuReq function. The first two lines are a 'let binding' to define it and a 'try-with' expression containing the body of the definition. The next line defines the parameter passed to the function and cast it to a menu item. Now it prints two 'newlines'. Then it binds 'sp' to the Header of the itemPassed and casts it to a StackPanel. Extracting the Children of the StackPanel, it binds 'fp' to the second one and casts it to a TextBox. It binds 'cp' to the third child and casts it to a Textbox. The first child is ignored. Now We have to define the parameter for the parseDirInfo function because it is used in different program structures that do not share scope. If the user has entered a file-name string to search for We set fSfname to that string, otherwise, We set it to the value of fname, unless fname is blank, in which case, We set it to 'Happiness' which can not be found unless you create your own happiness. If it is left blank it causes an IO ERROR! Now if the queue is empty or fSfname is not equal to fTfname We set fname to the value of fSfname, even if it is "happiness', then clear the queue, in case We have a change in the file-name search string, set the Window Title and output the search message and begin the search.

If We are in a directory, We build the queue from that directory, otherwise, We define an array of file paths and call parseDirInfo for each string in the array. If We had defined rootDir in the preceding 'if' or 'for' they would be in different scopes and would require two different 'let' bindings. Now We check the queue to see if it contains the file-name of the treeviewitem that was clicked. If it does, We 'peek' at the first entry of the queue that could be pulled off. If it's the one We clicked on We exit the while loop, otherwise, We pull it off and put it on the other end of the queue, then loop for the next one. We exit the while loop with the first available string to be dequeued the file-name of the item that was clicked. If the queue did not contain the file-name We start at the begining of the queue, the first file found. If We have NOT built the queue the next candidate for dequeueing is the one following the last one found by a previous file search.

When fSfname and fTfname are not the same, "<>" - F# not equal operator, We make them equal and do NOT dequeue the next entry because We have just built the queue and since the file-names have changed, We want to find the new search argument in the current directory. If fSfname and fTfname are the same then We haven't built the queue but the 'Find String' parameter may have changed. If so or if this is the first string search We want to search the current file and therefore do NOT pull an entry off the queue. If the Text in the textbox is the same as fndStr then We haven't re-built the queue nor are We searching for a different string so We dequeue the next entry and then put it on the other end of the queue.

F#
let findStrReq (e:RoutedEventArgs) =
 try
  let itemPassed = e.Source:?>MenuItem
  printf "\n\n"
  let sp:StackPanel = itemPassed.Header:?>StackPanel
  let fp:TextBox = sp.Children.[1]:?>TextBox
  let cp:TextBox = sp.Children.[2]:?>TextBox
  let mutable rootDir:System.IO.DirectoryInfo = null
  if fp.Text <> "???" && fp.Text <> "" then
   if fSfname <> fp.Text then
    fSfname <- fp.Text
  else
   if fname <> "" then
    fSfname <- fname
   else
    fSfname <- "Happiness"
  //endif
  if (fQue.Count = 0) || (fSfname <> fTfname) then
   fname <- fSfname
   fQue.Clear()
   mainWindow.Title <- "Searching for " + fname
   printf "\nSearching for %s\n" fname
   if Directory.Exists(itemPassed.Tag.ToString()) then
    rootDir <- new System.IO.DirectoryInfo(itemPassed.Tag.ToString())
    parseDirInf rootDir
   else
    let filStrings:string[] =
     [|
      @"C:\Documents and Settings\Owner\My Documents\sharpDevelop"
      @"C:\Documents and Settings\Owner\My Documents\SharpDevelop Projects"
      @"C:\the Explorer Imperative"
      @"C:\tXi"
      |]
    for s in 0 .. filStrings.Length - 1 do
     rootDir <- new System.IO.DirectoryInfo(filStrings.[s])
     parseDirInf rootDir
     done
   if fQue.Contains(itemPassed.Tag.ToString()) then
    while fQue.Peek() <> itemPassed.Tag.ToString() do
     startInDir <- fQue.Dequeue()
     fQue.Enqueue(startInDir)
     done
   //endif
  if fSfname <> fTfname then
   fTfname <- fSfname
  else
   if fndStr <> "" && cp.Text = fndStr then
   startInDir <- fQue.Dequeue()
   fQue.Enqueue(startInDir)

Continuing the 'findStrReq' function, We define fQcount to determine when We have searched the entire queue, that is, fQcount is greater than or equal to fQue.Count, at which point We stop searching. If the textbox for the 'Find String' function contains anything besides three question marks We put it into fndStr. We change the Window Title and output the searching for string message. We define a string to indicate success or failure. While that string does not indicate success or failure We loop through the queue, reading each file in the queue(if it exists) to see if it contains the search string, fndStr. If it does contain fndStr We indicate success and perform the Console and Screen Output. Having found the string in the file, We call the recursive routine, findit, to find and expand the directory, expanding everything necessary, including the filestring of the target directory to find the file and bring it into view. If fndStr is not found the queue entry is dequeued and re-enqueued. Next the loop count is incremented and if the count is equal to or greater than fQue.Count and the string has mot been found then failure is indicated and the Window Title and textbox text are changed to notify the user of the failure and the while loop is completed. The try-with expression is completed with the error message expression.

F#
 let mutable fQcount = 0
 if cp.Text <> "???" && cp.Text <> "" then
  fndStr <- cp.Text
  mainWindow.Title <- "Searching for String:" + fndStr
  printf "\nSearching for String: %s\n" fndStr
  let mutable strFound = ""
  while strFound <> fndStr && strFound <> "not found" do
   let mutable line:string = ""
   printf "\nLooking in %s\n " (startInDir)
   if File.Exists(startInDir) then
    let sr:StreamReader = new StreamReader(startInDir)
    line <- sr.ReadToEnd()
    sr.Close()
   if (line.Contains(fndStr)) then
    textBox1.Text <- line
    mainWindow.Title <- "Found String:" + fndStr
    strFound <- fndStr
    fQcount <- fQue.Count
    let mutable foundAt = line.IndexOf(fndStr, 0)
    let mutable lineIndex = textBox1.GetLineIndexFromCharacterIndex(foundAt)
    let mutable textLine = ""
    let mutable lastLinePrinted = 0
    printf "\n. . . . . . . . . . . .\n"
    printf "%s " fndStr
    printf "found in ...................\n%s\n\n" (startInDir)
    while foundAt <> -1 do
     if lineIndex > 0 && lastLinePrinted < lineIndex - 1 then
      textLine <- (textBox1.GetLineText(lineIndex - 1)).TrimEnd()
      printf "\n%s " ((lineIndex - 1).ToString())
      printf "%s\n" (textLine)
      lastLinePrinted <- lineIndex - 1
     if lastLinePrinted < lineIndex then
      textLine <- (textBox1.GetLineText(lineIndex)).TrimEnd()
      printf "%s " (lineIndex.ToString())
      printf "%s\n" (textLine)
      lastLinePrinted <- lineIndex
     if lineIndex < textBox1.LineCount && lineIndex = lastLinePrinted then
      textLine <- (textBox1.GetLineText(lineIndex + 1)).TrimEnd()
      printf "%s " ((lineIndex + 1).ToString())
      printf "%s\n" (textLine)
      lastLinePrinted <- lineIndex + 1
      foundAt <- line.IndexOf(fndStr, foundAt + 1)
     if foundAt < line.Length - 1 && foundAt <> -1 then
      lineIndex <- textBox1.GetLineIndexFromCharacterIndex(foundAt)
     done
    printf "\nfound in ...................\n%s\n" (startInDir)
    let mutable qItem = new TreeViewItem()
    let mutable pItem = new TreeViewItem()
    for i in 0 .. (treeTrunk.Items.Count) - 1 do
     pItem <- treeTrunk.Items.[i]:?>TreeViewItem
     if startInDir.Contains(pItem.Tag.ToString()) then
      findIt pItem
     done
   if strFound <> fndStr then
    startInDir <- fQue.Dequeue()
    fQue.Enqueue(startInDir)
    fQcount <- fQcount + 1
   if fQcount >= fQue.Count && strFound <> fndStr then
    mainWindow.Title <- "Can't find String:" + fndStr
    strFound <- "not found"
    textBox1.Text <- "Can't find String:" + fndStr
   done //endWhile
  //
with
 |e -> eprintf "\n\n Error: %O\n" e

The Supporting Cast, 'parseDirInfo' and 'findIt' Functions

These two functions are RECursive functions, meaning they call themselves. Whereas C# functions(for instance) can automatically call themselves, F# functions can call themselves if and only if the 'rec' keyword is included in the 'let binding'(e.g. 'let rec parseDirInf (root:System.IO.DirectoryInfo) ='. The let binding allows the function to call itself. The part in parentheses tells the compiler the name and type of the parameter(s). The colon is a type specifier. If you want the official technical explanation of the keywords, my advice is to RTFM(read the F# Manual). Here We can see that 'root' is a 'System.IO.DirectoryInfo' which contains the directory info passed to it by the calling function, which may be it's self. The first thing we do now is define a file info and another directory info, both of which are mutable, that is, they are not the default NON-Mutable, fixed value objects they could have been if only We had not specified 'mutable'. The next step is a 'try-with' expression to handle any exceptions that might occur getting the 'files' in 'root' that match fname. This is, for the most part, where an error would occur. If it does, files could be null, in which case,We have nothing to enqueue so We get the sub-directories from the 'root' and loop through them, recursing 'parseDirInf' in a 'try-with' expression. There is a potential pit-fall recursing inside a 'try-with' expression. F# optimizes a recursive call that is the last action in a function by using 'tail-recursion' in which the stack frame is dropped so that a very large number of recursion's will not cause a stack over-flow. Since 'tail-recursion' can't be applied you could potentially have a stack-over-flow here on a very large directory. If this is bothersome, elimanate the last 'try-with' and the function will be tail-recursive. The return-type of the function is 'unit', which is analogous to 'void' in C#. This works here because the queue, 'fQue' is defined at the lowest level and can be accessed from any point in the program. If any files matched fname they are now queued up in a 'first in - firdt out' structure ready for use by the program.

F#
let rec parseDirInf (root:System.IO.DirectoryInfo) =
 let mutable files:System.IO.FileInfo[] = null
 let mutable subDirs:System.IO.DirectoryInfo[] = null
 try
  files <- root.GetFiles ( fname )
 with
  |e -> eprintf "\n\n Error: %O\n" e
 if (files <> null) then
  for fi:System.IO.FileInfo in files do
   fQue.Enqueue (fi.FullName)
   printf "%s\n" ( fi.FullName )
   done
 subDirs <- root.GetDirectories ( )
 for dirInfo:System.IO.DirectoryInfo in subDirs do
  try
   parseDirInf dirInfo
  with
   |e -> eprintf "\n\n Error: %O\n" e
  done

The 'findIt' recursive function is called by all of the functions that find a file, including 'findIt' itself. As with 'parseDirInfo', the definition is a 'let rec' binding with a 'try with' expression containing the function code in an exception handler. In the function code We define two mutable objects with the type of treeviewitem. The first defines qItem by calling TreeViewItem's 'new' constructor. The second one defines pItem of type treeviewitem and sets it equal to the itemPassed. Now a 'for' loop takes the i'th element of pItem going from 0 to the count of items minus one and puts them into qItem. This 'for' loop contains an 'if' expression and the recursive call to findit, passing qItem to findIt. The 'if' expression asks if this element's Tag is contained in startInDir. If not it drops down to the recursive call and repeats the loop. If the answer is 'Yes' then it asks if it is equal to startInDir. If 'Yes' then select it and bring it into view, taking mouse focus away from the menu by using Mouse.Capture(focusItem). This is a function call even though the left and right parentheses aren't used in the program code, they could have been but are optional in this case. It is necessary to 'capture the mouse' here because the menu has it and won't let go otherwise. We need to re-build the menu to make sure it will be focused on the new selection and not the one We used to initialize the search. But now We have to release the mouse so We 'capture the mouse', passing it a null value. Since this is the last expression in an 'if' expression, it must have the same type specifier as the last expression in the matching 'else' expression, therefore, We pipe it to 'ignore' with the 'pipe' operator "|>", requiring the same pipe to ignore at the end of the 'else' expression. This operation results in a type of 'unit' as the 'type' of the 'if-else' expression. The first 'mouse capture' of focusItem used a 'let binding' of the wildcard value -'_' to also produce a type of unit. Let us proceed to the 'else' expression. Here We know it is either a directory or there is an error We haven't encountered yet so We then expand the virtual filestring folder and the actual directory folder. To do this We simply clear it and rebuild everything, treating each file as a separate item, just as We treat a directory. The tooltips contain different information, but otherwise the only thing a directory item has that a file item doesn't is a dummynode. This means the file won't have a plus or minus appear to it's left. This code is similar to a section of code in another part of the program but here We clear the node and re-build it, while in the other section of code the filestring item is deleted and the file items are inserted into the node. You can use either approach, of course, to build your treeview nodes. Here We are using a file system as the source of the data but a database of any type could be used instead. In a database application you would probably use an insertion for a new item, rather than re-building the entire node(treeviewitem in WPF).

F#
let rec findIt (itemPassed:TreeViewItem) =
 try
  let mutable qItem = new TreeViewItem()
  let mutable pItem:TreeViewItem = itemPassed
  for i in 0 .. (pItem.Items.Count) - 1 do
   qItem <- pItem.Items.[i]:?>TreeViewItem
   if startInDir.Contains(qItem.Tag.ToString()) then 
    if startInDir = qItem.Tag.ToString() then
     focusItem <- qItem
     let _ = Mouse.Capture focusItem
     focusItem.IsExpanded <- true
     focusItem.BringIntoView()
     focusItem.IsSelected <- true
     Mouse.Capture null|>ignore
    elif Directory.Exists(qItem.Tag.ToString()) then
     if (qItem.Items.Count = 1 && qItem.Items.[0] = dummyNode) then 
      qItem.Items.Clear()
      let mutable insInd = 0
      let info:DirectoryInfo = DirectoryInfo(qItem.Tag.ToString())
      for f:FileInfo in info.GetFiles() do
       let lSubitem = new TreeViewItem ( )
       lSubitem.Header <- f.Name
       lSubitem.Tag <- f.FullName
       lSubitem.FontWeight <- FontWeights.ExtraBold
       lSubitem.ToolTip <- "Size: "+f.Length.ToString() +
       " Index: "+insInd.ToString() + 
       " Attr: "+f.Attributes.ToString() +  
       "\nDate Modified: "+f.LastWriteTime.ToString() +
       "\nDate Accessed: "+f.LastAccessTime.ToString() +
       "\nDate Created : "+f.CreationTime.ToString()
       qItem.Items.Add(lSubitem)|>ignore
       insInd <- insInd + 1 
       done
      for s in Directory.GetDirectories ( qItem.Tag.ToString ( ) ) do
       let tSubitem = new TreeViewItem ( )
       tSubitem.Header <- s.Substring(s.LastIndexOf("\\") + 1)
       tSubitem.Tag <- s
       tSubitem.FontWeight <- FontWeights.ExtraBold
       let _ = tSubitem.Items.Add ( dummyNode )
       tSubitem.IsExpanded <- false
       qItem.Items.Add ( tSubitem )|>ignore
       done
    findIt(qItem)
   done
 with
  |e -> eprintf "\n\n Error: %O\n" e

The functionality discussed here may seem at first glance to be re-inventing a wheel that has already been re-invented many times over, but recognizing that I have not used or even heard about anymore than a very small percentage of the tools that this functionality might be similar to, I have not seen a product I would rather use. You may prefer the other UI's or you may need the output or you may simply prefer to use regular expressions in a perl script, or maybe you just don't consider this to be a serious tool and don't want to bother with it. That's OK, but you might be surprised if you try it. Just think of it as big old balloon tires on your swamp buggie. You don't want them on your BMW or Caddy but you can still have fun with them. The real purpose of the code is to feed the insatiable hunger for knowledge and comply with the imperative to explore all possibilities.

Background

This Article discusses the 'Search' functions in the 'Explorer Imperative'. The functions use WPF in the Menus and the display in general as well as the .NET Language, F#. This implies the code could be translated to C# very easily. I have no doubt that they could, and they would undoubtedly be just as fast. And XAML may add capabilities you require. But I have often found myself wondering how to get some C# code to work or even compile because there was something that just didn't compute in my limited internal circuitry, but so far F# has given me the clues I needed to get it to work. Maybe it's just that my brain is organized in a way that favors F# or maybe I'm just getting smarter....nah, I'm still the same dumb ol' knucklehead BAL Coder. So it must be something about F#!(I just had to find a way to use 'she bang' in the same article as 'perl').

The Application, 'the Explorer Imperative', is a file system viewer with 'zoom'. It's original purpose was to 'Explore' the file system, not manage it. It does have some file management capabilities but was never intended to be a viable replaxement for Windows Explorer. None of the example functions are supposed to be the best, fastest, most efficient, most understandable or most secure. They are all examples showing "A" way to do something programmatically. I am a proponent of simplicity. I try not to be 'slick' because when I have been 'slick' I have invariably 'slid' into a problem. So I try to avoid un-necessary complexity. The code should be understandable if you have any level of knowledge of F#, whether 'beginner' or 'advanced'. My hope is that it is simple enough to understand even if you aren't familiar with F Sharp. But You will also need the latest version of F# Interactive and Compiler which is freely available from Microsoft and can be run on Windows or Mono. For all the details, including other libraries needed you should 'Bing' 'F#' or 'F Sharp' and choose a link to a Micrsoft Website that is most noticably about F#.

Using the code

The individual code segments use .Net Objects and routed events that are fired from a treviewitem or a menu. There are many applications that use tree structures and menues. Many of them can use some of these functions. Just add the event handler call to the object that wants to initiate the function.

The program executes the same whether you compile it or run it interactively. The only real difference is that you have to create a project to compile it, unless you can do the command line compile. There are some real advantages to having a project, But it's like having a frying pan and a griddle(you can fry your eggs and grill your pancakes at the same time). With a project you can add something by just clicking and the IDE takes care of it, without the project you have to do it manually.

The 'Explorer Imperative' was written as a single file, WPF program, using code-only, no Xaml. For the Keyboard Navigation, I decided to use text input rather than raw input but that required the inclusion of the WPF Event Loop. The code for the WPF Event Loop is the copyrighted property of Microsoft. It is only needed for the script version of the program. The compiled program does not require it. If you create a project and compile the program, you don't need to download the source code for the WPF Evebt Loop or the IwshRuntimeLibrary but instead you should add a com reference to The Windows Script Host Object Model. This creates a wrapper for the com object that allows it to be used like a .Net Object. If the wrapper doesn't match the com object or if the com object is not present, the 'Create a Short Cut' function will not be available. In this case, delete the munuitem and the event handler for it. I stated previously that F# ran on Windows and Mono. This does not mean that this prohram will work on Mono. I do not know if and can not guarantee that, any particular section of code will work in the same way or even at all on Mono as it does on Windows.

I am writing this program(and Article) using a text editor and Microsoft F# Interactive. I periodically do compile the program to make sure it compiles and produces the same results as the script. I currently do not have a Mono Environment installed on my XP. With my limited resources, I can't check all possiby situations. I hope it works for you, maybe you can use something in this code in your own programs.

History

This article is devoted to a discussion of the new functionality added to the Explorer Imperative, specifically the SEARCH functions, concentrating on the find string function for illustration purposes. The popup and context menu are discussed as they are required to invoke the search methods. I regard all of the code for the menues, including the functions that increase and decrease the font size for the menus as new code even if it was in the original program because it was not discussed at that time. Akthough the menues themselves were working as they do now the code was not showing up in the article. That's okay because I also did not provide any explanation of the code involved. For that reason I won't list any code chamges unless thay are bug fixes or noticable changes in functionality.

Here are the changes you should be aware of;

  • The 'File>New>File' function contained a bug in the code that extracted the parameters from the menu item. This bug has been corrected in this version.
  • The 'File>New>Folder' function contained a bug in the code that extracted the parameters from the menu item. This bug has been corrected in this version.
  • The 'View>Increase Font Size' function was changed to increase the size of the menu instaead of the size of the treeview.
  • The 'View>Decrease Font Size' function was changed to decrease the size of the menu instaead of the size of the treeview.
  • The 'Search' Menu has been enabled and functions added to 'Find a File', 'Start the search from a specific Directory', 'Find a String' and print out the context in which it is used and 'Find the Next File' using either the menu or the F3 Key.

That's it for now! May the F#RCE be with You!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --