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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.77/5 (27投票s)

2006 年 6 月 22 日

CPOL

7分钟阅读

viewsIcon

345357

downloadIcon

10583

本文演示了如何使用组合框下拉列表来选择 TreeView 节点中的文本。

DropDownTreeView Control Screenshot

引言

最近,我正在设计一个图形用户界面,其中使用 TreeView 来表示某些数据的结构。我需要让用户能够更改其中几个项的文本,但文本需要保持特定的格式。如果我能在每个 TreeView 项的位置放置一个 ComboBox 下拉控件就好了!这样用户就可以从预定义的项列表中选择 TreeNode 的值。

本文介绍了我为实现这个 DropDownTreeView 控件所做的几晚努力的结果。希望您喜欢并觉得它有用!

背景

我搜索了网络(当然是从 CodeProject 开始),看看是否有人做过类似的事情。虽然我没有找到我需要的东西(这就是为什么我写了这个示例),但我确实找到了一些有趣的创意,并将它们整合到一起。

  • 带组合框的 TreeView。这篇文章虽然目前形式完全不可用,但为我的控件提供了灵感。
  • 在您的应用程序中使用 Treeview。我使用了从 TreeNode 继承的这个想法来构建我的控件。
  • 下拉 TreeView 控件。这与我想要解决的问题正好相反。这篇文章将 TreeView 放在了 ComboBox 的下拉列表中。而我则希望将 ComboBox 下拉列表放在 TreeView 控件上。

我解决整个问题的办法是创建一个继承自 TreeView 的自定义控件,该控件能够解释我存储在继承自 TreeNode 的类中的额外数据。我将该控件命名为 DropDownTreeView,它知道如何显示 DropDownTreeNodes。让我们逐一来了解一下。

扩展 TreeNode

由于每个 DropDownTreeNode 都必须能够显示一个 ComboBox,因此我简单地将一个 ComboBox 添加为 DropDownTreeNode 的一个属性。为了使一切正常工作,我需要确保 DropDownTreeNodeComboBoxDropDownStyle 设置为 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 集合中。本质上,这意味着常规的 TreeNodes *以及* 新的 DropDownTreeNodes 都可以用于 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 中进行了选择,因此我们向 ComboBoxSelectedValueChanged 事件添加了一个事件侦听器。我们还想知道何时发生了某些事情(例如用户单击了显示 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 并将其设置为未下拉。然后,ComboBoxTreeView 的控件列表中移除,并且当前节点被置为 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)添加到您的窗体中。

然后,您需要用任何组合的 TreeNodes 和 DropDownTreeNodes 来填充 TreeView。您可以访问 DropDownTreeNodeComboBox 属性,并像对待任何其他 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);

最后,您需要将您的 TreeNodes 和 DropdownTreeNodes 添加到 DropDownTreeViewNodes 属性中,如上述代码示例的最后一行所示。

我将示例项目分成了两个 Visual Studio 项目——一个用于控件,另一个用于使用该控件的测试应用程序。控件项目将编译成一个包含两个类 DropDownTreeViewDropDownTreeNode 的 DLL。然后,您可以将此控件导入到您的 Visual Studio 工具箱中。

  1. 启动 Visual Studio 2005 的新实例。使用 **Windows 应用程序** 模板创建一个新应用程序。
  2. 将 *DropDownTreeView.dll* 复制到您的项目目录中。
  3. 在新的 Windows Forms 项目中,以设计模式打开主窗体。转到工具箱,右键单击,然后选择“选择项...”(Choose Items...)。然后,单击“浏览...”(Browse...) 按钮,并选择您在步骤 2 中复制到项目的 *DropDownTreeView.dll*。
  4. 这应该会添加组件并使其在对话框中高亮显示。确保它已选中,然后单击“确定”。
  5. DropDownTreeView 控件应该会出现在工具箱的“常规”(General) 选项卡中——现在您可以将其拖到窗体上使用了。它的行为应该与普通的 TreeView 完全相同!
  6. 尽情享用!

结论

希望您喜欢!如果您喜欢或不喜欢,或者有任何改进建议,请随时给我发送评论。这是我的第一个 CodeProject 文章,我写得很开心。

历史

  • 2006/6/21 - 创建文章的第一个版本。包含示例代码和示例项目。
  • 2006/9/21 - 终于抽出时间更新了文章。此版本包含许多错误修复,感谢所有与我联系并提供改进和建议的人。此版本比第一个版本更完善。感谢 mpasqual、OrlandoCurioso 和 Ashalatha Adavalli。

联系方式

您可以通过 matt dot valerio at gmail dot com 联系我,关于本文有任何问题。

© . All rights reserved.