具有多选功能的轻量级 FileTreeView






4.80/5 (7投票s)
轻量级事件驱动控件,递归最小化且无需手动跟踪选定项。
引言
市面上有很多公共领域的支持多选的TreeView
控件以及文件浏览器,但大多数要么非常复杂,要么很老(.NET 2.0或更早),要么速度太慢不符合我们的需求,要么不支持多选。
我们想要一个轻量级、灵活且相对快速的控件,能够按需从文件系统的任何节点进行填充。
背景
当我开始构建这个DLL时,多选功能似乎是最棘手的问题,所以我查看了一些网上的其他代码。我发现的任何东西都在IList
、ArrayList
(嗯)或其他数据结构中手动跟踪节点选择。
如果允许用户随意选择/取消选择,相关的代码可能会变得有点复杂,我觉得一定有更好的方法。
我曾考虑使用TreeNode Tag
属性来存储一个自定义对象,该对象将同时包含节点的选中状态以及适当的“Info
”对象(FileInfo
、DirectoryInfo
、DriveInfo
),但一时冲动,我决定先看看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

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日:首次发布