Explorer 的命令
带字体缩放的文件系统查看器
您的起始目录
文件夹中的文件列表
展开的“tList”
显示工具提示的 HTML 文件
按 <ENTER> 打开文件
引言
三个指令
每个人天生就有三种指令:吃饭的需求,睡觉的需求和探索的需求。之后,其他需求会出现,但那是另一回事了!
本文讨论了探索的指令,即“探险者指令”。实际上,它意味着“一个用纯指令式 F# 编写的探险者式程序”;而不是函数式 F# (FunF) 或面向对象式 F# (OOF)。一些更高级的 F# 程序员会说,以这种风格编程是 F# 语言的不完美且不恰当的用法,但我已经是十足的新手了!我的背景是 IBM 大型机语言,例如 Bal、Cobol、Rexx 和 Dialog Manager。
我写这篇文章是因为在我学习 F# 语法时,发生了一件奇怪的事情!我写了一些东西,它就能正常工作,就像我想要的那样。唯一不起作用的时候,是因为我的语法是错误的。而且,令人难以置信的是,它告诉我如何修复错误。我惊呆了!我想和大家分享我的惊奇。我谦卑地提交这项工作,作为这个非常强大的语言的一小部分可以做什么的例子。
必备组件
此示例需要 F# Interactive(FSI.EXE) 和/或 F# 编译器(FSC.EXE),可从 F# 开发中心下载,网址为 FSharp.net,以及某种 C# IDE。还需要 WPF 库,包含在 Visual Studio 2010 Express 中,或允许您创建 F# 项目并添加 COM 引用的其他环境,这是创建桌面快捷方式的一个示例所必需的。所有示例都可以在交互式和已编译的环境中运行,但交互式更有趣。如果您熟悉命令行,可以使用 TLBIMP 从 'wshom.ocx' 创建 "Interop.IWshRuntimeLibrary.dll"。如果您有 IDE 和编译器,只需添加一个引用,选择“COM”选项卡,然后单击 IWshRuntimeLibrary
。如果您无法在 IDE 中创建 F# 项目,也可以在 C# 项目中完成此操作。
描述
此示例包含一个 fsx 脚本,可以由 'FSI.EXE' - F# Interactive 解释,或“另存为”一个 'fs' 文件,并将其添加到 F# 项目中。您可以选择任何一种,我没有包含项目,因为对于那些资源有限、只能使用 FSI Interactive 来尝试让已编译程序在解释模式下运行的人来说,创建项目要容易得多。
应用程序,“探险者指令”
这个应用程序最初是一个带有缩放功能的 C# 文件系统查看器,但我很快发现我无法停止添加功能。它已经占据了我的思绪。我曾预料到阿诺德会弹出来。最终,它让我停了下来,但它说:“走!在互联网上告诉大家”。
该应用程序是一个纯代码的 WPF 窗口。没有 XAML。该窗口包含一个带有 3 行 1 列的 Grid。第一行包含一个 Textbox。第二行仅包含一个 Grid Splitter。这用于调整其他两行的身高。第三行在一个 Scrollviewer 中包含一个 Treeview。
脚本版本可以在有或没有 WPF 事件循环的情况下运行。WPF 事件循环为鼠标和键盘事件提供了一组基本的事件响应。如果您的应用程序使用了这些响应,那么它就需要一个 WPF 事件循环。此应用程序不完全依赖于基本响应集,因此本文中的大多数示例都可以独立运行。如果“bing”搜索“WPFEventLoop”找到微软的版权源代码,您可以取消注释此行 '//#load "WPFEventLoop.fsx"',并下载或输入它。我输入的版本引用了一个已弃用的函数“rethrow”,但 fsi.exe 告诉我将其更改为“reraise”。它运行得很好,但您编写的任何事件处理程序都不需要它。当然,已编译的程序有自己的 WPF 事件循环,所以它不需要加载。
在第一部分,我们有在定义项目时需要添加到项目定义中的引用。我假设您使用的是可以创建 F# 项目的 IDE。因此,我假设您不使用命令行编译器。如果您只能创建 C# 项目,您可以使用它来创建 Interop.IWshRuntimeLibrary.dll,该文件将显示在项目引用中,名为 'IWshRuntimeLibrary
'。要添加它,请在项目资源管理器中右键单击引用节点,然后单击“添加引用”。单击“COM”选项卡,单击任何条目,然后键入“w”,按住该键或向下箭头直到您向下滚动到“Windows Script Host Object Model”。选择它。这将为文件系统中的 'wshom.ocx' 文件创建一个包装器,并将其放入您的项目中。这是首选版本,因为在我文件系统上工作的版本可能在您的文件系统上无法工作。尽管这是“#r”中的最后一个,但它必须在引用节点和 open 语句中的第一个。
#light
#if INTERACTIVE
//#load "WPFEventLoop.fsx"
#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
在下一节中,定义了 mainWindow 和将要放入其中的对象。在本节末尾,有一个块注释,以左括号和星号(*)开头,以星号和右括号结束。删除这两行,保留中间的三行代码,然后将直到此点的代码粘贴到 F# INTERACTIVE 中。将显示一个 WPF 窗口,其中包含下面代码中的 cHelpStr 在文本框中。您可以用鼠标“抓取”SPLITTER BAR 并将其向下拖动以使文本框变大。您可以使用鼠标与窗口交互,但仅此而已。
如果您下载了源代码,请删除整个注释,否则不要动它。如果此时显示了窗口,树将不会被加载,所以唯一的新功能是您可以与文本框交互,但仅此而已。您可以删除 show 函数,但会收到一些错误消息,所以只需删除整个注释。
let dummyNode = null
let mutable copyArgs:string = null
let mutable pasteArgs:string = null
let mutable execArgs:string = null
//
// define 'ifl' here so streamreader and streamwriter
// will be using the same object.
//
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()
//
// Create the application's main window
let mainWindow = new Window()
mainWindow.Title <- "Loading the Explorer Imperative"
mainWindow.Width <- 860.0
mainWindow.Height <- 680.0
// Create the Grid
let myGrid = new Grid()
myGrid.MinWidth <- 40.0
myGrid.MinHeight <- 20.0
myGrid.HorizontalAlignment <- HorizontalAlignment.Stretch //Left
myGrid.VerticalAlignment <- VerticalAlignment.Top
myGrid.IsEnabled <- true
myGrid.Focusable <- true
//
// Define the Rows
//
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)
// Add the rows to the Grid
myGrid.RowDefinitions.Add(rowDef1)
myGrid.RowDefinitions.Add(rowdefSplitterRow)
myGrid.RowDefinitions.Add(rowDef)
myGrid.RowDefinitions.Add(rowDef2)
// Create the text box and output instructions for splitter
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
// Tell the text box where to go in the Grid
Grid.SetRow(textBox1, 0)
// Create the gridSplitter and tell it where to go in the Grid
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
// Create the scrollViewer and tell it where to go in the Grid
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)
// Create the root for the treeTrunk
let mutable myComputer = new TreeView()
myComputer.IsEnabled <- true
myComputer.Focusable <- true
myComputer.TabIndex <- 1
// Create the treeTrunk
let treeTrunk = new TreeViewItem()
treeTrunk.Header <- "My Computer"
treeTrunk.FontSize <- 32.0
treeTrunk.FontWeight <- FontWeights.Bold
// Add the elements to the Grid Children collection
let _ = myGrid.Children.Add(textBox1)
let _ = myGrid.Children.Add(myGridSplitter)
// graft the trunk onto the root, put it in
// the scrollViewer box add it to the grid orphanage
let _ = myComputer.Items.Add(treeTrunk)
let _ = myScrollViewer.Content <- myComputer
let _ = myGrid.Children.Add(myScrollViewer)
(* Delete or comment(//) this line and it's counterpart below
mainWindow.Content <- myGrid // Put the Grid in the Window
mainWindow.Show () // Show it to the user
mainWindow.Title <- "the Explorer Imperative" Show the Title
// This is the end of the Window definition
Delete or comment(//) this line and it's counterpart above *)
此函数将光标移动到以您键入的字符开头的下一个项目。该例程使用文本输入,而不是原始输入,因此它是区分大小写的。它会循环,所以下一个匹配项可能在当前位置之前。使用的方法是找到第一个匹配项,然后跳到当前位置并开始搜索下一个匹配项。如果未找到匹配项,则当前项目保留焦点。匹配项在发生时获得焦点。这意味着,“下一个”匹配项会将焦点从第一个匹配项上移走(如果它有焦点)。此例程不是一个eventhandler
,而是一个递归函数。'rec
' 关键字指定递归,否则递归调用会导致编译时错误。换句话说,F# 不是自动递归的。
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"
//printf "\nnext %s\n" (qItem.Tag.ToString())
Keyboard.Focus qItem|>ignore
if whatFound = "nothing" then
whatFound <- "first"
//printf "\nfirst %s\n" (qItem.Tag.ToString())
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 // return that what was found
下一个例程是 treeviewitem 'treeTrunk' 的事件处理程序。它“冒泡”所以适用于 treeTrunk 的所有“子节点”。这是主要的键盘界面。它处理光标键和回车键。该例程的详细描述将在不久的将来提供。
let keyUpDetected(e:KeyEventArgs) =
try
//printf "\nkey is %s\n" (e.Key.ToString())
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 fi:FileInfo = null
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"
当您按住 Control 键并旋转鼠标滚轮时,此事件处理程序会增加或减小文本框的字体大小。
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
此eventhandler
处理 `treeTrunk` 的字体大小和滚动。WinForms 处理垂直和水平滚动,但 WPF 不行。因此,如果您需要水平滚动,则必须自己处理。ScrollViewer
支持它,但不会自动提供。至少,我找不到。我提供了两种水平滚动的方法。第一种是按住 Shift 键并旋转滚轮。第二种方法检测 delta 属性是否等于零。这意味着滚轮已被倾斜。我不知道如何确定倾斜的方向,所以无法左右滚动,但我知道用户想要水平滚动,所以我关闭垂直滚动条并打开水平滚动条。现在用户可以旋转鼠标滚轮,向上变成向右,向下变成向左。您可以根据需要反转此设置。如果您能确定倾斜的方向,您就可以利用倾斜来驱动水平滚动。您可以使用 F# 互操作性,但我现在不这样做。
现在要垂直滚动,用户倾斜滚轮,事件处理程序会关闭水平滚动条并打开垂直滚动条。这样,用户队列和函数队列是相同的。您不能改变一个而不改变另一个,因为它们是相同的。
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
下一个例程处理文本输入,并调用递归例程,将光标移动到以键入字符开头的下一个项目。
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)
下一段是事件处理程序,当其“IsExpanded
”属性设置为“true
”时,它会填充“节点”。此算法构建一个包含此Directory
中文件的 string
,用空格分隔。它被放在子文件夹之前,以便与父文件夹相邻。按键弹起事件处理程序包含将字符串“展开”为“节点列表”的代码,允许选择和处理单个文件。
它还为用户定义的起始目录中包含的任何文件夹设置“IsExpanded
”属性。当 2 个值相等时,“focusItem
”,当窗口显示时要接收焦点的“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
下面的行是一个“lambda”或未命名函数,用于处理“FrameWorkElement.Loaded”事件,该事件在元素(mainWindow
)被布局、渲染并准备好交互时触发。“fun(ction)”将首先尝试根据程序集的强名称从该程序集的“Isolated Storage”读取,以获取用户定义的“Starting Directory”。如果失败,则会向标准错误打印错误消息,并使用编程值。您可以通过编辑定义 `startInDir` 的“let”语句来更改此设置。“IsolatedStorageFile
”、“ifl”在同一程序位置定义,因为流读取器和流写入器都必须使用相同的“object
”。
正如注释所说,如果您(程序)到了这里,您已经读取了起始目录,光标应该定位在上面。
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() // if you got here then you have read
// the starting directory you saved previously
with
|e -> eprintf "\n\n ERROR: %O\n" e
下一行获取系统上的“驱动器”集合。紧随其后的是一个错误处理程序(try..with),用于它所封装的“for ? in ??'”循环。循环定义了“node”,“let item = ...”并用驱动器信息填充它。在此循环中,将路由事件调用添加到项的事件集合中。添加了一个“dummyNode
”,以便“node”可以“Expanded
”。这种组合消除了递归填充树的需要。
请注意,如果起始目录“Contains
”项的“Tag
”,则该项被“expanded
”。然后将该项带入视图并选择。这样,我们“完成”了循环,但没有完成函数。
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
“Loaded
”函数中的最后一个块添加了用于用户交互的eventhandlers
。请注意 MouseRightButton
事件。PreviewMouseRightButtonDown
可以写成带或不带 preview 前缀,但建议使用前缀。但是,另一个按钮 MouseRightButtonUp
事件不能与 PreviewMouseRightButtonDown
事件组合使用 preview 前缀。如果您只想使用上下文菜单,请注释掉“menuReq”行。如果您将这些代码段粘贴到 F# INTERACTIVE 中(不要忘记 ';;'),而没有菜单定义,则需要注释掉这两行。
最后,我们通过将 focusItem
带入视图并选中它来完成“Loaded
”函数。右括号标志着函数的结束。
现在剩下要做的就是将 Grid 放入 Window,显示 Window 及其 Title,并将 Keyboard Focus 设置为 'focusItem
'。
textBox1.PreviewMouseWheel.Add(textBox_MouseWheel)
treeTrunk.PreviewMouseLeftButtonDown.Add(focusReq)
treeTrunk.PreviewKeyUp.Add(keyUpDetected)
treeTrunk.PreviewTextInput.Add(textInput)
//treeTrunk.PreviewMouseRightButtonDown.Add(menuReq)
//treeTrunk.MouseRightButtonUp.Add(contextReq)
myComputer.PreviewMouseWheel.Add(folders_MouseWheel)
treeTrunk.IsEnabled <- true
focusItem.IsExpanded <- true
focusItem.BringIntoView()
focusItem.IsSelected <- true
)
mainWindow.Content <- myGrid // Put the Grid in the Window
mainWindow.Show ()
mainWindow.Title <- "the Explorer Imperative"
focusItem.Focus() // Give the Keyboard Focus to focusItem
// FSI.EXE will ignore the rest of the program but
// FSC.EXE will Compile it.
#if COMPILED
[<STAThread>]
[<EntryPoint>]
let main(_) = (new Application()).Run(mainWindow)
#endif
这个程序中有许多我尚未解释的功能,但我觉得有必要写另一篇文章,并且务必不要试图将所有内容都塞进这一个。
背景
出于各种原因,我经常发现有必要使用一种显示字体大小非常难于阅读的屏幕分辨率。我长期以来一直想编写一个实用程序来增强 Windows Explorer 的缩放功能,类似于 IE。我编写它是为了个人使用,但发现仅仅缩放功能通常不足以激励我执行它。然后我添加了一些我希望在 Windows Explorer 中看到的其他功能,以及在我想做一些我尚未在我的程序中实现的事情时将文件夹发送到 Explorer 的能力。
Using the Code
各个代码段使用 .NET 对象和从 treviewitem 或菜单触发的路由事件。有许多应用程序使用树结构和菜单。其中许多可以使用其中的一些功能。只需将事件处理程序调用添加到想要启动该功能的对象的调用即可。
程序无论是编译运行还是交互式运行,执行结果都相同。唯一的真正区别是,除非您可以使用命令行编译,否则您需要创建一个项目才能编译。拥有一个项目有一些实际优势。但这就像拥有一个平底锅和一个烤盘(您可以同时煎鸡蛋和做煎饼)。有了项目,您只需单击即可添加内容,IDE 会处理它,而没有项目,您必须手动完成。
“探险者指令”是作为一个单一文件的 WPF 程序编写的,仅使用代码,没有 XAML。我决定使用文本输入而不是原始输入,但这需要包含 WPF 事件循环。此代码是 Microsoft 的版权财产。仅在脚本版本的程序中需要。已编译的程序不需要它。如果您创建一个项目并编译该程序,您不应下载 IwshRuntimeLibrary,而应添加对 Windows Script Host Object Model 的 COM 引用,这会创建一个 COM 对象的包装器,使其可以像 .Net 对象一样使用。如果包装器与 COM 对象不匹配,或者 COM 对象不存在,“创建快捷方式”功能将不可用。在这种情况下,删除该菜单项及其事件处理程序。
历史
- 2011 年 7 月 13 日:初始版本