探险家命令 - 第二部分
探险家命令增加了搜索功能
探险家命令
第二部分
本“部分”致力于在与Windows资源管理器结合使用“探险家命令”。它适用于已编译和脚本版本。当调用“探险家命令”时,会显示一个窗口,其中包含一个文本框、一个网格分割器和一个“树”控件,其中包含您计算机的文件系统信息,并且键盘焦点位于您的起始目录。文本框包含一些使用说明。如果您按Shift Tab(反向制表),焦点将移至网格分割器。按下向上或向下箭头将使网格分割器向上或向下移动,箭头指向的方向,改变分割条的垂直位置以及文本框和树控件的高度。任何一侧都可以缩小直到消失。如果文本框消失且键盘焦点位于树控件上,则可以通过反向制表移至分割器,然后按向下箭头。同样,如果树控件消失且焦点位于文本框上,您可以向前制表以将焦点移至分割器。如果树控件消失,但在尝试将其恢复时您按了太多次制表键,按下向上箭头将导致文本框中显示的路径发生更改。这表明树控件具有焦点,但尚未显示出来。现在您可以反向制表到分割条并按住向上箭头直到将其移到您想要的位置。
“展开的目录”、“未展开的目录”和“未展开的文件字符串”的工具提示。请注意,在此工具提示中,“加号”表示目录,“减号”表示文件。
在初始视图中,您的起始目录具有键盘焦点。按下右箭头将展开“未展开”的目录。用“鼠标指针”悬停在“未展开的目录”上时,会显示一个工具提示,其中包含访问和写入时间以及父目录。如果目录已“展开”,工具提示将显示其包含的文件和同级目录。
“未展开的文件字符串”的工具提示,显示文件垂直列表。
在展开的目录上按右箭头键会将焦点移至第一个子节点。“探险家命令”的第一个子节点是一个包含目录中所有文件名的字符串。将鼠标悬停在“字符串”上会显示一个工具提示,其中以垂直方向的列表显示文件名。这与“展开的目录”的工具提示类似,但只有文件名,没有子目录。
“展开的目录”的工具提示和“展开的文件字符串(非工具提示)”并排显示。
展开“虚拟文件夹文件字符串”
如果您按“ENTER”键,“字符串”将在分隔空格处分裂。每个文件的统计信息将放入该文件的工具提示中。这允许直观地比较展开的目录的内容。这是您无法通过Windows资源管理器中的“列表视图”做到的,该列表视图显示当前选定“节点”的内容。这还允许每个文件作为单独的项目进行处理。

显示访问和写入时间、属性和索引的单个文件的工具提示。
当键盘焦点位于文件上时,“ENTER键”的默认操作是“打开”该文件。这使用了“ProcessStart”系统函数,并且打开文件的方式与Windows资源管理器相同。这意味着可执行文件将被执行,文档将被打开以定义它们的执行程序(例如,'.txt'由'记事本'打开,'.bat'由'cmd.exe'打开,'the Explorer Imperative.htm'在'I.E.'中打开(我猜我可以改用“即”而不是“例如”,但这会很令人困惑,不是吗?))。
当文件具有键盘焦点时,按下右箭头将显示一个文本输入框,用于将参数传递给将打开文件的程序。该程序必须接受文件名后跟参数。也就是说(即),举例来说(例如),一个回显其“命令行参数”然后暂停的'.bat'文件将回显(打印)传递给它的参数,然后打印“按任意键继续。。。”,然后窗口将消失。如果“命令行参数”为空,则即使在 '@' 符号前缀的情况下,执行 echo 语句时它仍会打印出“echo is off”。要完成与项目“单击”文本框边缘的交互。要取消交互,“单击”另一个文件或文件夹。
上面提到的'.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
“刷新”文件夹。
当文件夹具有键盘焦点时,按“ENTER”键将刷新文件夹内容,恢复“文件字符串”并重建子目录列表。
键盘导航。
当文件或文件夹具有键盘焦点时,键入可打印字符将“转到”下一个以该字符开头的文件或文件夹。“键盘导航”包括数字以及文件系统名称可以开始的任何特殊字符。它是区分大小写的,因此如果您键入小写“b”,它将找不到大写“B”。它不会展开文件夹或文件字符串并查找其内部。Windows资源管理器做了一些类似的事情,但它不区分大小写。

