65.9K
CodeProject 正在变化。 阅读更多。
Home

ExpTreeLib 版本 3 - 窗体的类似资源管理器的导航和操作

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (53投票s)

2012年7月16日

CPOL

22分钟阅读

viewsIcon

333105

downloadIcon

14195

一个类库,用于构建包含文件夹导航TreeView和特定于表单的ListView的表单,可以根据您的应用程序进行定制,并像Windows资源管理器一样运行。提供完整文档。

Sample Image - maximum width is 600 pixels

引言

ExpTreeLib是一个类库,提供了Windows资源管理器的大部分功能。它通常用于实现文件/目录工具和文档管理应用程序。

该库的核心类是CShItem,它是基于API的.NET FileSystemInfo类的超集。它还包含一个用户控件ExpTree,它是一个类似资源管理器的Windows Shell文件夹TreeView。ExpTree支持系统上下文菜单,拖放,以及正常的Shell式导航。该库提供对Shell命名空间(包括FileSystem)内容所有更改的动态通知,支持与各种控件进行拖放的类,以及一些本身很有用的实用函数。下载包中包含的示例表单演示了如何使用该库构建一个完整的类似Windows资源管理器的应用程序。演示表单可以轻松修改和/或提取以满足您的应用程序需求。此外,还包含一个演示表单,它在不使用ExpTreeListView的情况下使用该库,并与在DataGridView中显示的文件一起工作。

本文仅概述ExpTreeLib和演示项目。为了使文章长度适中并提供更好的参考来源,我提供了一个帮助文件(ExpTreeLib.chm)作为单独的下载。除了常见的帮助内容外,帮助文件还包含有关如何在您自己的应用程序中使用该库和演示表单的指导,以及该库和演示表单如何工作的详细信息。使用该库的第一个步骤应该是下载并浏览帮助文件!为了更好地理解该库,您可能还会浏览原始CodeProject文章。您还可以下载演示包,在不实际下载源代码或帮助文件的情况下查看该库的运行情况。

