带组合框下拉节点的 TreeView 控件






4.77/5 (27投票s)
本文演示了如何使用组合框下拉列表来选择 TreeView 节点中的文本。
引言
最近,我正在设计一个图形用户界面,其中使用 TreeView
来表示某些数据的结构。我需要让用户能够更改其中几个项的文本,但文本需要保持特定的格式。如果我能在每个 TreeView
项的位置放置一个 ComboBox
下拉控件就好了!这样用户就可以从预定义的项列表中选择 TreeNode
的值。
本文介绍了我为实现这个 DropDownTreeView
控件所做的几晚努力的结果。希望您喜欢并觉得它有用!
背景
我搜索了网络(当然是从 CodeProject 开始),看看是否有人做过类似的事情。虽然我没有找到我需要的东西(这就是为什么我写了这个示例),但我确实找到了一些有趣的创意,并将它们整合到一起。
- 带组合框的 TreeView。这篇文章虽然目前形式完全不可用,但为我的控件提供了灵感。
- 在您的应用程序中使用 Treeview。我使用了从
TreeNode
继承的这个想法来构建我的控件。 - 下拉 TreeView 控件。这与我想要解决的问题正好相反。这篇文章将
TreeView
放在了ComboBox
的下拉列表中。而我则希望将ComboBox
下拉列表放在TreeView
控件上。
我解决整个问题的办法是创建一个继承自 TreeView
的自定义控件,该控件能够解释我存储在继承自 TreeNode
的类中的额外数据。我将该控件命名为 DropDownTreeView
,它知道如何显示 DropDownTreeNodes
。让我们逐一来了解一下。
扩展 TreeNode
由于每个 DropDownTreeNode
都必须能够显示一个 ComboBox
,因此我简单地将一个 ComboBox
添加为 DropDownTreeNode
的一个属性。为了使一切正常工作,我需要确保 DropDownTreeNode
的 ComboBox
的 DropDownStyle
设置为 DropDownList
,因此在 get
/set
方法中进行了显式设置。
public class DropDownTreeNode : TreeNode
{
// *snip* Constructors go here
private ComboBox m_ComboBox = new ComboBox();
public ComboBox ComboBox
{
get
{
this.m_ComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
return this.m_ComboBox;
}
set
{
this.m_ComboBox = value;
this.m_ComboBox.DropDownStyle = ComboBoxStyle.DropDownList;
}
}
}
这使您可以使用如下代码访问内部 ComboBox
的属性:
DropDownTreeNode tn1 = new DropDownTreeNode("Vacation 2006 (Denver)");
tn1.ComboBox.Items.Add("Vacation 2006 (Denver)");
tn1.ComboBox.Items.Add("Vacation 2005 (Miami)");
tn1.ComboBox.Items.Add("Vacation 2004 (Washington DC)");
tn1.ComboBox.Items.Add("Vacation 2003 (Houston)");
tn1.ComboBox.SelectedIndex = 0;
继承的真正强大之处体现在此处。因为 DropDownTreeNode
继承自 TreeNode
,所以它可以被添加到 Treeview
的内部 Nodes
集合中。本质上,这意味着常规的 TreeNode
s *以及* 新的 DropDownTreeNode
s 都可以用于 TreeView
的内部 TreeNodeCollection
。
TreeView tv = new TreeView();
TreeNode tn1 = new TreeNode("Test Node 1");
DropDownTreeNode tn2 = new DropDownTreeNode("Test Node 2");
// Legal! This is how you would normally do it.
tv.Nodes.Add(tn1);
// Also legal! Through inheritance, a DropDownTreeNode is a TreeNode.
tv.Nodes.Add(tn2);
扩展 TreeView
现在到了有趣的部分——在 TreeView
控件中显示 ComboBox
。我(相对而言)是 Windows Forms 新手,所以这段代码可能完全走错了方向。但这是一个开始,并且在我目前的项目中起作用。此外,感谢那些发表评论的 CodeProject 用户——我已将您的建议纳入到本版文章中。
本质上,我想做的是创建一个继承自 TreeView
的控件,以便它可以监听 NodeMouseClick
事件并显示所选节点的 ComboBox
(但仅当该节点是 DropDownTreeNode
时)。代码大致如下:
public class DropDownTreeView : TreeView
{
public DropDownTreeView() : base()
{
}
private DropDownTreeNode m_CurrentNode = null;
protected override void
OnNodeMouseClick(TreeNodeMouseClickEventArgs e)
{
// Are we dealing with a dropdown node?
if (e.Node is DropDownTreeNode)
{
this.m_CurrentNode = (DropDownTreeNode)e.Node;
// Need to add the node's ComboBox to the
// TreeView's list of controls for it to work
this.Controls.Add(this.m_CurrentNode.ComboBox);
// Set the bounds of the ComboBox, with
// a little adjustment to make it look right
this.m_CurrentNode.ComboBox.SetBounds(
this.m_CurrentNode.Bounds.X - 1,
this.m_CurrentNode.Bounds.Y - 2,
this.m_CurrentNode.Bounds.Width + 25,
this.m_CurrentNode.Bounds.Height);
// Listen to the SelectedValueChanged
// event of the node's ComboBox
this.m_CurrentNode.ComboBox.SelectedValueChanged +=
new EventHandler(ComboBox_SelectedValueChanged);
this.m_CurrentNode.ComboBox.DropDownClosed +=
new EventHandler(ComboBox_DropDownClosed);
// Now show the ComboBox
this.m_CurrentNode.ComboBox.Show();
this.m_CurrentNode.ComboBox.DroppedDown = true;
}
base.OnNodeMouseClick(e);
}
void ComboBox_SelectedValueChanged(object sender, EventArgs e)
{
HideComboBox();
}
void ComboBox_DropDownClosed(object sender, EventArgs e)
{
HideComboBox();
}
}
我们重写了 OnNodeMouseClick
函数,并检查了当前被单击的 TreeNode
(由 e.Node
指定)。如果该节点不是 DropDownTreeNode
,则无需执行任何操作。另一方面,如果它是 DropDownTreeNode
,则我们继续将节点的 ComboBox
添加到 DropDownTreeView
的控件集合中,设置其边界,然后显示它。此外,我们需要知道何时用户在显示的 ComboBox
中进行了选择,因此我们向 ComboBox
的 SelectedValueChanged
事件添加了一个事件侦听器。我们还想知道何时发生了某些事情(例如用户单击了显示 ComboBox
以外的区域)会导致 DropDown
自行关闭,因此我们订阅了 DropDownClosed
事件。
当这些事件处理程序中的每一个被调用时,它们都会执行隐藏 ComboBox
的代码。
private void HideComboBox()
{
if (this.m_CurrentNode != null)
{
// Unregister the event listener
this.m_CurrentNode.ComboBox.SelectedValueChanged -=
ComboBox_SelectedValueChanged;
this.m_CurrentNode.ComboBox.DropDownClosed -=
ComboBox_DropDownClosed;
// Copy the selected text from the ComboBox to the TreeNode
this.m_CurrentNode.Text = this.m_CurrentNode.ComboBox.Text;
// Hide the ComboBox
this.m_CurrentNode.ComboBox.Hide();
this.m_CurrentNode.ComboBox.DroppedDown = false;
// Remove the control from the TreeView's
// list of currently-displayed controls
this.Controls.Remove(this.m_CurrentNode.ComboBox);
// And return to the default state (no ComboBox displayed)
this.m_CurrentNode = null;
}
}
这段代码本质上逆转了显示 ComboBox
所执行的所有操作。代码将选定的文本从节点的 ComboBox
复制到树节点的文本中。然后,它隐藏 ComboBox
并将其设置为未下拉。然后,ComboBox
从 TreeView
的控件列表中移除,并且当前节点被置为 null。
我在这里遇到了一个有趣的问题,因为所有这些操作的顺序很重要。在更改 DroppedDown
属性*之前*,必须取消订阅 DropDownClosed
事件。否则,将 DroppedDown
属性设置为 false
将会触发 DropDownClosed
事件,如果仍然存在该事件的处理程序,ComboBox_DropDownClosed
将会被调用,进而调用 HideComboBox
,从而产生一个导致应用程序崩溃的循环。幸运的是,Visual Studio 2005 有一个很棒的调试器,并且这个奇怪的操作并不难检测。
正如您所见,DropDownTreeView
的操作非常简单。基本上,隐藏的 ComboBox
被强制显示在正在编辑的节点正上方,然后在选择完成后隐藏。
我花了一些额外的时间来使控件可用,因为有时用户可能希望在 ComboBox
显示时取消选择。订阅 DropDownClosed
事件并隐藏 ComboBox
可以处理用户单击 ComboBox
外部的情况。此外,如果用户滚动鼠标滚轮,TreeView
会滚动,但 ComboBox
不会跟随。虽然我可以处理鼠标滚轮事件并将 ComboBox
相应地移动,但更简单的方法(我认为视觉效果更好)是发生这种情况时将其隐藏。
protected override void OnMouseWheel(MouseEventArgs e)
{
HideComboBox();
base.OnMouseWheel(e);
}
使用代码
使用该控件非常简单。您需要将一个 DropDownTreeView
控件(而不是普通的 TreeView
)添加到您的窗体中。
然后,您需要用任何组合的 TreeNode
s 和 DropDownTreeNode
s 来填充 TreeView
。您可以访问 DropDownTreeNode
的 ComboBox
属性,并像对待任何其他 ComboBox
一样对待它。然后,您可以在任何通常使用普通 TreeNode
的地方使用 DropDownTreeNode
。这是用于制作开头截图的示例项目中的一个简短代码片段:
DropDownTreeNode weightNode = new DropDownTreeNode("1/4 lb.");
weightNode.ComboBox.Items.Add("1/4 lb.");
weightNode.ComboBox.Items.Add("1/2 lb.");
weightNode.ComboBox.Items.Add("3/4 lb.");
weightNode.ComboBox.SelectedIndex = 0;
DropDownTreeNode pattyNode = new DropDownTreeNode("All beef patty");
pattyNode.ComboBox.Items.Add("All beef patty");
pattyNode.ComboBox.Items.Add("All chicken patty");
pattyNode.ComboBox.SelectedIndex = 0;
TreeNode meatNode = new TreeNode("Meat Selection");
meatNode.Nodes.Add(weightNode);
meatNode.Nodes.Add(pattyNode);
TreeNode burgerNode = new TreeNode("Hamburger Selection");
burgerNode.Nodes.Add(condimentsNode);
burgerNode.Nodes.Add(meatNode);
this.dropDownTreeView1.Nodes.Add(burgerNode);
最后,您需要将您的 TreeNode
s 和 DropdownTreeNode
s 添加到 DropDownTreeView
的 Nodes
属性中,如上述代码示例的最后一行所示。
我将示例项目分成了两个 Visual Studio 项目——一个用于控件,另一个用于使用该控件的测试应用程序。控件项目将编译成一个包含两个类 DropDownTreeView
和 DropDownTreeNode
的 DLL。然后,您可以将此控件导入到您的 Visual Studio 工具箱中。
- 启动 Visual Studio 2005 的新实例。使用 **Windows 应用程序** 模板创建一个新应用程序。
- 将 *DropDownTreeView.dll* 复制到您的项目目录中。
- 在新的 Windows Forms 项目中,以设计模式打开主窗体。转到工具箱,右键单击,然后选择“选择项...”(Choose Items...)。然后,单击“浏览...”(Browse...) 按钮,并选择您在步骤 2 中复制到项目的 *DropDownTreeView.dll*。
- 这应该会添加组件并使其在对话框中高亮显示。确保它已选中,然后单击“确定”。
DropDownTreeView
控件应该会出现在工具箱的“常规”(General) 选项卡中——现在您可以将其拖到窗体上使用了。它的行为应该与普通的TreeView
完全相同!- 尽情享用!
结论
希望您喜欢!如果您喜欢或不喜欢,或者有任何改进建议,请随时给我发送评论。这是我的第一个 CodeProject 文章,我写得很开心。
历史
- 2006/6/21 - 创建文章的第一个版本。包含示例代码和示例项目。
- 2006/9/21 - 终于抽出时间更新了文章。此版本包含许多错误修复,感谢所有与我联系并提供改进和建议的人。此版本比第一个版本更完善。感谢 mpasqual、OrlandoCurioso 和 Ashalatha Adavalli。
联系方式
您可以通过 matt dot valerio at gmail dot com 联系我,关于本文有任何问题。