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

我的个人指挥官变体

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.66/5 (13投票s)

2012 年 12 月 13 日

CPOL

14分钟阅读

viewsIcon

46065

downloadIcon

1261

一个 C#/WPF 应用程序,用于在网格上显示文件夹并对它们执行组合功能。

其他要求

  • 在 C# 4.0 环境下,可能需要安装 Expression Blend SDK 才能运行代码。
  • System.Management.Automation - 通常可以在安装 PowerShell SDK 后在 C:\Program Files\Reference Assemblies\Microsoft\WindowsPowerShell\v1.0\System.Management.Automation.dll 中找到。如果您不希望包含该功能,请从解决方案中删除 Lobster.PowerShell 项目。应用程序仍然可以运行,但 PowerShell 功能将不可用。

 

目录

简介 

MPCV (My Personal Commander Variant) 旨在成为 Windows Explorer 的一个高度可定制且可扩展的文件系统导航器替代方案。在此,可定制意味着用户可以直接在用户界面中更改应用程序的有用行为和外观属性。可扩展意味着开发人员可以轻松添加对新对象类型和属性的支持(例如,通过继承一小组基类,MPCV 可以轻松扩展以显示进程/线程列表)。

然而,我不想编写某个其他系统的克隆或复制品。事实上,我对 Norton Commander 只有一些非常模糊的回忆,并且几乎没有看过 Total Commander 等其他软件(参见 http://en.wikipedia.org/wiki/Norton_Commander)。如果我看了,我可能就不会自己写了。

那么,为什么:对我而言;学习 WPF 和一些其他 .NET 技术(到目前为止效果很好),跟上设计模式的最新进展,生成一些真正属于我自己的参考代码,并最终创造出有用的东西。

对您而言;好吧,您付钱了吗?如果付了,我想见见卖给您的人。现在,老实说,可能有一些想法或代码部分可能有用。如果您在其他地方发现可以使用的东西,请留下便条。

这是它的样子

Main Window

请不要期望一篇关于 WPF 的文章。稍后可能会有一些抱怨,但我没有什么新东西要补充,所以我将坚持解释应用程序的工作原理以及可以从中重复使用哪些内容。

此时,应用程序已经投入了大量时间,我想分享我的成果。

该应用程序分为以下程序集:

  • Lobster.MPCV:主应用程序和 MVVM 意义上的视图。它包含解决方案文件 mpcv.sln
  • Lobster.MPCV.VM:视图模型。
  • Lobster.FolderBrowser:模型被设计为易于扩展和重用的组件。
  • Lobster.MVVM:我自己的实验性 MVVM 库。我不认为它适合重用。
  • Lobster.Components:一些不直接链接到 MVVM 或应用程序本身的基类。

该应用程序无需安装。它会保留一个 app_state.xml 文件,用于在您的漫游应用程序数据文件夹中序列化配置(例如,在 Windows 7 上为 C:\Users\YourUserName\AppData\Roaming\MPCV)。

MPCV 用户指南  

用户文档可在 Lobster.MPCV 项目的 "help" 子文件夹中找到。它已包含在构建过程中,在构建并启动应用程序后,可以通过菜单栏 "Help" > "User Help" 访问帮助。 

简而言之,该应用程序是一个带有附加功能的多文件浏览器。它支持多个选项卡,每个选项卡最多可包含您显示器能够显示的任意数量的文件浏览器。亮点包括:

  • 文件夹和设置在会话之间持久存在。
  • 文件夹浏览器可以被赋予标题名称和背景颜色,以及一个固定的父文件夹,以便只能导航特定的目录树。 
  • 文件夹浏览器可以链接在一起,以便它们可以根据给定的父文件夹并行导航。
  • 每个选项卡上都有一个可配置的工具栏,可以启动应用程序。
  • 通过将文件拖放到文件夹浏览器上,文件夹会被导航到该文件。通过这种方式,一个文件夹浏览器的位置可以复制到另一个文件夹浏览器。不能将文件复制或移动到另一个应用程序。 
  • 完全无法意外更改浏览的文件和文件夹 - 除非您以某种方式配置了工具栏。 

关于项目当前状态的更多信息

  • 每个浏览器中的文件单选。
  • 双击文件会在其标准应用程序中打开它。 
  • 一个可配置且独立的上下文菜单,用于启动 shell 命令。
     

除了它做什么或不做什么之外,这里有一个关于此应用程序可以用于什么的设想:

Diff 应用程序是软件版本控制系统 (VCS) 的关键工具之一。至少在我使用的那些系统中,它们与 VCS 集成得很好,并且使用起来并不麻烦。然而,当比较文件系统上的文件时,在帮助用户选择文件的过程中所做的努力并不大。用户常常不得不点击半个文件系统才能找到包含所需文件的文件夹(好吧,如果他足够聪明,他可能会将路径从 Windows Explorer 粘贴到某个文件对话框),并且几乎没有帮助提供给想要进行多次连续比较的人。 

关注点

通过 PowerShell 进行自定义

我添加了对 Powershell 的支持,因此 MPCV 可以通过外部脚本进行自定义。目前,只能更改文件夹浏览器项的背景颜色,但这已经打开了一系列选项。机制非常简单。每当文件夹浏览器的内容发生变化时,就会调用一个 powershell 脚本,并传递包含的项目列表。在脚本中,可以分析每个项目并设置新的背景颜色。提供给脚本的列表由 Lobster.MPCV.VM.FolderBrowserInfoVM.FolderItemWrapper 对象表示(简化声明)。

public class FolderItemWrapper {
    public PathItemVM Item { get; }
	public PathItemVM GetSynchronizationSource();
}

这个对象仅仅是一个间接层,以便能够访问当前项以及它在另一个文件夹浏览器中的同步项(项目同步可以在菜单 "Tabs" > "Properties" 中配置)。Item 是一个属性,因为它直接绑定;同步项必须主动查找,这对于每个项目大约需要 O(N),总共需要 O(N²)。每个路径项本身可以通过以下简化接口访问:

public class PathItemVM {
    public string DisplayName {get;}
    public string PathName {get;}
    public System.Windows.Media.Color BackgroundColor {get; set;}
}

作为一个例子,我使用了这个新接口来创建一个文件夹比较(在 $\lobster\MPCV\Scripts\Compare.ps1 中找到)。较新的文件以浅绿色标记,较旧的文件以浅红色标记。在另一个文件夹中找不到的文件以浅灰色标记。相同的文件的背景保持白色。

Start-Sleep -m 500

foreach ($i in $input) { 
    $c = New-Object System.Windows.Media.Color
    
    $source = $i.GetSyncSource();
    # $bkgcol = $i.Item.BackgroundColor;
    
    $c.A = 255;
    $c.R = 255;
    $c.G = 255;
    $c.B = 255;

    if ($source) 
    {    
        # object does exist in synchronization source
        # Test whether it is a file
        if ((Test-Path $i.Item.Pathname -pathType leaf) -and 
                       (Test-Path $source.Pathname -pathType leaf))
        { 
            $md5 = new-object -TypeName System.Security.Cryptography.MD5CryptoServiceProvider;
            $hash1 = [System.BitConverter]::ToString(
                      $md5.ComputeHash([System.IO.File]::ReadAllBytes($i.Item.Pathname)));
            $hash2 = [System.BitConverter]::ToString(
                      $md5.ComputeHash([System.IO.File]::ReadAllBytes($source.Pathname)));
            if ($hash1 -eq $hash2) {
                # Files are identical - keep white
            } else {
                    $lw1 = (Get-Item $i.Item.Pathname).LastWriteTime;
                    $lw2 = (Get-Item $source.Pathname).LastWriteTime;
                if ($lw1 -gt $lw2) {
                    # Files are different - mark red
                    $c.A =255;
                    $c.R =128;
                    $c.G =255;
                    $c.B =128;
                } else {
                    # Files are different - mark red
                    $c.A =255;
                    $c.R =255;
                    $c.G =128;
                    $c.B =128;
                }
            }
        } else {
            # At least one item is not a file, therefore state is unknown
        }
    } else {
        # Object does not exist in synchronization source
        $c.A =255;
        $c.R =192;
        $c.G =192;
        $c.B =192;    
    }    
    $i.Item.BackgroundColor = $c;
}

脚本到位后,必须将其链接到文件夹浏览器(在菜单 "Tab" > "Configuration" > Folder Browser Configuration > "Update Script" 中为每个应使用该脚本的文件浏览器设置脚本的完整路径名)。结果自定义的 MPCV 外观如下:

Powershell Result Sample

脚本将异步运行,以避免干扰文件夹内容的加载。

FolderBrowser 模型

Lobster.FolderBrowser 命名空间被设计为可重用和可扩展的。入口点是 PathModelBridge 类,它通过 CurrentPath 属性设置为一个路径。然后 Items 集合将包含位于给定路径下的项。总而言之,以下成员值得关注:

public class PathModelBridge {
    /// The path (e.g. C:\Windows) which items are to be shown.
    /// When a new path is set, it may relative to the current path 
    /// as well as absolute. The path model creators are responsible 
    /// for finding out what is meant.
    public string CurrentPath { get; set; };
		
    /// The items that are located at the current path and are shown
    /// under the given configuration. This is an observable collection
    /// as the implementation may observe the path and reflect changes 
    /// directy. 
    public ReadOnlyObservableCollection<IPathItemModel> Items { get; };
		
    /// Performs a precheck if there is a parent path to the current path
    /// and whether this path is an extension of the base path
    public bool CanNavigateUp { get; };
				
    /// Performs a precheck if a given path extends the current base path 
    public bool ExtendsBasePath(string value);
		
    /// Gets or sets the base path which serves as root for any navigation. The current 
    /// path may not be further up the directory tree than the base path.
    public string BasePath { get; set; };
		
    /// Returns true, if the current path can be set to the given path. False
    /// may be returned if the base path is violated or there is no appropiate
    /// implementation for the given path.
    /// The given path may be absolute or relative to the current one. The
    /// path model creators are responsible for finding out what is meant.
    public bool CanNavigateTo(string path);
		
    /// Is true if the base path can be navigated to. This is typically 
    /// not the case if the base path is already the current path.
    public bool CanNavigateToBase { get; };
}

您可能注意到所有关于实现的描述,其背后的想法如下:一旦设置了当前文件夹(从架构角度来看,这只是一个字符串,目前只有一个针对路径名的实现),桥就会通过使用工厂模式选择一个合适的实现,然后返回该实现提供的项。我创建了一个类图来概述模型。

Model Class Diagram

所有文件夹实现都提供 IPathModel 接口,目前有一个针对工作区的 DriveModel 实现和一个针对文件夹的 FileModel 实现。文件夹项由实现创建,并且必须提供 IPathModelItem 接口。 

因此,可以轻松编写自己的实现并将其挂接到工厂中。

WPF 和 MVVM 

老实说,有很多东西,尤其是 WPF 部分。我有很多东西需要弄清楚,而且一些小问题却花费了大量时间。WPF 和 MVVM 肯定不是一种可以轻松快速开发用户界面的方法。无论如何,我真的不想深入研究这个,因为我写 MVVM 教程已经晚了。如果您正在考虑编写一个 WPF 项目,请问自己:我是否真的愿意忍受被忽视的复杂性(附加依赖属性、转换器、绑定语法、验证器、臃肿的 C# 代码和 XAML 使 XSLT 看起来不错)来获得大家希望在其中看到的好处?

不久前,CodeProject 做了一项调查,询问 未来编程的复杂性会增加还是减少? 简而言之,很多人认为复杂性保持不变。人们也可能想维护遗留软件,以确保复杂性不会增加。

集合连接器 (The Collection Joiners)

好的,抱怨够了。顾名思义,集合连接器类集(位于 Lobster.Components.Collections.Joiner)为基于 ICollection 的集合提供即时连接。

只需将几个通用集合放入 CollectionJoiner 类的输入 Collections 中,输出 JoinedCollection(类型为 ReadOnlyObservableCollection - 允许在此处添加和删除项对我来说似乎太费力了)就会在每次添加或删除集合时更新。对任何输入集合的更改也会转发到输出(除了 Clear(),目前 NotifyCollectionChangedAction.Reset 不受支持)。

这两个类内部都使用 CollectionJoiner

另一项功能是自动包装连接集合中的项。构造函数允许传入一个类类型 TJoinedItem,它将在输出中使用而不是输入项类型。

public class CollectionJoiner<TJoinedItem> 
    where TJoinedItem : class
{...}

当然,连接器本身通常不知道如何从集合项创建输出项,因此必须通过使用委托 CreateItemBox 来提供此信息。以下是应用程序中的一个使用示例:

...
{
    ...
    var SubItemWrapper = new CollectionJoiner<ITreeNodeVM>();
    SubItemWrapper.CreateItemBox = (o) =>
    {
        return createItemBox(this, o);
    };
    SubItems = SubItemWrapper.JoinedCollection;
    SubItemWrapper.Collections.Add(subItems);

    this.SubItems = SubItemWrapper.JoinedCollection;
    ...
}
...

有一个特殊的可选接口 IItemPositionInfo,用于 TJoinedItem,它附带实现并且如果使用,会得到特别支持。该接口提供一个属性 Y,用于标识输入集合的索引,以及 X,用于标识项在输入集合中的索引。

还有两种类型安全的 CollectionJoiner 包装器:

public class ObservableCollectionJoiner<TItem, TJoinedItem> 
    where TItem : class
    where TItemBox : class
{...}
				
public class ReadOnlyObservableCollectionJoiner<TItem, TJoinedItem> 
    where TItem : class
    where TItemBox : class
{...}

当使用这些类而不是基实现时,输入集合类型固定为 ObservableCollection / ReadOnlyObservableCollection。输入项类型通过 TItem 类型参数固定。我更喜欢这些类,因为它们更类型安全,因此在尝试添加不兼容集合时会进行编译时警告。

我在实现中经常使用它,例如,主窗口中每个浏览器行本身就是一个集合,但 "Folder Browser Configuration" 需要一次性显示所有信息。您还可以看到第一个列中生成的索引信息。

当然,还有一些标准的组件可以用于类似的任务:

  • System.Windows.Data.CollectionViewSource 可用于过滤和排序。
  • System.Windows.Data.CompositeCollection 允许合并不同类型的输入集合(它们不需要是可观察的),并且不指定输出类型。

如何为路径项添加自定义详细信息

假设我们要通过添加文件版本信息来扩展文件模型。从技术上讲,文件版本很容易获得:

FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(pathName);
string fileVersion = myFileVersionInfo.FileVersion;

为了将该信息添加到 FileModel 类,我们打开 FileModel.cs。在那里,我们在类末尾插入一个 FileVersion 属性(项模型中属性的顺序很重要,因为它用作 UI 的初始顺序)。默认情况下,属性名称将用作列标题。通过使用 DisplayNameAttribute 可以分配另一个标题。由于创建 FileModelFolderModel 可能从后台线程执行更新,因此属性必须是线程安全的,并且需要将通知封送到 UI 线程。

private object fileVersionLocker = new object();
private string fileVersion;

[DisplayName("File Version")]
public string FileVersion {
    get {
        lock (fileVersionLocker) {
            return fileVersion;
        }
    }
    private set {
        lock (fileVersionLocker) {
            if (value == fileVersion) return;
            fileVersion = value;
        }
        syncContext.Post((o) => {
            this.OnPropertyChanged("FileVersion");
        }, null);
    }
}

项模型通常从其构造函数初始化。但是,由于 FolderModel 可能会发送更新,因此初始化已移至 FileModel.UpdateDetails() 方法。当底层文件发生更改时,也会调用此方法。在此方法处理文件的部分,我们添加以下代码:

public void UpdateDetails()
{
    ...
    if (File.Exists(pathname))
    {
        ...
		
        FileVersionInfo myFileVersionInfo = FileVersionInfo.GetVersionInfo(pathname);
        FileVersion = myFileVersionInfo.FileVersion;
    }
    ...
}

就这样。由于新属性会自动添加到 UI,您可能需要隐藏它们。为此,有两个属性:标记为 [PropertyHidden] 的属性永远不会转发到 UI,而 [PropertyHiddenByDefault] 表示属性最初不会在 UI 中显示。

参考文献

关于 WPF 和 MVVM

关于 MVVM 已经有很多文章了,最好从那里阅读,而不是从我这里阅读。

更多关于特定问题解决方案的参考文献可以在代码中找到。

历史记录

  • 2012/12/13 首次发布
  • 2013/01/06
    • 新的配置对话框取代了 DataGrid 实现。新的配置对话框需要一个新的 VM,这表明 View 和 VM 并非真正可以分离。例如,用一个包含单个选择的 TreeView 和一个完全不同的选择模型(每个树项 VM 中的 IsSelected 成员)替换两个 DataGrid(每个需要一个 SelectedItem VM 成员)会引起 VM 的巨大变化。
    • 模型现在接受相对路径名;原始模型要求 VM 从相对输入计算绝对路径,这要求 VM 假设路径始终看起来像文件系统路径。这应该是模型的责任。 
  • 2013/01/11 下载更正:嗯,我包含了错误的项目,而且没有人告诉我……  无论如何,现在已经修复了……我希望 
  • 2013/01/14 对 MiniColorPicker、工具栏和配置对话框的修复。
  • 2013/02/09
    • 路径项可以关联详细信息,这些详细信息可以像 Windows 资源管理器的报告视图一样选择性地显示。为现有路径模型添加新详细信息就像编写一个属性一样简单。
    • FileModel 类的新路径模型详细信息包括:文件大小、属性(文本表示)、创建和修改日期。
    • DriveModel 类的新路径模型详细信息包括:可用大小、总大小、使用百分比、卷标。
    • 路径模型可以单独配置。虽然路径模型在每次路径更改后都会重新创建,但路径模型配置会为每个 PathModelBridge 重用。
    • 改进了路径编辑框的命令语法。现在它们接受带参数的命令。例如,通过清除路径,然后键入 "explorer ." 并按 Enter,将会在当前选定的路径处打开一个 Windows 资源管理器。  
  • 2013/03/24
    • 修复了文件夹内容更改时 UI 更新崩溃的问题。
    • 为文件夹浏览器配置上下文菜单。
    • 用户指南以 HTML 帮助 .chm 文件形式提供,可从 MPCV 访问。
    • 切换到 ListView 以显示路径项。
  • 2013/04/07
    • 启用了文本搜索,可以通过键入其首字母来查找活动浏览器中的项。
    • 书签,重要的项可以根据其路径名高亮显示。
    • MPCV 可以置于所有窗口之上。  
  • 2013/04/20
    • 改进了 Tab 顺序。
    • 通过红色边框清楚地识别了路径文本框和文件夹浏览器的键盘焦点。
    • 文件夹浏览器接受 Enter 键以切换到子文件夹或激活主命令(针对路径项)。
    • 文件夹浏览器接受 Backspace 键以切换到父路径。
  • 2013/05/09
    • 修复了在错误文件夹浏览器上执行上下文菜单的问题。
    • 修复了观察短暂文件时的崩溃(当 FolderFileModel.UpdateDetails(...) 在一个正在删除的文件上运行时)。
  • 2013/05/30
    • 添加了基本 PowerShell 支持以进行自定义,目前更新脚本可以更改文件夹项的背景颜色。
  • 2013/11/23
    • 修复了添加新选项卡和向选项卡配置中的行添加文件夹浏览器时的两个崩溃。不过,QA 部门在哪里呢,当你需要他们的时候。添加了次要改进,例如访问主文件夹以及在 PowerShell 脚本路径中使用 {home} 占位符。
  • 2016/10/05
    • 修复了文件名中下划线的显示问题。
My Personal Commander Variant - CodeProject - 代码之家
© . All rights reserved.