仅仅显示文件夹的结构和内容对于一个有用的应用程序来说是不够的。真正的问题是应用程序可以对显示的项做些什么。开箱即用,主要演示表单(frmThreadfrmTemplatefrmThreadCSfrmThread的C#版本))提供了Windows资源管理器的关键功能,但其形式易于修改以适应应用程序的需求。ExpTree和主要演示表单支持以下功能:

  • 当任何进程对显示的文件夹或其内容进行任何更改时,自动更新TreeView或ListView。
  • 将文件/文件夹拖放到ExpTree和/或ListView上。此功能支持正常的Windows Control键修饰符和右键拖动菜单,以指示接收控件将文件/文件夹复制或移动到目标文件夹。拖动源可以是其本身或任何提供至少FileDropCF_HDROPDataFormatted信息的应用程序(包括Windows资源管理器)。此类应用程序的一个有用类别是Windows电子邮件客户端(Outlook、Outlook Express、Thunderbird、Windows Live Mail等)。
  • 从ExpTree或ListView拖动文件/文件夹。支持正常的Windows Control键和右键拖动操作。拖放目标可以是其本身或任何接受至少FileDropCF_HDROPDataFormatted信息的窗口(包括Windows资源管理器)。
  • 右键单击文件/文件夹以显示Windows资源管理器在右键单击时会显示的相同上下文菜单。包括文件/文件夹重命名支持。
  • 编辑ListView的第一列以重命名文件/文件夹——当且仅当第一列是文件/文件夹名称时。
  • 在详细信息视图中显示时,ListView列的列点击排序(带排序字形)。
  • 响应Delete键或上下文菜单删除选定的项。
  • 双击文件以“打开”它。
  • 相对于早期版本,库和演示表单都进行了大幅优化。

包摘要

ExpTreeLib的可下载Zip包

  • 源代码——作为Visual Studio 2008解决方案的完整源代码,包含四个项目,其中包括一个用C#实现frmThread的完整C#项目。VS2008解决方案应该可以无错误地升级到VS2010解决方案。此下载包括:
    • ExpTreeLib - 库。
    • ExpTree_Demo -- 三个演示/示例表单,演示如何使用ExpTreeLib
    • 文档 - 库和示例的文档集,组织成一个网站。这些材料也包含在帮助文件下载中作为.chm文件的一部分。
    • ExpDemoCSharp - 用C#实现的frmThread,以说明C#开发人员如何使用ExpTreeLib,包括ExpTree控件。设置为启动项目以运行此项目。
  • 演示——仅包含可执行文件及其所需的.dll文件,供快速浏览。
  • 帮助文件——一个.chm文件,包含大量额外的演示/示例表单文档以及如何在您自己的应用程序中使用它们。

背景

此库的1.0版本最初于2004年在CodeProject上发布。它在TreeView中提供了Shell命名空间的静态视图。经过多次修订,它发展为仍然是静态但可刷新的视图,并开始支持(大多正确)形式的拖放,最终以2.11版本的形式出现,即原始CodeProject文章现在描述并提供下载的版本。该版本的最后一次更新作为2.12版本发布于2012年。该版本中可用的拖放功能也发布在一篇现已过时的文章中。对于那些喜欢使用2.12版本而不是此处介绍的3.0版本的人来说,该文章可能很有趣。

为了正确实现拖放,有必要大幅重写CShItem类并添加几个支持类。CShItem的重写包括一个机制,该机制提供对应用程序感兴趣的文件夹的任何更改的通知。这允许ExpTree和任何其他控件接收更改通知并更新GUI。这一更改将ExpTreeLib从Shell命名空间的静态视图转换为动态视图。我还对库、ExpTree和一个演示表单进行了其他增强,最值得注意的是系统上下文菜单。这项工作的结果是2.14版本,在论坛中也称为“未发布版本”。

2.14版本已分发给通过原始文章论坛与我联系的许多人。2.11和2.14版本都已证明很受欢迎,并且仍在原始文章的论坛和直接发送给我的电子邮件中讨论。2.14版本的最新更新已于2010年12月26日发送到分发列表。

3.02版本 

一些第三方Shell扩展导致了库中的错误。3.02版本增强了错误处理以解决这些错误。具体来说,当Shell扩展需要第三方.dll并且该.dll加载失败时,以前的版本无法正确处理该错误。这已得到修复。

修正了一个内存泄漏。还修正了似乎由Visual Studio 2012引入的问题。 

感谢Jens Madsen,SystemImageListManager现在可以处理带有叠加的XL图标。

3.01版本 

3.01版本主要是错误修复版本。它还包括一个额外的项目,其中包含一个带有frmThread.vb的检测版本和另外两个表单,对于希望深入研究ExpTreeLib的开发人员来说,这些表单可能略有兴趣。3.01的发行说明包含在源代码下载中包含的文档项目中。

3.00版本

此包的3.00版本对ExpTreeLib库和演示表单都进行了优化。当用户的应用程序在Windows XP系统上运行时,大部分优化是不需要的。但是,当应用程序在更高版本(Vista/Win7)系统上运行并访问大型服务器文件夹时,这些优化非常重要。3.00版本还添加了帮助文件(.chm)、新的和更正的注释,以及以HTML页面形式提供的一系列附加文档,其中包括有关如何修改或从演示表单中提取以在您自己的项目中使用的附加信息。演示项目的文档也包含在可单独下载的帮助文件中。

相对于2.12版本的更改摘要

更多详情可在帮助文件中找到的2.14和3.00版本发行说明中获取。总而言之,3.00版本通过提供以下功能改进了2.12版本:

  • 所有进程对Shell命名空间进行的所有更改的动态更改通知,这些更改与应用程序相关。
  • 大大增强了对拖放到ExpTree以及正确编码的ListView或其他控件的处理。有关如何正确编码ListView和其他控件,请参阅演示表单和文档。
  • 从ExpTree和正确编码的ListView中拖动。
  • ExpTree和正确编码的ListView中的Windows系统上下文菜单。有关如何正确编码ListView系统上下文菜单,请参阅演示表单。
  • 为补偿不同版本Windows(Win7、Vista、XP)之间的差异所需的所有更改。
  • 显著改善了在Windows7和Vista系统上运行并访问远程文件夹的应用程序的GUI响应速度。XP一直足够快。
  • 新的演示表单,其中包含了旨在提高响应速度并更容易修改和/或提取以在应用程序中使用的更改。
  • 最复杂的演示表单的C#版本,以说明如何从C#使用ExpTreeLib
  • ExpTreeLib和演示表单的文档进行了非常显著的改进。这包括提供帮助文件以及代码中XML注释的相应改进。
  • ExpTree使用Win7/Vista主题。

使用ExpTreeLib和演示表单

有多种方法可以将ExpTreeLib集成到您自己的应用程序中。

  • 从头开始,使用文档和示例来使用ExpTreeLib的部分或全部功能。请注意,并非必须使用任何Windows Forms组件。控制台应用程序可能会受益于ExpTreeLib
  • 或者从演示项目的一个表单副本开始,添加您的应用程序的控件和代码。请参阅帮助文件中的“构建应用程序”主题。
  • 或者将ExpTree和/或ListView控件和代码从一个演示表单复制到您自己的表单。请参阅帮助文件中的“从演示表单派生”主题。

构建使用ExpTreeLib的表单

第一篇文章概述了如何在您的应用程序中使用ExpTree控件。另请参阅帮助文件中的“从演示表单派生”主题。总结一下使用frmThread作为模型的方法:

  1. 向您的项目添加对ExpTreeLib DLL的引用。
  2. ExpTree控件添加到Visual Studio工具箱。
  3. 将一个ExpTree控件添加到您的表单。
  4. 在您的表单中添加适当的Imports(或C#中的using)语句。请参阅演示表单以了解用法。
  5. 添加一个事件处理程序来处理ExpTree1.ExpTreeNodeSelected事件,以接收节点选择通知。
        Private Sub AfterNodeSelect(ByVal pathName As String, ByVal CSI As CShItem) _
               Handles ExpTree1.ExpTreeNodeSelected
    
  6. 如果使用SystemImageListManager为ListView提供文件和文件夹图标,请将任一演示表单中#Region "Form Load/VisibleChanged lv1 HandleCreated"区域中的代码添加到您的代码中。
  7. 如果使用ListView以类似于演示表单的方式显示文件/文件夹,并且希望支持从/到该ListView的拖放,请添加任一演示表单中声明和初始化CDragWrapperClvDropWrapper实例的适当声明。您还必须将ListView.Tag设置为在上面显示的AfterNodeSelect处理程序中作为CSI传入的CShItem。这对于正确处理拖放到当前没有项的ListView上是必需的。
  8. 要支持ListView的动态更新
    • 将任一演示表单的Dynamic Update Handler区域中的代码添加到您的代码中。
    • 在您的FormLoad事件中添加一个AddHandler语句。
      AddHandler CShItemUpdate, AddressOf UpdateInvoke
    • 声明一个FormClosing事件处理程序以删除该处理程序。
      Private Sub frmThread_FormClosing(ByVal sender As Object, _
              ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
          RemoveHandler CShItemUpdate, AddressOf UpdateInvoke
      End Sub
    • 为了完整起见,还要在您的表单的Dispose例程中移除该处理程序。
  9. 请参阅演示表单、帮助文件和其他随附文档,以了解支持应用程序使用CShItems所需的其他代码元素。

演示表单

ExpTree_Demo项目中包含多个演示表单。要查看特定表单的运行情况,请通过取消注释Application.Run(someform)语句并确保所有其他语句都已注释掉来修改该项目中的Module modMain。要运行frmThreadCS,请将其项目设置为Visual Studio中的启动项目。这些表单将执行与Windows资源管理器中相同的正常文件操作。但是,它们主要用于提供如何使用ExpTreeCShItem和相关类的示例。

frmTemplate

此表单是一个完全可用的起点,适用于任何需要ExpTree和ListView并留有足够空间用于特定应用程序控件的表单。frmTemplate包含所有不需要使用BackgroundWorker的优化。由于缺少该功能,它更容易理解和使用。对于处理中小型本地文件夹的应用程序,frmTemplate将提供完全可接受的性能。不建议用于处理远程文件夹的应用程序。

frmTemplate示例

  • 使用ExpTreeNodeSelected事件处理程序。
  • 使用CDragWrapperClvDropWrapper支持从ListView拖动和拖放到ListView上。
  • 处理CShItemUpdate事件中的动态更新事件。
  • ListView中的完整上下文菜单。
  • 使用LVColSorterColumnHeader单击时进行排序。
    • 使用MakeLviItem作为自定义ListViewItem构建器,它构建包含对LVColSorter有用的属性的ListViewItem
    • 使用ListViewSortGlyph.SetSortIcon在排序列上设置排序字形。
    • SortLVItems,用于说明如何响应上下文菜单中的刷新命令来刷新ListView
  • ListViewItem编辑(仅限第一个SubItem)。
  • 正确处理Delete键。
  • 展示如何处理ListViewItem上的DoubleClick

frmThread

frmThread是一个完全可用的起点,适用于任何需要ExpTree和ListView并可能访问大型和/或远程文件夹的表单,需要多线程方法来提高GUI响应能力。与frmTemplate一样,它也为特定于应用程序的控件留下了足够的空间。

frmThread包括

  • frmTemplate中包含的所有功能。
  • 使用BackgroundWorker线程将可能需要过长时间收集的信息填充到ListView中。

frmThread - C# 版本

源代码下载中包含一个独立的C#项目,其唯一表单是使用C#实现的frmThread。除了实现语言之外,它与VB.Net版本相同。在完整解决方案中,将“CS”项目设置为启动项目以运行此表单。

frmDragToControl

此表单演示了如何使用ControlDropWrapper处理数据绑定DataGridView的拖入/拖放事件。它是一个相对简陋的表单,仅用于帮助理解ControlDropWrapper的使用。

可用文档

关于ExpTreeLib和演示表单如何工作的权威解释是源代码。对细节的完整解释至少需要与代码行数一样多的文本行,可能更多。

类别的权威参考是帮助文件。帮助文件记录了所有重要的公共类及其方法、事件和属性。Visual Studio的智能感知机制也会显示适当的注释。帮助文件还包含大量补充文档。

补充文档也以项目形式在源代码下载中提供。该项目名为“Documentation”,包含所有补充文档,形式为类似“网站”的HTML页面集合,可以在您喜欢的浏览器中查看。只需打开文件Documentation\Exp_Index.htm并遵循链接即可。本系列中的第一篇文章包含有价值的信息,特别是辅以帮助文件和文档项目中找到的2.14和3.00版本发行说明时。您也可以在本文章的论坛中提问。

工作原理 - 关键概念

为了避免本文过长,以下是一些关键点的摘要。要理解的概念已加粗。帮助文件中有更详细的说明。

CShItem 类

CShItem类是ExpTreeLib的主要类。CShItem的每个实例都封装了关于ShellNamespace项的信息集合。Shell命名空间项包括FileSystem项和非FileSystem项。Windows资源管理器(WinExp)是Shell命名空间的可见表示。CShItem的实例等同于WinExp显示的项(文件或文件夹)之一。CShItem使用Windows API获取并获取有关这些项的信息。ExpTreeLib的每个主要方法或属性都涉及或需要一个CShItem。在演示表单中,每个TreeNodeListViewItem都在其.Tag属性中包含一个CShItem。在frmDragToControl中,DataGridView中的每一行都数据绑定到一个CShItem

在内部,CShItem类维护一个共享树结构,其中包含应用程序感兴趣的CShItem实例。给定一个代表Shell命名空间中文件夹的现有CShItem,名为“CSI”,应用程序可以通过CSI.DirectoriesCSI.Files属性或调用CSI.GetItemsCSI.GetDirectoriesCSI.GetFiles方法获取代表该文件夹内容的CShItem。一旦应用程序通过这些属性或方法之一获取了CShItem,它就被视为应用程序的感兴趣项,直到通过CSI.ClearItems方法明确释放。为了节省内存,应用程序应始终明确释放不需要的文件CShItem。有关详细信息,请参阅2.14版本发行说明。

每个感兴趣项都由一个CShItem精确表示。该CShItem以与相应Shell命名空间项在Shell命名空间中的位置完全相同的方式存储在内部树中。当Shell命名空间发生更改时,CShItem会收到该更改的Windows消息通知。如果更改影响内部树中的一个或多个感兴趣项,则会引发事件以通知应用程序该更改。然后,应用程序可以对GUI进行相应的更改,并执行应用程序需要执行的任何其他操作以反映该更改。有关应用程序如何处理通知事件的示例,请参阅演示表单。

没有公开可访问的CShItem构造函数 (Sub New)CShItems通过 .Directories 或 .Files 属性获取。在极少数情况下,应用程序可能需要获取与文件系统路径对应的CShItem。在这些情况下,请使用CShItem.GetCShItem(Path)方法或其重载之一。这通常仅用于更改ExpTreeRootItem属性,以便将TreeView根植于特定于应用程序的文件夹。

拖放

ExpTree或任何正确配置的ListView拖动以及拖放到其上,行为与从Windows资源管理器项目拖动或拖放到其上完全相同。正确配置的ListView将包含ListViewItem,所有这些ListViewItem都在其.Tag属性中包含一个CShItem。拖放到任何其他类型的控件(例如DataGridView)是可能的,如果该控件与单个文件夹相关联。对于其他特定于应用程序的控件,拖放是通过将控件与ClvDropWrapperControlDropWrapperCtvDropWrapper类的新实例关联来实现的。从特定于应用程序的ListView或TreeView拖动是通过将ListViewTreeViewCDragWrapper类的新实例关联来实现的。

就像Windows资源管理器一样,成功的拖放操作将导致被拖动的文件和/或文件夹被复制或移动到另一个文件夹。右键拖放将显示与Windows资源管理器在相同操作中显示的相同选择菜单。由于任何成功的拖放都会导致底层Shell命名空间的更改,CShItem将收到更改通知并引发一个或多个CShItemUpdate事件,从而通知应用程序更改。

解决响应性问题

撰写关于ExpTreeLib 2.14版本的CodeProject文章已经在我待办事项列表中多年。然而,我一直没有动手。最近,2.11和2.14的用户报告了性能/响应性问题。共同之处在于,在他们开始使用Windows 7客户端访问服务器上的大型文件夹之前,一切都很好。同样的应用程序在客户端运行XP时访问这些相同的文件夹没有问题。我进行了调查。调查及其结果在补充文档中进行了深入介绍,并总结如下。结果导致了ExpTreeLib和演示表单的许多大大小小的优化。注意——使用未优化或优化代码对本地机器上测试文件夹副本进行的测试操作需要15到1200毫秒。对正常大小的本地文件夹的操作几乎是即时的。优化只有在对远程文件夹操作时才会产生显著改进。

我的测试环境包括一台Win7客户端,访问一台缓慢、小型、运行Server 2003的WHS系统上的两个不同文件夹。一个文件夹包含3,000个各种类型的文件。另一个文件夹包含2,000个空子文件夹。

优化分为三个阶段,每个阶段都有其自身的问题和解决方案

  1. 演示表单代码的优化。主要变化是:
    • 消除了GetAttr函数的使用。这包括更改CShItem属性的定义,并添加一个新属性。
    • 简单地使用AddRange而不是多个Add来填充ListView
    这些更改有所改进,但不足以在测试文件夹上创建可接受的用户体验。
  2. 某些CShItem属性(长度以及创建、最后写入、最后访问日期)是使用FileInfo/DirectoryInfo类获取的。我早就意识到这些类并非最佳,尤其是在访问远程文件/文件夹时。我添加了一个类来定义Windows API FindFirstFile/FindNextFile (FFF/FNF) 方法。不幸的是,使用FFF/FNF的好处只有在首次调用FindFirstFile返回的SafeFindHandle一次性检索所有项时才会出现。
    尽管FFF/FNF比FileInfo/DirectoryInfo有了很大的改进,但从GUI线程中使用它仍然导致测试文件夹上的GUI响应性严重延迟。CShItem的设计和使用使得将FFF/FNF在单独线程中整合到CShItem中变得复杂。我转而创建了frmThread,使其从BackgroundWorker中使用FFF/FNF来收集大型文件夹内容所需的信息。
  3. Windows对HasSubFolders属性的处理在XP sp2之后有所改变。早期版本对于远程文件夹总是返回True。当前定义查询远程文件夹以获取准确值。获取HasSubFolders从一个无需检测时间的操作变为一个非常耗费成本的操作。CShItem会例行获取每个创建的文件夹CShItem的该属性值。API的这一变化解释了ExpTreeLib在XP上与在后期系统上(如Vista/Win7)性能差异的大部分原因。
    我更改了CShItem,使其仅在应用程序明确请求时才检索HasSubFolders,并且还将CShItem更改为恢复到XP的使用方式,即始终为远程文件夹返回True。请注意,ExpTree确实明确使用HasSubFolders来确定TreeNode是否可展开。优化的效果是表示远程文件夹的TreeNode将始终可展开,即使该文件夹为空。在尝试展开TreeNode时会纠正这一点。
  4. ExpTree控件进行了更改,以消除冗余和耗时的代码。消除了.NET方法TreeView.TreeViewNodeSorter的使用,该方法在向TreeView添加节点时效率非常低。

优化结果如下。F1是包含3,000个各种类型文件的远程文件夹。F2是包含2,000个子文件夹的远程文件夹。所有时间均以秒为单位

操作 原始 GUI 冻结* 优化后 GUI 冻结* 优化后总时间**
在 ListView 中显示 F1 的内容 67.907 1.014 6.536
直接访问 F2 的子文件夹 78.405 1.435 1.435
在 Exptree 中展开文件夹 F2 节点 25.146 1.060 1.060

   * GUI 对用户操作响应之前的时间。
** 完全完成信息收集所需的时间。

您的体验可能会有所不同!我很乐意听到任何显著差异以及可能导致这些差异的情况。将其与通过Windows资源管理器执行相同操作所需的时间进行比较。注意:引用不可用机器上的远程文件夹总是会遇到网络超时延迟。

致谢

  • Calum McLellan做出了重大贡献,改进了此控件。Calum的文章VB.NET中的资源管理器ComboBox和ListView通过ComboBoxListView类扩展了此库。我的库和演示的ContextMenu部分源自Calum的工作。Calum在他的项目中使用了ExpTreeLib的早期版本。它缺乏所有优化和一些Vista/Win7修改。
  • 我的ExpTreeLib原始版本包含一个用于访问系统图像列表的类。该类的一些重要片段仍然存在于SystemImageListManager中。我的原始版本只是将Steve McMahon的一些系统图像列表类(可在此处找到)从C#翻译为VB.NET。Steve的类具有显著的额外功能,可用于绘制图标并将其附加到其他类型的控件。
  • Steven Roebert 的文章描述了一个 C# 包,该包部分受到了我原始文章的启发。他的工作构成了我修复原始拖放和更改通知的基础。自2006年以来,他的代码未更新或支持,并且从论坛来看,它似乎在 Vista/Win7 和 64 位操作系统上存在严重问题。
  • Eric Woodruff 的Sandcastle HelpFileBuilder 是一个用于构建帮助文件的绝佳工具。 
  • 我也得到了我之前文章论坛的贡献者以及CodeProject和互联网上其他开源代码和讨论的帮助。

历史

  • 2014年1月9日——3.02版本,修复了不兼容的第三方Shell扩展、内存泄漏和一些次要增强功能,包括带有覆盖的XL图标。 
  • 2012年11月14日——3.01版本错误修复及小幅增强。
  • 2012年7月16日——3.00版本。此系列第三篇文章首次提交到CodeProject。
  • 2012年4月20日——2.12版本。更新了下载和第一篇文章——包括所有可在2.11版本中实现的优化修复。还包括所有2.11错误修复的汇总。
  • 2010年12月26日——2.14版本最后一次更新发送到邮件列表。
  • 2006年12月12日——(大约日期)2.14版本(“未发布版本”)发送给请求者。
  • 2006年3月12日——2.11版本。两篇文章均更新以支持VS2005。
  • 2005年9月16日——2.1版本。源代码和演示更新,与第二篇文章中的文件相同。
  • 2005年9月16日——本系列第二篇文章发布。
  • 2005年8月23日——版本2发布——更新了本文系列的第一部分、源代码和演示。添加了拖放功能。
  • 2004年10月11日——第一篇文章和ExpTreeLib的初始版本——1.0版本
© . All rights reserved.