右键单击鼠标以显示菜单。如果鼠标指针不在菜单上,则显示上下文菜单。
使用菜单系统
菜单系统以两种不同的方式响应“鼠标右键”。按“鼠标右键”将显示弹出菜单。此菜单具有水平方向。鼠标指针下的文件或文件夹将传递给菜单进行处理。当您释放按钮时,如果指针位于菜单上,菜单将“捕获”“鼠标”。存在一些导致鼠标指针不在菜单上的因素。这些因素包括“用户手势”。在释放鼠标按钮之前将鼠标指针移动到另一项将“选择”“新项”并显示更“传统的”上下文菜单。两个菜单都具有相同的功能。它们分别按预期工作,以实现各个菜单选择。
使用文件菜单
文件菜单包含以下选项;新建、打开、执行、CMD.EXE、删除、重命名和退出。两个菜单都以垂直方向显示子菜单,分别与常规菜单和上下文菜单相同。2
文件>新建命令
“文件>新建”命令提供4个选项;文件、文件夹、快捷方式和起始目录。前两个选项提供一个文本输入框用于“新建”文件或文件夹名称。“新建>文件夹”将在当前文件夹中创建一个新文件夹。它不会尝试在文件中创建文件夹。
- “文件>新建>文件”会调用记事本创建一个文本文件,并提供添加文本到文件的机会。这是一种“模板”方法。此技术可用于创建任何类型的正确文件。或者,您可以创建具有所需特征的“文件系统对象”。
- “文件>新建>文件夹”会调用CMD.EXE使用“mkdir”命令创建一个文件夹。
- “创建新快捷方式”将在“所有者”桌面上放置指向选定文件或文件夹的“桌面快捷方式”。本示例使用COM对象的包装器,该包装器需要正确版本的COM对象。如果它们不匹配,您可能会遇到错误。如果发生这种情况,您应该使用您文件系统上存在的COM对象重新构建包装器。不要尝试下载包装器来匹配COM对象。已知存在包含恶意软件的对象版本。
- “设为起始目录”允许用户选择任何他们想要的目录作为“起始”目录。它使用“隔离存储”来持久化任何所需的信息。它不是安全存储,但它是您程序的唯一存储。
文件>打开命令
此菜单命令使用通常打开项目的“EXE”来“打开”选定项目。这意味着文档将由与文档类型关联的文字处理器或编辑器打开,而程序或脚本将被执行。文件夹将由Windows资源管理器“打开”。
文件>执行命令
菜单命令“执行”提供了一个字段,用于将参数传递给选定项目。如果我们选择“temp.bat”并向其传递字符串“Hello!”,它将多次回显参数并“暂停”。我们按“空格键”,批处理程序结束。如果您单击输入框的左边缘或右边缘而不输入参数,结果将取决于程序。在这种情况下,“echo”报告echo已关闭。
文件>CMD.EXE 命令
“CMD.EXE”菜单命令提供2个输入字段,一个在选定项目名称之前,一个在之后。第一个用于“.bat”或“.exe”命令或“CMD.EXE”参数(例如“type”或“notepad”,或“/t:fc”,但不能同时使用)。除非字段以斜杠开头,否则菜单命令会在字段前添加“/K”,在这种情况下,“/K”会添加到字段后。对于CMD,' /K '之后的所有内容都是要执行的命令的一部分。您可以在第一个字段中将“Notepad”传递给“CMD.EXE”来编辑“temp.bat”文件,或者在第二个字段中将字符串传递给“temp.bat”。我建议您使用系统菜单“关闭”CMD,而不是使用“exit”命令行。
注意:这里有一个很酷的“技巧”,可以使使用CMD更加轻松。在第一个字段中使用“/F:ON”参数。这会在CMD中启用“目录”和“文件”补全功能。现在输入“cd c:\
文件>删除命令
“删除”命令调用“Cmd”并带有一个“/c”参数来执行删除。
文件>重命名命令
“重命名”命令调用“Cmd”并带有一个“/c”参数来执行一个带有“新名称”的“移动”。
文件>退出命令
“退出”命令调用常规的“退出”函数。
使用编辑菜单
编辑菜单包含以下选项;“从副本”、“移动”和“粘贴到”。它的工作方式与“剪贴板”复制和粘贴不同。
编辑>从副本命令
“编辑>从副本”命令将当前选定的项目复制到“粘贴到”目录。它要求先指定“粘贴到”目录。一旦完成,任何数量的文件都可以复制到该目标,并且在复制操作之间可以执行任何其他操作。
编辑>移动命令
“编辑>移动”命令将当前选定的项目移动到“粘贴到”目录。它也要求先指定“粘贴到”目录。一旦完成,任何数量的文件都可以移动到该目标,并且在移动操作之间可以执行任何其他操作。这类似于“复制”和“删除”,因为对象将从其原始位置移除。
编辑>粘贴到命令
“编辑>粘贴到”命令将当前选定的目录存储在静态内存位置,供“从副本”和“移动”命令用作“粘贴到”目录。这必须是“编辑”命令中第一个执行的命令。它在列表的最后是因为它的选择频率最低。人体工程学规则,兄弟!
使用视图菜单
视图菜单包含以下选项;“增大字体大小”和“减小字体大小”。此菜单项控制菜单的大小。这些是Lambda函数的示例。我试图让这个程序纯粹是命令式的,但我决定菜单比我需要的纯粹性更需要缩放。(不是说我反对函数式编程,事实上,我热爱它!但这是“探险家命令”。(缩放缩放))。此外,“mainWindow.Loaded.Add”函数是一个“事件处理程序”,当“mainWindow”被WPF加载并准备好显示时,它将一个LAMBDA函数“添加到”mainWindow事件处理程序集。
视图>增大字体大小命令
“视图>增大字体大小”命令将Lambda(匿名)函数添加到菜单项事件处理程序集,而不是添加事件处理程序调用。这两种弹出菜单和上下文菜单都这样处理。这消除了定义单独的命名函数以及对该函数的调用的需要。这更简单。
视图>减小字体大小命令
“视图>减小字体大小”命令也是一对Lambda函数,一个用于弹出菜单,一个用于上下文菜单。
左键单击“搜索”菜单项。搜索子菜单出现。
使用搜索菜单
搜索菜单包含以下四项选择;“查找文件”、“从此处开始”、“查找字符串”和“查找下一个(F3)”。它使用预定义的目录子集进行“快速”搜索,但允许您搜索整个驱动器。您不能搜索整个计算机。
搜索>查找文件命令
“搜索>查找文件”命令获取当前选定的文件名。如果当前项目是目录,它将显示前一个文件名进行编辑。通配符是“*”和“&”。它们按正常方式工作。要开始“查找文件”,请单击文本输入框的边缘。
搜索>从此处开始命令
“搜索>从此处开始”命令获取当前选定的目录。如果当前项目是文件,它将获取文件的父目录。要启动“搜索>从此处开始”,请单击菜单选择的任何位置。
单击“文本框”中的文本,输入文件名或“???”来搜索字符串。
搜索>查找字符串命令
“搜索>查找字符串”命令保留用作文件搜索函数参数的文件名,但如果您尚未搜索文件,它将获取当前选定的文件名。您可以在“搜索字符串”或“搜索文件”的文件名输入框中输入通配符字符,但只能在文件名中,而不能在“字符串”中。右键单击不同的文件名不会更改“字符串搜索”的搜索参数,但会更改“文件搜索”的参数。如果第一个搜索没有选定目录,“搜索”函数有一个默认的“快速搜索”目录列表将被搜索。由于即使您只右键单击项目的文件名,文件名输入框也会获取文件名,然后转到您要搜索的目录并选择它进行搜索。然后,您可以选择除“搜索下一个”之外的任何搜索选项。如果您想将“字符串搜索”限制为单个文件,请选择包含它的目录。此技术会被包含相同名称文件的子文件夹所阻止。“字符串搜索”将在选定或默认目录中搜索“字符串”,如果找到,它将复制到分割条上方的文本框中,并且包含“字符串”的行将发送到stdout,并附带前后行的上下文。使用最简单的方法来避免多次打印同一行。
一些控制台输出显示找到的“file.fsx”文件的完整路径,然后是它“搜索”字符串“Wide”的文件。
搜索>查找字符串控制台输出
“搜索>查找字符串”命令列出在“搜索>查找文件”、“搜索>查找字符串”和“搜索>从此处开始”命令的文件搜索函数中找到的文件,因为它们被排入队列以供后续搜索相同文件名。然后它尝试与它在TreeView中的当前位置同步。如果您位于包含“file.fsx”和“file.fs”的文件夹中,则对文件名的初始搜索将始终找到下一个,但之后选择另一个文件名将在同一目录中找到它。然而,选择一个文件然后在文本输入框中更改它将不会在同一个文件夹中找到它。
“搜索>查找字符串”函数基于“字符串”参数的变化而触发,因此,只要字符串参数在每次搜索时都发生变化并且在文件中找到,它就会在当前文件夹中进行搜索。如果文件名和搜索字符串都相同,则搜索将继续到队列中的下一个条目。如果字符串在当前文件中未找到,搜索将继续到队列中的下一个条目。要将搜索锁定在单个文件上,请通过右键单击它并选择“搜索>从此处开始”菜单选项来选择文件。这允许您“重新搜索”同一个文件,即使在文件中搜索失败。要打破锁定,请更改文件名或在选择不同目录后使用“搜索>从此处开始”菜单选项。
从成功搜索字符串“let _ = '”的控制台输出,显示了其使用上下文。
“搜索>查找字符串”函数打印出它搜索的队列条目以及“字符串未找到”消息或“字符串已找到”消息,后跟包含该字符串的行,并以其前后行作为上下文。如果一行已被打印,它不会被再次打印。行号会显示在行之前。
搜索>查找字符串屏幕输出
成功搜索字符串的屏幕输出。
“搜索>查找字符串”命令在标题栏和分割条上方的文本框中显示“未找到字符串”消息或“找到字符串”消息。然后,它将文件复制到该文本框中,并在输出包含搜索字符串的行之后。此文件的复制仅用于浏览。与TreeView的任何交互都将导致其被替换。由于很难击中分割条,最好通过反向制表到分割条并使用光标键将其向下移动,而不是用鼠标移动。我认为控制台输出更有用。
搜索>查找下一个(F3)命令
“搜索>查找下一个(F3)”命令将当前条目入队并出队下一个条目。这会将队列变成一个循环或轮子(“目录轮子”或“滚轮目录”)。F3是指F3键,它执行相同的功能并“滚动”到下一个“目录”。如果您在“查找”之前尝试“查找下一个”,似乎什么都没发生。实际上,它已经将当前项目的完整路径名入队。虽然您一无所获,但搜索功能是令人满意的。恭喜!您找到了幸福!现在,当您“找到”某物时,它将与您之前的“查找”不同。因此,它会在同一个目录中找到它。
“搜索功能”的代码说明
本示例包含与原始文章基本相同的元素。即,一个可以由“FSI.EXE”解释的fsx脚本 - F#交互式或“另存为”fs文件并将其添加到F#项目中,并为“#IF”指令中的所有“#r”行添加引用并进行编译。您可以选择任何一种。我没有包含项目,因为对于资源有限的人来说,创建项目比试图让已编译的程序在交互模式下工作的FSI交互式要容易得多。我还包含了WPF事件循环源代码,这是Microsoft的版权财产,仅脚本版本需要。Windows脚本主机对象模型,这是“设为起始目录”菜单选项所必需的,也已包含在内。有两个错误修复(请参阅下面的更改日志),并且添加了“搜索功能”。本文的其余部分将讨论搜索的代码。
应用程序,“探险家命令 - 第二部分”
包含搜索功能的应用程序是一个带有缩放和现在具有搜索功能的“文件系统查看器”。该应用程序是一个纯代码的WPF窗口。没有XAML。窗口包含一个具有3行1列的Grid。第一行包含一个文本框。第二行仅包含一个Grid Splitter。用于调整其他两行的身高。第三行包含一个位于ScrollViewer中的TreeView。脚本版本将在有或没有WPF事件循环的情况下运行。WPF事件循环为鼠标和键盘事件提供了基本的用户交互响应集。如果您的应用程序使用这些响应,则需要WPF事件循环。此应用程序的所有菜单交互都需要它。由于我们正在讨论菜单,除非我们编译'file.fs'版本,否则我们需要它。
我们将按照讨论顺序(而不是程序源代码中的出现顺序)呈现代码。此处是为了简单起见。在程序源代码中,菜单位于其调用的事件处理程序之后。事件处理程序位于其调用的任何函数之后。这些原因将在本文中不作解释,但我可能会在将来进行讨论。总之,它必须先于调用。因此,我们将首先查看弹出菜单函数。
菜单系统 - 弹出菜单和上下文菜单
生成和显示菜单
以下事件处理程序在mainWindow.Loaded.Add Lambda函数中编码。“menuReq”函数(不是lambda,也不是匿名的)在按下鼠标右键时调用。它构建弹出菜单。如果释放鼠标右键时鼠标指针不在弹出菜单上,则由contextReq函数构建上下文菜单。此示例显示如何编码一个弹出菜单,该菜单顺便包含一个菜单。contextReq函数是上下文菜单的示例,但我们不会过多讨论它。
treeTrunk.PreviewMouseRightButtonDown.Add(menuReq)
treeTrunk.MouseRightButtonUp.Add(contextReq)
在下一节中,将解释menuReq函数,“弹出菜单”。它的大部分功能与“上下文菜单”相同。从视觉上看,区别在于“上下文菜单”是垂直方向的,而“弹出菜单”是水平方向的。上下文菜单通常被称为快速菜单或快捷菜单,所以如果你不想称之为弹出菜单,你也可以称“弹出菜单”为快捷快速菜单。但是,然后你可以将鼠标右键Up事件处理程序改为调用弹出菜单,然后称之为pop-pop菜单。或者将两者都改为调用上下文菜单,并称之为pop-con菜单(原谅我拙劣的幽默)。总之,您可以选择菜单样式,甚至不需要样式表。当然,您可以在两者中放置不同的功能,甚至可以添加常规菜单。
下面的第一行代码在F#语言中表示“请允许‘menuReq’作为委托函数来处理接受名为‘e’的MouseButtonEventArgs参数的MouseButtonEvent,并让以下行定义函数”。“e:”表示“e是...的类型”,这里是“MouseButtonEventArgs”。“try”和“with”一起处理异常。“try”也可以有一个“finally”子句。接下来,我们通过调用其“new”函数来定义一个Popup,并给它命名为“menuPopup”。然后告诉popup显示在哪里。“<-”是F#的“将左参数设置为右参数”。“:?>”三合一运算将参数e的源向下转换到TreeViewItem。它表示“将menuPopup的PlacementTarget设置为刚刚单击的TreeViewItem”。给它一个垂直偏移量“-40.0”和一个水平偏移量“20”。这在大多数情况下会将其放置在TreeViewItem的上方。现在定义thisItem和一个名为popupMenu的新Menu。设置popupMenu的初始字体大小。我们定义一个菜单项并称之为“pmF”,它是“Popup”、“Menu”和“File”的第一个字母 - 这是此菜单项的“Header”。我们将使用此约定来生成我们需要的菜单项的名称。第一个子项“File”菜单的Header是“New”,所以我们将其命名为“pmFn”。现在我们想将'pmFn'添加到'pmF',但这会导致类型不匹配的警告,因为F#期望的是MenuItem类型,但这里是Unit类型,在c#中相当于Void。为了防止警告,我们使用“let绑定”将Add函数的结果绑定到“通配符”参数“_”。F#不会抱怨如果您将'_'重新定义为与其它定义不同类型的类型。现在我们将添加一个具有更有意义名称的菜单项来创建一个“新文件”。所以我们将“File”添加到已生成的prefix中。resulting name is 'pmFnFile',我们将其添加到'pmFn'菜单项。下一个菜单项将包含一个用于新文件名的文本框,并将添加命令函数的事件处理程序调用。由于我们不再向此菜单项添加更多子项,因此不用担心名称变得过大且难以处理,所以我们称之为“pmFnFileName”。现在我们定义文本框并给它一个最小宽度,以便它足够宽可以单击而不会单击菜单项边框,这将导致Click Event被触发。现在我们将菜单项的Header设置为文本框,将TreeViewItem的完整路径名添加到项的Tag中,并将Click Event Handler添加到项中,最后,将“pmFnFileName”菜单项添加到“pmFnFile”菜单项。这是展示向菜单项添加文本输入功能所需步骤的最简单示例。当需要多个条目时,它可能会变得更加混乱。现在让我们跳到“Search>For String Menu Command”。
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)
菜单项的文本输入
“搜索>查找字符串”菜单项本身就是一段相当复杂的代码,但它在菜单项中显示了多个文本框,因此它是下一个合乎逻辑的解释对象。它还包含一些决策逻辑。我们从“pmSfs”菜单项的定义开始。我们需要添加多个文本框,所以我们定义了一个StackPanel。我们需要第一个文本框用于“For String”。我们将其设置为只读,因为用户不应该更改它。第二个文本框用于文件名参数。此参数用于存储GetFiles函数中使用的字符串,并且可以包含通配符字符“*”和“?”。如果fSfname在进入菜单时为空,则文本框的Text将设置为fname,如果已进行文件搜索,则设置为fSfname,否则,它将设置为TreeViewItem的文件名,除非它是目录,在这种情况下,它设置为“???”。如果fSfname不为空,则文本框的Text设置为fSfname。现在我们定义要“搜索”的字符串的文本框参数,fndStr。如果fndStr为空,我们将Text设置为“???”;否则,我们将它设置为fndStr的值。接下来,我们将三个文本框添加到StackPanel中,并将StackPanel放入菜单项的Header中。现在我们将TreeViewItem的Tag放入菜单项的Tag中,并将findStrReq添加到Click Event中。最后,我们将pmSfs菜单项添加到pmS菜单项中。
我希望我已经用一种可以被他人理解,或者至少在一周内我可以理解的方式来解释了这一点。现在让我们回到findStrReq函数,看看代码(它在下面,但在程序中,它在这一点之上)。
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
在我们开始下一节之前,让我们看看上面的最后十行。这里我们定义了所有菜单项,然后我们将popupMenu作为子元素添加到Popup中。定义并将其放入popupMenu后,我们打开Popup并告诉它如果失去鼠标焦点时不保持打开状态。这后面跟着前面“try”的“with”子句。这将捕获任何错误并将消息发送到stderr,即控制台。
“查找字符串”函数
findStrReq函数与menuReq函数以相同的方式定义。前两行是定义它的“let绑定”和一个包含定义主体的“try-with”表达式。下一行定义传递给函数的参数并将其强制转换为菜单项。然后打印两个“换行符”。然后将'sp'绑定到itemPassed的Header,并将其强制转换为StackPanel。提取StackPanel的Children,将'fp'绑定到第二个并将其强制转换为TextBox。它将'cp'绑定到第三个子项并将其强制转换为TextBox。第一个子项被忽略。现在我们必须定义parseDirInfo函数的参数,因为它在不共享范围的不同程序结构中使用。如果用户输入了一个文件名字符串要搜索,我们就设置fSfname为该字符串,否则,我们就设置它为fname的值,除非fname为空,在这种情况下,我们就设置为“Happiness”,它找不到,除非你自己创造幸福。如果留空,它会导致IO错误!现在,如果队列为空,或者fSfname不等于fTfname,我们就设置fname为fSfname的值,即使它是“happiness”,然后清空队列,以防文件名搜索字符串发生变化,设置窗口标题并输出搜索消息并开始搜索。
如果我们位于一个目录中,我们就从该目录构建队列,否则,我们就定义一个文件路径数组并为数组中的每个字符串调用parseDirInfo。如果我们前面的“if”或“for”中定义了rootDir,它们将在不同的作用域中,并且需要两个不同的“let”绑定。现在我们检查队列是否包含单击的TreeViewItem的文件名。如果是,我们就“窥探”队列中的第一个可以取出的条目。如果是我们单击的那个,我们就退出while循环,否则,我们就取出它并将其放在队列的另一端,然后循环下一个。我们使用第一个可用的字符串退出while循环,该字符串是单击的项目的文件名,它将被出队。如果队列不包含文件名,我们就从队列的开头开始,即找到的第一个文件。如果我们没有构建队列,那么下一个要出队的候选者是上一次文件搜索找到的条目之后的那个。
当fSfname和fTfname不相等时,“<>” - F#不等于运算符,我们使它们相等,并且不“出队”下一个条目,因为我们刚刚构建了队列,并且由于文件名已更改,我们希望在当前目录中找到新的搜索参数。如果fSfname和fTfname相等,那么我们没有构建队列,但是“查找字符串”参数可能已更改。如果是这样,或者这是第一个字符串搜索,我们希望搜索当前文件,因此不从队列中取出一个条目。如果文本框中的文本与fndStr相同,那么我们没有重建队列,也没有搜索不同的字符串,所以我们出队下一个条目,然后将其放在队列的另一端。
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)
继续“findStrReq”函数,我们定义fQcount来确定何时搜索了整个队列,即fQcount大于或等于fQue.Count,此时我们停止搜索。如果“查找字符串”函数的文本框包含除三个问号以外的任何内容,我们就将其放入fndStr。我们更改窗口标题并输出搜索字符串消息。我们定义一个字符串来指示成功或失败。当该字符串不指示成功或失败时,我们循环遍历队列,读取队列中的每个文件(如果存在),以查看它是否包含搜索字符串fndStr。如果它包含fndStr,我们就指示成功并执行控制台和屏幕输出。在文件中找到字符串后,我们调用递归例程findit来查找和展开目录,展开所有必要的内容,包括目标目录的文件字符串,以找到文件并将其显示出来。如果未找到fndStr,则队列条目被出队并重新入队。然后循环计数器递增,如果计数等于或大于fQue.Count且尚未找到字符串,则表示失败,窗口标题和文本框文本将更改以通知用户失败,然后while循环完成。try-with表达式以错误消息表达式完成。
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
支持的演员,“parseDirInfo”和“findIt”函数
这两个函数是递归函数,意味着它们调用自身。而C#函数(例如)可以自动调用自身,F#函数只有在“let绑定”中包含“rec”关键字时才能调用自身(例如,“let rec parseDirInf (root:System.IO.DirectoryInfo) =”。let绑定允许函数调用自身。括号中的部分告诉编译器参数的名称和类型。冒号是类型说明符。如果您想要官方的技术解释,我的建议是RTFM(阅读F#手册)。这里我们可以看到'root'是'System.IO.DirectoryInfo',它包含由调用函数传递给它的目录信息,该函数可能就是它本身。我们现在做的第一件事是定义一个文件信息和另一个目录信息,它们都是可变的,也就是说,它们不是默认的不可变、固定值对象,如果我们没有指定“mutable”的话,它们本来可以不是这样。下一步是“try-with”表达式来处理在获取“root”中的匹配fname的“files”时可能发生的任何异常。这在很大程度上是可能发生错误的地方。如果发生错误,files可能为null,在这种情况下,我们没有什么可以入队的,所以我们从“root”获取子目录并循环遍历它们,在“try-with”表达式中递归调用“parseDirInf”。在“try-with”表达式中递归调用可能存在陷阱。F#通过使用“尾递归”来优化作为函数最后操作的递归调用,在这种情况下,堆栈帧会被删除,因此大量递归不会导致堆栈溢出。由于不能应用“尾递归”,因此在非常大的目录上可能在这里发生堆栈溢出。如果这很麻烦,则消除最后的“try-with”,该函数将是尾递归的。返回类型是“unit”,相当于C#中的“void”。这里可行,因为队列“fQue”是在最低级别定义的,可以在程序的任何地方访问。如果任何文件匹配fname,它们现在就排队在一个“先进先出”的结构中,供程序使用。
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
递归函数“findIt”被所有找到文件的函数调用,包括“findIt”本身。与“parseDirInfo”一样,定义是带有“try with”表达式的“let rec”绑定,其中包含异常处理程序中的函数代码。在函数代码中,我们定义了两个类型为TreeViewItem的可变对象。第一个通过调用TreeViewItem的“new”构造函数定义了qItem。第二个定义了类型为TreeViewItem的pItem并将其设置为itemPassed。现在一个“for”循环获取pItem的i个元素,从0到元素个数减一,并将它们放入qItem。这个“for”循环包含一个“if”表达式和对findit的递归调用,将qItem传递给findIt。如果这个元素的Tag包含在startInDir中,则“if”表达式会问。如果不包含,它会下降到递归调用并重复循环。如果答案是“是”,那么它会问它是否等于startInDir。如果“是”,则选择它并将其显示出来,通过使用Mouse.Capture(focusItem)将鼠标焦点从菜单中移开。这是一个函数调用,即使程序代码中没有使用左括号和右括号,它们也可以使用,但在这里是可选的。在这里“捕获鼠标”是必要的,因为菜单拥有它,否则它不会放手。我们需要重新构建菜单,以确保它会聚焦在新选择上,而不是我们用来初始化搜索的那个。但是现在我们必须释放鼠标,所以我们“捕获鼠标”,传递一个null值。由于这是“if”表达式中的最后一个表达式,因此它必须与匹配的“else”表达式中的最后一个表达式具有相同的类型说明符,因此,我们将其通过“pipe”运算符“|>”管道化为“ignore”,在“else”表达式的末尾也需要相同的管道到ignore。此操作的结果是“if-else”表达式的类型为“unit”。第一个“mouse capture”of focusItem使用“let绑定”的通配符值“_”来产生一个unit类型。让我们继续“else”表达式。这里我们知道它要么是一个目录,要么是我们还没有遇到的错误,所以我们展开虚拟文件字符串文件夹和实际目录文件夹。为此,我们只需清除它并重新构建所有内容,将每个文件视为一个单独的项目,就像我们对待目录一样。工具提示包含不同的信息,但否则目录项比文件项多的是一个虚拟节点。这意味着文件前面不会出现加号或减号。此代码与程序中的另一部分代码相似,但这里我们清除节点并重新构建它,而在另一部分代码中,文件字符串项被删除,文件项被插入节点。当然,您可以使用任何一种方法来构建您的TreeView节点。这里我们使用文件系统作为数据的来源,但也可以使用任何类型的数据库。在数据库应用程序中,您可能会使用插入新项,而不是重新构建整个节点(WPF中的TreeViewItem)。
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
这里讨论的功能乍一看似乎是在重新发明一个已经被发明过很多次的轮子,但认识到我只听说过或使用过很少一部分可能与此功能相似的工具,我还没有看到一个比这更好的产品。您可能更喜欢其他UI,或者您可能需要输出,或者您可能只是喜欢在perl脚本中使用正则表达式,或者您可能只是不认为这是一个严肃的工具而不愿意费心。这没关系,但如果您尝试一下,可能会感到惊讶。把它想象成您的沼泽车上的大号气球轮胎。您不希望它们装在您的宝马或凯迪拉克上,但您仍然可以玩得开心。代码的真正目的是满足对知识的永不满足的渴望,并遵守探索所有可能性的命令。
背景
本文讨论了“探险家命令”中的“搜索”功能。这些功能在菜单和显示方面都使用了WPF,以及.NET语言F#。这意味着代码可以很容易地翻译成C#。我毫不怀疑它们可以,而且它们无疑会和以前一样快。XAML可能增加了您需要的功能。但我经常发现自己想知道如何让一些C#代码工作,甚至编译,因为有些东西就是无法在我的有限的内部电路中计算出来,但到目前为止,F#给了我需要的线索来让它工作。也许只是我的大脑组织方式有利于F#,或者我只是变得更聪明了……不,我仍然是那个愚蠢的老笨蛋BAL Coder。所以一定是关于F#!(我只是想在同一篇文章中使用“she bang”和“perl”)。
应用程序,“探险家命令”,是一个带有“缩放”的文件系统查看器。它的原始目的是“探索”文件系统,而不是管理它。它确实有一些文件管理功能,但从未打算成为Windows资源管理器的可行替代品。所有示例函数都不是最好的、最快的、最有效的、最易于理解的或最安全的。它们都展示了“一种”以编程方式完成某事的方法。我主张简洁。我尽量不“花哨”,因为当我“花哨”时,我总是“滑入”了一个问题。所以我尽量避免不必要的复杂性。如果您对F#有任何程度的了解,无论是“初学者”还是“高级”,代码都应该是可以理解的。我希望它足够简单,即使您不熟悉F#也能理解。但您还需要最新版本的F#交互式和编译器,它是Microsoft免费提供的,可以在Windows或Mono上运行。有关所有细节,包括其他所需的库,您应该“Bing”搜索“F#”或“F Sharp”,然后选择一个Microsoft网站链接,该链接最明显地是关于F#的。
使用代码
单个代码段使用.Net对象和路由事件,这些事件从TreeViewItem或菜单触发。有许多应用程序使用树结构和菜单。其中许多可以使用这些功能。只需将事件处理程序调用添加到想要启动该功能的对象即可。
程序无论是编译还是交互式运行都执行相同的结果。唯一的真正区别是您必须创建一个项目来编译它,除非您可以进行命令行编译。拥有项目有一些真正的优势,但就像拥有平底锅和煎饼烤盘一样(您可以同时煎鸡蛋和烤煎饼)。有了项目,您只需单击即可添加内容,IDE会处理它,没有项目您就必须手动完成。
“探险家命令”是用单个文件、WPF程序编写的,仅使用代码,没有Xaml。对于键盘导航,我决定使用文本输入而不是原始输入,但这需要包含WPF事件循环。WPF事件循环的代码是Microsoft的版权财产。它仅用于脚本版本的程序。编译后的程序不需要它。如果您创建一个项目并编译程序,则无需下载WPF事件循环的源代码或IwshRuntimeLibrary,而是应该添加对Windows脚本主机对象模型的COM引用。这会创建一个COM对象的包装器,允许它像.Net对象一样使用。如果包装器与COM对象不匹配或COM对象不存在,“创建快捷方式”功能将不可用。在这种情况下,删除菜单项及其事件处理程序。我之前说过,F#在Windows和Mono上运行。但这并不意味着此程序将在Mono上运行。我不知道,也不能保证,任何特定代码段在Mono上的运行方式或是否与在Windows上相同。
我正在使用文本编辑器和Microsoft F# Interactive编写此程序(和文章)。我定期编译程序以确保它能够编译并产生与脚本相同的结果。我目前在我的XP上没有安装Mono环境。以我有限的资源,我无法检查所有可能的情况。我希望它对您有用,也许您可以在自己的程序中使用此代码中的某些内容。
历史
本文致力于讨论添加到“探险家命令”的新功能,特别是“搜索”功能,重点关注“查找字符串”功能以供说明。弹出菜单和上下文菜单被讨论,因为它们是调用搜索方法所必需的。我认为所有菜单代码,包括用于增加和减少菜单字体大小的函数,都属于新代码,即使它们已在原始程序中,因为当时没有讨论。尽管菜单本身像现在一样工作,但代码没有出现在文章中。没关系,因为我也没提供任何代码解释。因此,除非是错误修复或功能上的明显更改,否则我不会列出任何代码更改。
以下是您应该注意的更改;
- “文件>新建>文件”函数中的代码存在一个错误,该错误用于从菜单项中提取参数。此错误在此版本中已更正。
- “文件>新建>文件夹”函数中的代码存在一个错误,该错误用于从菜单项中提取参数。此错误在此版本中已更正。
- “视图>增大字体大小”函数已更改为增大菜单的大小,而不是增大TreeView的大小。
- “视图>减小字体大小”函数已更改为减小菜单的大小,而不是减小TreeView的大小。
- “搜索”菜单已启用,并添加了“查找文件”、“从特定目录开始搜索”、“查找字符串”并打印其使用上下文以及使用菜单或F3键“查找下一个文件”的功能。
现在就到这里!愿F#之力与你同在!