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

C#支持多选的TreeView

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.37/5 (22投票s)

2002年8月14日

3分钟阅读

viewsIcon

444522

downloadIcon

6041

在 .NET treeview 控件中启用多选

.NET TreeView 控件没有内置的多选功能。现在有了。这篇文章关于 C# 描述了一个具有多选功能的树形视图控件,它派生自 .NET TreeView 控件。它支持 CTRL 和 SHIFT 组合。

如何使用

此控件是一个 C# 控件。编译后,它就变成了托管的 .NET 组件,其行为很像 VB 开发人员的优秀 ActiveX 组件。要在您的应用程序中使用它,您至少有两个选择。第一个是简单地引用源代码项目中的 TreeViewMS 项目,方法是右键单击您当前的 Windows Form 应用程序并选择 添加引用,然后浏览到 TreeViewMS.csproj。下面的屏幕截图显示了步骤

将对新 TreeView 控件的引用添加到您的代码中

您也可以将树形视图控件一次性添加到您的 Toolbox 窗口中,然后简单地将其拖放到您的 Form 上。为此,显示 .NET Studio Toolbox 窗口,然后右键单击并选择 自定义工具箱,然后选择 .NET Framework 组件 选项卡,然后浏览到已编译的控件:TreeViewMS.dll,如下面的截图所示

将新的树形视图控件添加到 Visual Studio .NET 工具箱

一旦您在表单中添加了这个树形视图控件,您就可以开始像使用基本的 .NET TreeView 控件一样使用它。新增内容在于一个新公开的属性 SelectedNodes,它返回选中的 TreeNode 元素的集合。如果我们以演示应用程序为例,它将选定的项目添加到右侧的列表视图中,那么代码如下所示

    private TreeViewMS.TreeViewMS treeViewMS1;
    private System.Windows.Forms.ListView listView1;
    ...

    // add selected items from treeview to the
    // listview on the right hand side
    foreach (TreeNode n in treeViewMS1.SelectedNodes)
    {
       listView1.Items.Add( n.Text, n.ImageIndex );
    }

SelectedNodes 也是一个读写属性。以下是一个强制选择给定树形项目的示例代码

    // valid item from the sample tree
    TreeNode n1 = treeViewMS1.Nodes[0].Nodes[0].Nodes[0]; 
    // valid item from the sample tree
    TreeNode n2 = treeViewMS1.Nodes[0].Nodes[0].Nodes[2]; 
    ArrayList coll = new ArrayList();
    coll.Add(n1);
    coll.Add(n2);
    treeViewMS1.SelectedNodes = coll;

技术细节

当然,此控件派生自基本的 TreeView 控件

public class TreeViewMS : System.Windows.Forms.TreeView
{
    // class members
    protected ArrayList     m_coll;
    protected TreeNode      m_lastNode, m_firstNode;

    ...

    /* API method */ public ArrayList SelectedNodes
    {
        get
        {
            return m_coll;
        }
        set
        {
            removePaintFromNodes();
            m_coll.Clear();
            m_coll = value;
            paintSelectedNodes();
        }
    }

}

我们需要控制项目选择的事件,例如 BEFORESELECTAFTERSELECT,以获取当前选定的节点。BEFORESELECT 仅用于取消节点选择,例如,当您按住 CTRL 键两次选择一个节点时。

由于 Microsoft 已经清楚地意识到 TreeView 可能会被派生,因此他们有可重写的方法 BeforeSelect(...)AfterSelect(...),它们不会干扰同名的事件。我们所需要做的就是重写这两种方法,并且不要忘记在实现中调用基类

protected override void OnBeforeSelect(TreeViewCancelEventArgs e)
{
    // e.Node is the current node exposed by the base TreeView control
    base.OnBeforeSelect(e);

    bool bControl = (ModifierKeys==Keys.Control);
    bool bShift = (ModifierKeys==Keys.Shift);

    // selecting twice the node while pressing CTRL ?
    if (bControl && m_coll.Contains( e.Node ) )
    {
        // unselect it
        // (let framework know we don't want selection this time)
        e.Cancel = true;

        // update nodes
        removePaintFromNodes();
        m_coll.Remove( e.Node );
        paintSelectedNodes();
        return;
    }

    m_lastNode = e.Node;
    if (!bShift) m_firstNode = e.Node; // store begin of shift sequence
}

