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

具有多选功能的轻量级 FileTreeView

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (7投票s)

2010年4月3日

CPOL

4分钟阅读

viewsIcon

26611

downloadIcon

1696

轻量级事件驱动控件,递归最小化且无需手动跟踪选定项。

引言

市面上有很多公共领域的支持多选的TreeView控件以及文件浏览器,但大多数要么非常复杂,要么很老(.NET 2.0或更早),要么速度太慢不符合我们的需求,要么不支持多选。

我们想要一个轻量级、灵活且相对快速的控件,能够按需从文件系统的任何节点进行填充。

背景

当我开始构建这个DLL时,多选功能似乎是最棘手的问题,所以我查看了一些网上的其他代码。我发现的任何东西都在IListArrayList(嗯)或其他数据结构中手动跟踪节点选择。

如果允许用户随意选择/取消选择,相关的代码可能会变得有点复杂,我觉得一定有更好的方法。

我曾考虑使用TreeNode Tag属性来存储一个自定义对象,该对象将同时包含节点的选中状态以及适当的“Info”对象(FileInfoDirectoryInfoDriveInfo),但一时冲动,我决定先看看TreeNode Checked属性是否能正常工作,即使CheckBox的**显示**被关闭了。

结果呢,它们确实可以。即使TreeView没有显示它们,它们也能在后台愉快地进行选中。有了这个(对我来说至少是)小小的启示,多选就变得容易多了。

本文的其余部分将重点介绍我们如何使用Checked属性来实现多选功能。

注意:该项目还需要TreeView的按需填充代码,我不想重复造轮子,因此我借鉴了Chandana Subasinghe在2006年10月发表在CodeProject上的另一篇文章 - https://codeproject.org.cn/KB/cs/TreeViewFileExplorer.aspx)。是的,我本可以使用DataSource,但对于这个项目,越轻量级越好。

代码

MFT多选代码的核心主要由两个事件处理程序组成。

_AfterSelect在节点被点击后触发,此时Tree的SelectedNode属性已被设置。_AfterCheck在该事件之后触发,每次我们以编程方式选中一个节点时。

让我们先来看_AfterSelect

在我们的代码中,事件处理程序通过查看用户按下的ModifierKeys(SHIFT、CTRL或两者)来决定选中哪些节点。

我们实现了复制Windows资源管理器右侧窗格的功能。SHIFT键和SHIFT-CTRL键的处理是最棘手的,但也不是太难,因为我们只触发“Checked”事件,而不试图跟踪选择(为清晰起见,省略了错误处理)。

protected void MultiSelectFileTreeView_AfterSelect(object sender, TreeViewEventArgs e)
{
    // if it is CTRL-SHIFT then bControl and bShift are both false 
    // but bBoth is true
    bool bBoth = ModifierKeys == (Keys.Shift | Keys.Control); 
    bool bControl = (ModifierKeys == Keys.Control);
    bool bShift = (ModifierKeys == Keys.Shift);

    if (!(bBoth || bControl || bShift))
    {
        //no modifier keys, so clear all nodes and select just this one
        ClearChecksRecursive(this.Nodes);
        e.Node.Checked = true;
        this.SelectedNode = e.Node;
        return;
    }
    if (bControl)
    {
        //don't clear, just do it - if already selected then deselect it
        e.Node.Checked = !e.Node.Checked;
        this.SelectedNode = e.Node;
        return;
    }
    if (bShift || bBoth)
    {
        // _prevNode is the SelectedNode at the time of the click
        //  bShift means no CTRL key so clear all the checks
        if (bShift)
            ClearChecksRecursive(this.Nodes);
        TreeNode TopNode = new TreeNode();
        TreeNode BottomNode = new TreeNode();

        // don't allow range selection across levels 
        if (_prevNode.Level != SelectedNode.Level)
        {
            TopNode = SelectedNode.Parent.Nodes[0];
            BottomNode = e.Node;
        }
        else
        {
            //are we going up or down?
            TopNode = (_prevNode.Index < SelectedNode.Index) ? _prevNode : SelectedNode;
            BottomNode = (SelectedNode.Index > _prevNode.Index) ? 
			SelectedNode : _prevNode;
        }
        // check all nodes in the range we selected
        // and if it's not a file, collapse it
        // we could add an option here to check folder children
        // and then not collapse the folder
        for (int x = TopNode.Index; x <= BottomNode.Index; x++)
        {
            TreeNode n = SelectedNode.Parent.Nodes[x];
            n.Checked = true;
            if (n.Tag.GetType() != typeof(FileInfo))
                n.Collapse();
        }
        return;
    }
 } 

