Your Starting Directory
List of Files in Folder
The "tList" expanded
HTML File showing Tool Tip
Press <ENTER> to OPEN File
Introduction
Three Imperatives
Each man is born with three imperatives: the need to eat, the need to sleep
and the need to Explore. Later on, other needs arise but that's the stuff for
another story!
This article discusses the Imperative to Explore, ergo, "the Explorer
Imperative". Actually it means 'an Explorer like program written in straight
Imperative F#'; as opposed to Functional F#(FunF) or Object Oriented F#(OOF).
Some more advanced F# programmers will say that to program in this style is an
ImperFect and in-appropriate use of the F# Language, but I'm a newbie to the nth
degree! My background is in IBM Main Frame languages, such as Bal, Cobol, Rexx
and Dialog Manager.
I wrote this article because while I was learning F# syntax, a weird thing
happened! I would write something down and it would just work, just like I
wanted it to. The only times it didn't work, it was because I had the syntax
wrong. And, incredably, it told me how to fix the error. I was amazed! I wanted
to share my amazement with everyone. I humbly submit this work as an example of
what can be done with a very small portion of this very powerful language.
Prerequisites
This sample requires F# Interactive(FSI.EXE) and/or the F#
Compiler(FSC.EXE) which is available for download from the F#
Development Center at FSharp.net, as well
as a C# IDE of some sort. Also required are the WPF Libraries, included in
Visual Studio 2010 Express, or another environment which will allow you to
create an F# Project and add COM References, needed for an example of how to
create a shortcut to the Desktop. All the examples work in both Interactive and
Compiled Environments but Interactive is a lot more fun. And if you are good
with command line you could use TLBIMP to create
"Interop.IWshRuntimeLibrary.dll" from 'wshom.ocx'. If you have
available an IDE and Compiler jusr add a Reference, selecting the "COM" tab and
click IWshRuntimeLibrary
. This can be done in an C# Project if you
can't create an F# Project in your IDE.
Description
This sample contains an fsx script that can be interpreted by
'FSI.EXE' - F# Interactive or 'save as' an 'fs' file, add it to an F#
Project. 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 interpreted mode.
The Application, 'the Explorer Imperative'
This application started as a file system viewer with ZOOM but I soon
discovered I could not stop adding functionality. It had taken over my mind. I
was expecting Arnold to pop up. Finally it allowed me to stop but it said 'Go!
Tell it on the Internet'.
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 event responses for both MOUSE and KEYBOARD Events.
If your application uses these responses then it needs a WPF EVENT LOOP. This
application doesn't depend soley on the base set of responses, so most of the
examples presented in this article work without it. This line, '//#load
"WPFEventLoop.fsx"', can be uncommented if you 'bing' "WPFEventLoop" to find the
Copywrited Micro Soft source code and either download it or type it in. The
version I typed in had a reference to an obsolete function, 'rethrow', but
fsi.exe told me to change it to 'reraise'. It worked great, but any EVENT
HANDLER you write doesn't require it.Of course, the compiled program has has
it's own WPF Event Loop, so it isn't loaded.
In the first section, We have the references that need to be added to the
Project Definition when you define the project. I assume you are using an IDE
that can create an F# Project. I therefore assume that you are not using the
command line compiler. If you can only create a C# Project, You can use it to
create Interop.IWshRuntimeLibrary.dll which will show in the project
references as 'IWshRuntimeLibrary
'. To add it, Right Click th
references node in the Project Explorer ans click 'Add Reference'. Click the
'COM' Tab click any entry, then type 'w', hold down that key or the down arrow
until you get down to 'Windows Script Host Object Model'. Select it. This will
create a wrapper for the 'wshom.ocx' file in your File System and place it in
your project. This is the preffered version, since the version that works on my
File System nay not work on yours. Although this is last in the "#r's", it must
be first in the references node and in the open statements.
#light
#if INTERACTIVE
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r "PresentationCore.dll"
#r "PresentationFramework.dll"
#I @"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319"
#r "System.dll"
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.5"
#r "System.Core.dll"
#I @"C:\WINDOWS\Microsoft.NET\Framework\v4.0.30319"
#r "System.Xaml.dll"
#r "System.Xml.dll"
#I @"C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0"
#r "WindowsBase.dll"
#r @"C:\Documents and Settings\Owner\My Documents\SharpDevelop Projects\
the Explorer Imperative\the Explorer Imperative\bin\Debug\Interop.IWshRuntimeLibrary.dll"
#endif
open IWshRuntimeLibrary
open System
open System.Collections.Generic
open System.ComponentModel
open System.Diagnostics
open System.IO
open System.IO.IsolatedStorage
open System.Linq
open System.Security.Permissions
open System.Text
open System.Windows
open System.Windows.Controls
open System.Windows.Controls.Primitives
open System.Windows.Data
open System.Windows.Documents
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Imaging
open System.Windows.Navigation
open System.Windows.Shapes
open System.Windows.Threading
open System.Xaml
In the next section, the mainWindow and the objects that will be put into it
are defined. At the end of this section is a block comment, delimited by a left
parenthesise and an asterisk('*') at the beginning of rhe comment and an
asterisk, followed by a right parenthesise at the end of the comment. Delete
these 2 lines leaving the 3 lines of code in between the 2 lines and paste the
code up to that point into F# INTERACTIVE. A WPF WINDOW will be displayed with
the cHelpStr from the code below in the textbox. You can GRAB the SPLITTER BAR
with MOUSE and Drag it down to make the textbox bigger. You can interact with
the Window by using the MOUSE but that's about it.
If you have downloaded the
source code either delete the entire comment or don't touch it. If the window is
shown at this point, the tree won't get loaded, so the only thing new is, you
can interact with the textbox, but that is all. You can delete the show
function, but you will get some error messages, so just delete the entire
comment.
let dummyNode = null
let mutable copyArgs:string = null
let mutable pasteArgs:string = null
let mutable execArgs:string = null
let mutable ifl = IsolatedStorageFile.GetUserStoreForAssembly()
let mutable startInDir = @"C:\Documents and Settings\Owner\My Documents"
let mutable nextItem = new TreeViewItem()
let mutable focusItem = new TreeViewItem()
let mainWindow = new Window()
mainWindow.Title <- "Loading the Explorer Imperative"
mainWindow.Width <- 860.0
mainWindow.Height <- 680.0
let myGrid = new Grid()
myGrid.MinWidth <- 40.0
myGrid.MinHeight <- 20.0
myGrid.HorizontalAlignment <- HorizontalAlignment.Stretch
myGrid.VerticalAlignment <- VerticalAlignment.Top
myGrid.IsEnabled <- true
myGrid.Focusable <- true
let rowDef1 = new RowDefinition()
rowDef1.Height <- new GridLength(46.0)
let rowdefSplitterRow = new RowDefinition()
rowdefSplitterRow.Height <- new GridLength(10.0)
let rowDef = new RowDefinition()
rowDef.Height <- new GridLength(1.0)
let rowDef2 = new RowDefinition()
rowDef2.Height <- new GridLength(1.0, GridUnitType.Star)
myGrid.RowDefinitions.Add(rowDef1)
myGrid.RowDefinitions.Add(rowdefSplitterRow)
myGrid.RowDefinitions.Add(rowDef)
myGrid.RowDefinitions.Add(rowDef2)
let textBox1 = new TextBox()
let mutable cHelpStr =
"PRESS the SHIFT and Tab Key together! Then PRESS the Down " +
"Arrow Key. This sequence gives the KEYBOARD FOCUS to the " +
"Splitter Bar and will increase the number of VISIBLE ROWS " +
"of this TEXTB0X, that is, the textbox containing this text.\n\n" +
"If you select Help>Contents from the MENU, the SPLITTER BAR " +
"has KEYBOARD FOCUS so just Press the DOWN ARROW KEY.\n\n" +
"Some information may span many lines. You can make the textbox " +
"any size you want. You can even make it disappear. Press the " +
"UP Arrow Key to Decrease the Visible Rows. To HIDE the TextBox " +
"hold down the Up Arrow Key. The SPLITTER BAR can also be " +
"GRABBED with the Mouse. If it is HIDDEN, HOLD DOWN the Shift " +
"KEY and PRESS the TAB Key, then release the SHIFT Key and " +
"PRESS the DOWN Arrow Key if you are in the EXPLORER panel. " +
"Press TAB Key to go from the SPLITTER BAR to the EXPLORER " +
"PANEL. TAB again to go to the TextBox. TAB TWICE to ESCAPE " +
"the TextBox.\n\n" +
"To activate the MENU place the Mouse Pointer on the " +
"target item and PRESS the RIGHT MOUSE BUTTON. A 'MENU' " +
"will 'Popup'with a Horizontal Alignment. If the MOUSE " +
"Pointer is on the MENU, it will Capture the MOUSE when " +
"the BUTTON is released. Some factors will cause the Menu " +
"to be off of the selected Item. When this happens the MOUSE " +
"will FOCUS on the Item it is on. You will need to move " +
"the pointer and re-select the Item. If the MOUSE Pointer " +
"is not on the Horizontal Menu a 'Context Menu' will popup. " +
"The same MENU Selections are available on the contex menu " +
"as are available on the Popup Menu.\n\n" +
"Zoom works the same as Internet EXPLORER. Hold down the Control " +
"key and roll the MOUSE Wheel.\n\n" +
"To SCROLL Horizontally, 'nudge' the Wheel to the left or " +
"to the right, then roll the Wheel. The SCROLLBAR will show" +
"whether you can scroll vertically or horizontally.\n\n" +
" ---------------- END ----------------\n\n\n"
textBox1.Text <- cHelpStr
textBox1.FontSize <- 32.0
textBox1.AcceptsReturn <- true
textBox1.FontWeight <- FontWeights.Bold
textBox1.Width <- 1280.0
textBox1.TextWrapping <- TextWrapping.WrapWithOverflow
textBox1.TabIndex <- 2
Grid.SetRow(textBox1, 0)
let myGridSplitter = new GridSplitter()
Grid.SetRow(myGridSplitter, 1)
myGridSplitter.HorizontalAlignment <- HorizontalAlignment.Stretch
myGridSplitter.VerticalAlignment <- VerticalAlignment.Top
myGridSplitter.MinHeight <- 10.0
myGridSplitter.Height <- 10.0
myGridSplitter.TabIndex <- 0
let myScrollViewer = new ScrollViewer()
myScrollViewer.VerticalAlignment <- VerticalAlignment.Top
myScrollViewer.VerticalScrollBarVisibility <- ScrollBarVisibility.Visible
myScrollViewer.HorizontalScrollBarVisibility <- ScrollBarVisibility.Hidden
myScrollViewer.CanContentScroll <- true
myScrollViewer.IsEnabled <- true
myScrollViewer.Focusable <- true
Grid.SetRow(myScrollViewer, 3)
let mutable myComputer = new TreeView()
myComputer.IsEnabled <- true
myComputer.Focusable <- true
myComputer.TabIndex <- 1
let treeTrunk = new TreeViewItem()
treeTrunk.Header <- "My Computer"
treeTrunk.FontSize <- 32.0
treeTrunk.FontWeight <- FontWeights.Bold
let _ = myGrid.Children.Add(textBox1)
let _ = myGrid.Children.Add(myGridSplitter)
let _ = myComputer.Items.Add(treeTrunk)
let _ = myScrollViewer.Content <- myComputer
let _ = myGrid.Children.Add(myScrollViewer)
(* Delete or comment(
mainWindow.Content <- myGrid
mainWindow.Show ()
mainWindow.Title <- "the Explorer Imperative" Show the Title
Delete or comment(
This function advances the cursor to the next item that begins with
the character you type. The routine uses text input, rather than raw input so it
is case sensitive. It wraps around, so the next hit may be before the current
position. The method used is to find the first hit, then skip to the current
position and begin the search for the next hit. If a hit is not found the
current item retains focus. The hits are given focus as they occur. This means,
the 'next' hit takes the focus away from the first hit, if it has it. This
routine is not an eventhandler
, but is a
recursive function. The 'rec
' keyword specifies
recursion, otherwise a recursive call will cause a compile time error. In other
words, F# is not automatically recursive.
let rec findThisChar(thisItem, currentItem, eKeyToString, foundit) =
let mutable eKey:string = eKeyToString
let mutable pItem:TreeViewItem = thisItem
let mutable item:TreeViewItem = currentItem
let mutable qItem = new TreeViewItem()
let mutable whatFound:string = foundit
for i in 0 .. (pItem.Items.Count) - 1 do
qItem <- pItem.Items.[i]:?>TreeViewItem
if whatFound <> "next" then
if (qItem.Header.ToString().StartsWith(eKey) ) then
if whatFound = "current" then
whatFound <- "next"
Keyboard.Focus qItem|>ignore
if whatFound = "nothing" then
whatFound <- "first"
Keyboard.Focus qItem|>ignore
if qItem.Tag = item.Tag then
whatFound <- "current"
if qItem.IsExpanded then
if whatFound <> "next" then
whatFound <- findThisChar(qItem, item, eKey, whatFound)
whatFound
This next routine is an eventhandler for the treviewitem 'treeTrunk'. It 'bubbles up'
so it applies to all of the 'sub-nodes' of treeTrunk. This is the primary keyboard
interface. It handles the cursor keys and the enter key. A detailed description of
this routine will be provided in the near future.
let keyUpDetected(e:KeyEventArgs) =
try
let mutable item = new TreeViewItem()
if (e.Source :? TextBox) then
e.Handled <- true
elif (e.Source :? TreeViewItem) then
item <- e.Source:?>TreeViewItem
item.Focusable <- true
item.IsEnabled <- true
item.IsSelected <- true
textBox1.Text <- item.Tag.ToString()
if e.Key = Key.Right then
if File.Exists(item.Tag.ToString()) then
try
let filePopup = new Popup()
filePopup.PlacementTarget <- e.Source:?>TreeViewItem
filePopup.VerticalOffset <- -40.0
filePopup.HorizontalOffset <- 20.0
let mutable fileBox = new TextBox()
fileBox.MinWidth <- 40.0
fileBox.Text <- item.Header.ToString()
fileBox.IsReadOnly <- true
let mutable argBox = new TextBox()
argBox.Margin <- new Thickness(20.0,2.0,2.0,2.0)
argBox.MinWidth <- 40.0
argBox.IsReadOnly <- false
argBox.AcceptsReturn <- true
argBox.MaxLines <- 1
let mutable filePan = new StackPanel()
filePan.Orientation <- Orientation.Horizontal
let _ = filePan.Children.Add(fileBox)
let _ = filePan.Children.Add(argBox)
let fAMenu = new Menu()
let fAFep = new MenuItem()
fAFep.Header <- filePan
fAFep.Tag <- item.Tag
fAFep.Click.Add(eXecReq)
let _ = fAMenu.Items.Add(fAFep)
filePopup.Child <- fAMenu
filePopup.IsOpen <- true
let _ = Keyboard.Focus argBox
filePopup.StaysOpen <- false
with
|e -> eprintf "\n\n ERROR: %O\n" e
elif item.HasItems = false then
if e.Key = Key.Enter then
if File.Exists(item.Tag.ToString()) then
let itemToOpen = item.Tag.ToString()
let info:ProcessStartInfo = new ProcessStartInfo(itemToOpen)
let rc = Process.Start(info)
let _ = Keyboard.Focus item
textBox1.Text <- "Opened " + itemToOpen
else
item.Items.Clear()
item <- item.Parent:?>TreeViewItem
item.Items.RemoveAt(0)
let mutable insInd = 0
let info:DirectoryInfo = DirectoryInfo(item.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()
item.Items.Insert(insInd, lSubitem )
insInd <- insInd + 1
done
Keyboard.Focus item|>ignore
myScrollViewer.PageLeft()
textBox1.Text <- item.Tag.ToString()
textBox1.Text <- item.Tag.ToString()
else
if e.Key = Key.Enter then
if item.IsExpanded then
item.IsExpanded <- false
item.Items.Clear()
let _ = item.Items.Add ( dummyNode )
item.IsExpanded <- true
textBox1.Text <- item.Tag.ToString()
mainWindow.Title <- item.Tag.ToString()
focusItem <- item
with
|e -> eprintf "\n\n ERROR: %O\n"
This eventhandler increses and decreases the textbox font when you hold down the
control key and rotate the mouse wheel.
let textBox_MouseWheel(e:MouseWheelEventArgs) =
if Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl) then
if e.Delta > 0 then textBox1.FontSize <- textBox1.FontSize + 2.0
if e.Delta < 0 then
if textBox1.FontSize > 12.0 then textBox1.FontSize <- textBox1.FontSize - 2.0
This eventhandler
handles font size and scrolling for
the treeTrunk
. WinForms handles both vertical and horizontal
scrolling, but not WPF. Therefore, if you need to scroll horizontally, you have
to handle it yourself. The ScrollViewer
does support it but doesn't
automatically provide it. At least, I couldn't find it. I have provided 2 ways
to scroll horizontally. The first is to hold down the shift key and rotate the
wheel. The second method detects when the delta property is equal to zero.
This means the wheel has been tilted. I know of no way to determine which
way it tilted so I can not scroll left or right but I do know the user
wants to scroll horizontally so I turn off the vertical scroll bar and turn on
the horizontal scroll bar. Now the user can rotate the mouse wheel, UP becomes
RIGHT, DOWN becomes LEFT. You can reverse this if you wish. If you can determine
the direction of tilt then you can use the tilt to drive horizontal scrolling.
You could use the F# Interoperability but I am not doing that now.
Now to scroll vertically the user tilts the wheel and the
eventhandler turns off the horizontal scroll bar and turns on the vertical
scroll bar. This way the user que and the function's que are the same. You can't
change one and forget to change the other, since they are the same.
let folders_MouseWheel(e:MouseWheelEventArgs) =
if Keyboard.IsKeyDown(Key.RightShift) || Keyboard.IsKeyDown(Key.LeftShift) then
if e.Delta < 0 then myScrollViewer.LineLeft()
if e.Delta > 0 then myScrollViewer.LineRight()
elif Keyboard.IsKeyDown(Key.RightCtrl) || Keyboard.IsKeyDown(Key.LeftCtrl) then
if e.Delta > 0 then treeTrunk.FontSize <- treeTrunk.FontSize + 2.0
if e.Delta < 0 then
if treeTrunk.FontSize > 12.0 then treeTrunk.FontSize <- treeTrunk.FontSize - 2.0
elif myScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Visible then
if e.Delta < 0 then myScrollViewer.LineDown()
if e.Delta > 0 then myScrollViewer.LineUp()
if e.Delta = 0 then
myScrollViewer.VerticalScrollBarVisibility <- ScrollBarVisibility.Hidden
myScrollViewer.HorizontalScrollBarVisibility <- ScrollBarVisibility.Visible
elif myScrollViewer.VerticalScrollBarVisibility = ScrollBarVisibility.Hidden then
if e.Delta < 0 then myScrollViewer.LineLeft()
if e.Delta > 0 then myScrollViewer.LineRight()
if e.Delta = 0 then
myScrollViewer.VerticalScrollBarVisibility <- ScrollBarVisibility.Visible
myScrollViewer.HorizontalScrollBarVisibility <- ScrollBarVisibility.Hidden
The next routine handles the textinput and calls the recursive
routine to advance the cursor to the next item that begins with the typed
character.
let textInput(e:TextCompositionEventArgs)=
if (e.Source :? TreeViewItem) then
let mutable item = new TreeViewItem()
item <- e.Source:?>TreeViewItem
let mutable eKey = new String(null)
eKey <- e.Text
let mutable whatFound:string = "nothing"
let mutable pItem = new TreeViewItem()
for i in 0 .. (treeTrunk.Items.Count) - 1 do
pItem <- treeTrunk.Items.[i]:?>TreeViewItem
if pItem.IsExpanded then
whatFound <- findThisChar(pItem, item, eKey, whatFound)
This next bit is the event handler that populates the 'nodes' when
they have their 'IsExpanded
' property set to 'true
'.
This algorithm builds a string
containing the files in this
Directory
, separated by spaces. It is placed before the sub-folders
so that it is adjacent to it's parent folder. The key-up eventhandler contains
the code to 'expand' the string into a 'list of nodes', allowing the selection
and processing of individual files.
It also sets the 'IsExpanded
' property for any folder
that is contained in the user defined Starting Directory. Whe the 2 values are
equal, 'focusItem
', the 'item
' that is to receive
Focus when the Window is shown is set to the current item,
'tSubitem
'.
let folder_Expanded(e:RoutedEventArgs)=
myScrollViewer.PageLeft()
let mutable item = new TreeViewItem()
item.Focusable <- true
item <- e.Source:?>TreeViewItem
if (item.Items.Count = 1 && item.Items.[0] = dummyNode) then do
item.Items.Clear()
try
let sb = new StringBuilder(32)
for s in Directory.GetFiles ( item.Tag.ToString ( ) ) do
sb.Append(" "+s.Substring ( s.LastIndexOf ( "\\" ) + 1 ))|>ignore
done
if (sb.ToString() <> "") then do
let mutable fs = sb.ToString()
fs <- fs.Substring (1)
let lSubitem = new TreeViewItem ( )
lSubitem.Header <- fs
lSubitem.Tag <- item.Tag
lSubitem.FontWeight <- FontWeights.ExtraBold
item.Items.Add ( lSubitem )|>ignore
for s in Directory.GetDirectories ( item.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 )
item.Items.Add ( tSubitem )|>ignore
if startInDir.Contains(tSubitem.Tag.ToString()) then
if startInDir <> tSubitem.Tag.ToString() then
tSubitem.IsExpanded <- true
if startInDir = tSubitem.Tag.ToString() then
focusItem <- tSubitem
done
with
|e -> eprintf "\n\n ERROR: %O\n" e
The line below is a 'lambda' or unnamed function to handle the
'FrameWorkElement.Loaded' event which is fired when the element
(mainWindow
) is laid out, rendered, and ready for interaction. The
'fun(ction)' will first attempt to read 'Isolated Storage' for this assembly,
based on the assemblies strong name, to get the user defined 'Starting
Directory'. If this fails, an error message is printed to standard error and the
programmed value is used. You can change this by editting the 'let' statement
that defines 'startInDir'. The 'IsolatedStorageFile
', 'ifl' is
defined at the same program location because both stream reader and stream
writer must both use the same 'object
'.
As the comment says, if you (the program) got there, you have read the
starting directory and the cursor should be positioned on it.
mainWindow.Loaded.Add(fun _ ->
try
let mutable isf:IsolatedStorageFileStream =
new IsolatedStorageFileStream("startInDir.ips", FileMode.Open, ifl)
let mutable sr:StreamReader = new StreamReader(isf)
startInDir <- sr.ReadToEnd()
sr.Close()
with
|e -> eprintf "\n\n ERROR: %O\n" e
The next line gets the collection of 'drives' on the system. This is followed
by an error handler, (try..with), for the 'for ? in ??' loop it encapulates. The
loop defines the 'node', 'let item = ...' and populates it with the drive
information. In this loop a routed event call is added to the item's events
collection. A 'dummyNode
' is added so the 'node' can be
'Expanded
'. This combination eliminates the need to recursively
populate the tree.
Note that if the Starting Directory 'Contains
' the items's
'Tag
' the item is 'expanded'. Next the item is brought into view
and selected. With that We are 'done' with the loop but not the function.
let drives = Directory.GetLogicalDrives()
try
for d in drives do
let item = new TreeViewItem()
item.Header <- d
item.Tag <- d
item.FontWeight <- FontWeights.Normal
let _ = item.Items.Add(dummyNode)
item.Expanded.Add(folder_Expanded)
let _ = treeTrunk.Items.Add(item)
if startInDir.Contains(item.Tag.ToString()) then
item.IsExpanded <- true
item.BringIntoView()
item.IsSelected <- true
done
with
|e -> eprintf "\n\n ERROR: %O\n" e
This last block of the 'Loaded
' function adds the
eventhandlers
for the user interactions. Note the
MouseRightButton
events. The
PreviewMouseRightButtonDown
could be written with or without the preview prefix but it is recommended
that the prefix be used. The other button however,
MouseRightButtonUp
event can not use the preview prefix in combination with
PreviewMouseRightButtonDown
event. If you want to use only the
context menu then comment the 'menuReq' line. These are the 2 lines you would
comment if you pasted these code segments, without the menu definitions, into F#
INTERACTIVE (don't forget the ';;').
Finally We finish the 'Loaded
' function by bringing the
focusItem
into view and selecing it. The Right Parenthesis marks
the end of the function.
Now the only thing left to do is put the Grid into the Window, show the
Window, and it's Title and give Keyboard Focus to 'focusItem
'.
textBox1.PreviewMouseWheel.Add(textBox_MouseWheel)
treeTrunk.PreviewMouseLeftButtonDown.Add(focusReq)
treeTrunk.PreviewKeyUp.Add(keyUpDetected)
treeTrunk.PreviewTextInput.Add(textInput)
myComputer.PreviewMouseWheel.Add(folders_MouseWheel)
treeTrunk.IsEnabled <- true
focusItem.IsExpanded <- true
focusItem.BringIntoView()
focusItem.IsSelected <- true
)
mainWindow.Content <- myGrid
mainWindow.Show ()
mainWindow.Title <- "the Explorer Imperative"
focusItem.Focus()
#if COMPILED
[<STAThread>]
[<EntryPoint>]
let main(_) = (new Application()).Run(mainWindow)
#endif
There is a great deal of functionality in this program that I have not yet
explained but I feel the need to write another article and it is imperative that
I do not try to put it all in this one.
Background
I often find it necessary, for various reasons, to use a screen resolution
that displays fonts in a size that is very hard for me to read. I have long
intended to write a utility to augment Windows Explorer with a zoom feature,
similar to IE. I wrote this for personal use but found that just a zoom feature
was usually not enought incentive to execute it Then I added some other
functions I would like to see in Windows Explorer, along with the ability to
send a folder to Explorer when I wanted to do something I had not yet put into
my program.
Using the Code
The individual code seqments use .NET Objects and routed events that are
fired from a treviewitem or a menu. There aremany 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 it or run it
unteractively. The only real difference is 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 ca fry your eggs and make 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. Idecided to use text input rather than raw input but that
required the inclusion of the WPF Event Loop. This code is the copyrighted
property of Micro Soft. 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 should not downkoad the IwshRuntimeLibrarybut 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' functionality will not be available. In this case, delete the
munuitem and the event handler for it.
History
- 13th July, 2011: Initial version