protected override void OnAfterSelect(TreeViewEventArgs e)
{
    // e.Node is the current node exposed by the base TreeView control

    base.OnAfterSelect(e);

    bool bControl = (ModifierKeys==Keys.Control);
    bool bShift = (ModifierKeys==Keys.Shift);

    if (bControl)
    {
        if ( !m_coll.Contains( e.Node ) ) // new node ?
        {
            m_coll.Add( e.Node );
        }
        else  // not new, remove it from the collection
        {
            removePaintFromNodes();
            m_coll.Remove( e.Node );
        }
        paintSelectedNodes();
    }
    else 
    {
        if (bShift)
        {
            Queue myQueue = new Queue();

            TreeNode uppernode = m_firstNode;
            TreeNode bottomnode = e.Node;

            // case 1 : begin and end nodes are parent
            bool bParent = isParent(m_firstNode, e.Node);
            if (!bParent)
            {
                bParent = isParent(bottomnode, uppernode);
                if (bParent) // swap nodes
                {
                    TreeNode t = uppernode;
                    uppernode = bottomnode;
                    bottomnode = t;
                }
            }
            if (bParent)
            {
                 TreeNode n = bottomnode;
                 while ( n != uppernode.Parent)
                 {
                     if ( !m_coll.Contains( n ) ) // new node ?
                         myQueue.Enqueue( n );

                      n = n.Parent;
                 }
            }
            // case 2 : nor the begin nor the
            // end node are descendant one another
            else
            {
                 // are they siblings ?                 

                 if ( (uppernode.Parent==null && bottomnode.Parent==null) 
                       || (uppernode.Parent!=null && 
                       uppernode.Parent.Nodes.Contains( bottomnode )) )
                 {
                      int nIndexUpper = uppernode.Index;
                      int nIndexBottom = bottomnode.Index;
                      if (nIndexBottom < nIndexUpper) // reversed?
                      {
                           TreeNode t = uppernode;
                           uppernode = bottomnode;
                           bottomnode = t;
                           nIndexUpper = uppernode.Index;
                           nIndexBottom = bottomnode.Index;
                      }

                      TreeNode n = uppernode;
                      while (nIndexUpper <= nIndexBottom)
                      {
                           if ( !m_coll.Contains( n ) ) // new node ?
                               myQueue.Enqueue( n );

                           n = n.NextNode;

                           nIndexUpper++;
                      } // end while

                  }
                  else
                  {
                      if ( !m_coll.Contains( uppernode ) ) 
                          myQueue.Enqueue( uppernode );
                      if ( !m_coll.Contains( bottomnode ) ) 
                          myQueue.Enqueue( bottomnode );
                  }

             }

             m_coll.AddRange( myQueue );

             paintSelectedNodes();
             // let us chain several SHIFTs if we like it
             m_firstNode = e.Node; 

         } // end if m_bShift
         else
         {
              // in the case of a simple click, just add this item
              if (m_coll!=null && m_coll.Count>0)
              {
                   removePaintFromNodes();
                   m_coll.Clear();
              }
              m_coll.Add( e.Node );
          }
     }
}


// Helpers
//
//


protected bool isParent(TreeNode parentNode, TreeNode childNode)
{
    if (parentNode==childNode)
        return true;

    TreeNode n = childNode;
    bool bFound = false;
    while (!bFound && n!=null)
    {
        n = n.Parent;
        bFound = (n == parentNode);
    }
    return bFound;
}


protected void paintSelectedNodes()
{
    foreach ( TreeNode n in m_coll )
    {
        n.BackColor = SystemColors.Highlight;
        n.ForeColor = SystemColors.HighlightText;
    }
}

protected void removePaintFromNodes()
{
    if (m_coll.Count==0) return;

    TreeNode n0 = (TreeNode) m_coll[0];
    Color back = n0.TreeView.BackColor;
    Color fore = n0.TreeView.ForeColor;

    foreach ( TreeNode n in m_coll )
    {
        n.BackColor = back;
        n.ForeColor = fore;
    }
}

正如您可能注意到的,SelectedNodes 返回一个底层 TreeView 元素的 System.Collections.ArrayList 实例,而不是更自然的 System.Windows.Forms.TreeNodeCollection 实例。为什么会这样?实际上,这不由开发人员决定,TreeNodeCollection 由 .NET 框架故意提供,具有隐藏的构造函数,因此无法重用它。这引起了公共新闻组的一些关注,但微软的人员尚未同意很快更改它。

更简单的重用

如果您不想重新分发 TreeViewMS.dll 库怎么办(我承认单独的库总是瓶颈)?您所要做的就是获取上述 OnBeforeSelect()OnAfterSelect() 的代码,并将它们附加到标准的 TreeView 事件。

感谢 David Sleeckx 的错误查找。

历史

  • 2002年8月8日 - 第一个版本。
  • 2002年8月18日更新。
© . All rights reserved.