现在我们触发了“Check”事件,我们所要做的就是捕获它们,并为正在被选中/取消选中的Node着色,以反映其状态。

_AfterCheck

  protected void MultiSelectFileTreeView_AfterCheck(object sender, TreeViewEventArgs e)
  {
    e.Node.BackColor = (e.Node.Checked == true) ? SelectedBackColor : this.BackColor;
    e.Node.ForeColor = (e.Node.Checked == true) ? SelectedForeColor : this.ForeColor;
    if (e.Node.Checked)
    {
        Type nodeType = e.Node.Tag.GetType();
        if (nodeType == typeof(DirectoryInfo) || nodeType == typeof(DriveInfo))
            e.Node.Expand();
    }
  }

Using the Code

MultiSelectFileTreeView_src

MFT类直接继承自TreeView,所有基类TreeView的成员都可以被您的代码访问。

使用正常的VS方法,将DLL引用添加到您的项目中,如果您愿意,还可以将其添加到您的VS工具箱中。

下载包中包含的ReadMe.txt文件描述了MFT的属性,这些属性很少。

我们追求的是轻量级,还记得吗?

您可以设置节点的选中颜色,并指定要使用的图像索引号,但父窗体负责提供ImageList,就像您使用普通的TreeView一样。

如前所述,MFT提供了与Windows资源管理器中发现的节点选择功能基本相同的功能 - 支持SHIFT-select、CTRL-select和SHIFT-CTRL-select功能。

此外,为可展开的节点提供了一个右键上下文菜单。截图显示了使用上下文菜单来选择D:\根目录中文件的测试WinForm应用程序。

测试应用程序在左侧窗格中实现了MFT控件,并订阅了其After_Check事件,使用下方显示的事件处理程序代码,在右侧的Listbox中填充选中的文件。一如既往,底层处理程序(我们的MFT事件)在父窗体中的处理程序之前运行。

注意:MFT还提供了一个PopulateAtPath方法,以便在适当的情况下,可以使用文件系统的一部分来满足应用程序的需求。

实现控件

以下是用于在测试窗体中实现控件的代码。请原谅缺乏错误处理,以便于阅读。

public Form1()
{
      InitializeComponent();
        MultiSelectFileTreeView1.ImageList = imageList1;
        MultiSelectFileTreeView1.AfterCheck += 
		new TreeViewEventHandler(MultiSelectFileTreeView1_AfterCheck);
        MultiSelectFileTreeView1.SelectedBackColor = Color.Violet;
        if (!MultiSelectFileTreeView1.Populate()) 
        {
            MessageBox.Show(MultiSelectFileTreeView1.Message);
        }
    }

    void MultiSelectFileTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
    {
        if (e.Node.Tag!=null && e.Node.Tag.GetType() == typeof(FileInfo))
     // in this case, I only want the File's path
        {
            listBox1.BeginUpdate();
            bool has = listBox1.Items.Contains(e.Node.FullPath);
            if (e.Node.Checked && !has)
            {
                listBox1.Items.Add(e.Node.FullPath);
             }
                 else if (!e.Node.Checked && has)
             {
                 listBox1.Items.Remove(e.Node.FullPath);
             }
             listBox1.EndUpdate();
         }
    }

关注点

需要注意的是,Node.Tag属性包含了每个节点的“info”对象,该对象是在节点从文件系统中填充时收集的。

因此,虽然测试应用程序只在listbox中显示FullPath,但整个Info对象同样可以轻松地供您的代码使用。

历史

  • 2010年4月3日:首次发布
© . All rights reserved.