锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / C#开源技术 / Winform多选树形控件

服务方向

人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发

联系方式

固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名“简易百科”和闲暇巴盗用锐英源原创内容。

Winform多选树形控件


最近开发里需要展开树形数据,考虑到观察的方便,需要让树形节点可以多选,但是C# Winform自带的控件不支持多选,就从codeproject上找了几个例子,这个例子比较简单,推荐给大家用,另外一个复杂,后面也会翻译。codeproject看不懂,找锐英源。

 

介绍

为什么.NET 没有多选树视图?用途很广,在树视图中打开复选框是一个非常糟糕的选择。我尝试了一些第三方树视图,我认为最让我失望的是对象模型与我习惯使用的 .NET 树视图不同。我想要的只是具有SelectedNodes属性和SelectedNode属性的标准 .NET 树视图。在 The Code Project 上快速搜索后,我在这里找到了Jean Allisat 的实现. 但我并不满意,因为有些事情表现不正确。例如,您单击一个节点,然后当您 Ctrl+单击第二个节点时,第一个节点将失去突出显示,直到单击操作完成。奇怪的。所以它看起来有点波涛汹涌,但它确实有效。我从 Jean 的实现开始,并将其提升到一个新的水平,尝试稍微清理一下 UI 行为。

使用代码

我在多选树视图的原始实现中遇到的“不稳定”问题是我们让树视图选择并突出显示选定的节点,而在被覆盖的事件中,我们将处理手动突出显示其他选定的节点。我得出的结论是自己做所有的突出显示,而不是与树视图争吵。所以我们需要做的第一件事是削弱树视图,使其永远不会有SelectedNode. 我们通过覆盖OnMouseDown, OnBeforeSelect&OnAfterSelect事件并设置base.SelectedNode为null以及设置e.Cancel一些事件来阻止它们处理来做到这一点。我们还隐藏了树视图的SelectedNode属性(使用 new 关键字)并重新实现了我们自己的版本。

 

注:很多朋友不明白面向对象的用处,这段话就非常直白地表达了用处,没用面向对象,是不可能改变样式。

 

现在我们有了一个残缺的树视图,我们可以实现选择节点的新逻辑。当您单击一个节点时,它会变为SelectedNode并突出显示。如果您没有按住 ModifierKey,那么我们可以清除之前的选择。如果您按住 ModifierKey即Ctrl,那么我们决定是否将此节点添加到选择中,或者如果它已经在选择中,则将其删除。如果您按住 Shift 键ModifierKey,那么我们必须选择从当前节点SelectedNode到此节点的所有节点。所有这些逻辑都存在于SelectNode()辅助函数中。

这里有一个问题。所有树视图的KeyDown消息都是以SelectedNode为出发点,因为从来没有SelectedNode(我们已经削弱了它......)然后你不能使用键盘来导航/编辑树。好吧,那不好……所以我们必须捕获OnKeyDown事件并处理 Left、Right、Up、Down、Home、End、Page Up、Page Down 和任何字母数字字符。如果按下 Ctrl 或 Shift,这些键命令中的每一个都可以具有不同的行为ModifierKey,并且如果分支是否展开,则可能具有不同的行为。

选定节点属性

C#
#region Selected Node(s) Properties

public MultiSelectTreeview()
{
    m_SelectedNodes = new List();
    base.SelectedNode = null;
}

private List m_SelectedNodes = null;
public List SelectedNodes
{
    get
    {
        return m_SelectedNodes;
    }
    set
    {
        ClearSelectedNodes();
        if( value != null )
        {
            foreach( TreeNode node in value )
            {
                ToggleNode( node, true );
            }
        }
    }
}

// Note we use the new keyword to Hide the native treeview's 
// SelectedNode property.
private TreeNode m_SelectedNode;
public new TreeNode SelectedNode
{
    get
    {
        return m_SelectedNode;
    }
    set
    {
        ClearSelectedNodes();
        if( value != null )
        {
            SelectNode( value );
        }
    }
}

#endregion  

被覆盖的事件

C#
#region Overridden Events

protected override void OnGotFocus( EventArgs e )
{
    // Make sure at least one node has a selection
    // this way we can tab to the ctrl and use the
    // keyboard to select nodes
    try
    {
        if( m_SelectedNode == null && this.TopNode != null )
        {
            ToggleNode( this.TopNode, true );
        }

        base.OnGotFocus( e );
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnMouseDown( MouseEventArgs e )
{
    // If the user clicks on a node that was not
    // previously selected, select it now.
    try
    {
        base.SelectedNode = null;

        TreeNode node = this.GetNodeAt( e.Location );
        if( node != null )
        {
            //Allow user to click on image
            int leftBound = node.Bounds.X; // - 20; 
            // Give a little extra room
            int rightBound = node.Bounds.Right + 10; 
            if( e.Location.X > leftBound && e.Location.X < rightBound )
            {
                if( ModifierKeys == 
                    Keys.None && ( m_SelectedNodes.Contains( node ) ) )
                {
                    // Potential Drag Operation
                    // Let Mouse Up do select
                }
                else
                {
                    SelectNode( node );
                }
            }
        }

        base.OnMouseDown( e );
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnMouseUp( MouseEventArgs e )
{
    // If you clicked on a node that WAS previously
    // selected then, reselect it now. This will clear
    // any other selected nodes. e.g. A B C D are selected
    // the user clicks on B, now A C & D are no longer selected.
    try
    {
        // Check to see if a node was clicked on
        TreeNode node = this.GetNodeAt( e.Location );
        if( node != null )
        {
            if( ModifierKeys == Keys.None && m_SelectedNodes.Contains( node ) )
            {
                // Allow user to click on image
                int leftBound = node.Bounds.X; // - 20; 
                // Give a little extra room
                int rightBound = node.Bounds.Right + 10; 
                if( e.Location.X > leftBound && e.Location.X < rightBound )
                {
                    SelectNode( node );
                }
            }
        }

        base.OnMouseUp( e );
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnItemDrag( ItemDragEventArgs e )
{
    // If the user drags a node and the node being dragged is NOT
    // selected, then clear the active selection, select the
    // node being dragged and drag it. Otherwise if the node being
    // dragged is selected, drag the entire selection.
    try
    {
        TreeNode node = e.Item as TreeNode;

        if( node != null )
        {
            if( !m_SelectedNodes.Contains( node ) )
            {
                SelectSingleNode( node );
                ToggleNode( node, true );
            }
        }

        base.OnItemDrag( e );
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnBeforeSelect( TreeViewCancelEventArgs e )
{
    // Never allow base.SelectedNode to be set!
    try
    {
        base.SelectedNode = null;
        e.Cancel = true;

        base.OnBeforeSelect( e );
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnAfterSelect( TreeViewEventArgs e )
{
    // Never allow base.SelectedNode to be set!
    try
    {
        base.OnAfterSelect( e );
        base.SelectedNode = null;
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
}

protected override void OnKeyDown( KeyEventArgs e )
{
    // Handle all possible key strokes for the control.
    // including navigation, selection, etc.

    base.OnKeyDown( e );

    if( e.KeyCode == Keys.ShiftKey ) return;

    //this.BeginUpdate();
    bool bShift = ( ModifierKeys == Keys.Shift );

    try
    {
        // Nothing is selected in the tree, this isn't a good state
        // select the top node
        if( m_SelectedNode == null && this.TopNode != null )
        {
            ToggleNode( this.TopNode, true );
        }

        // Nothing is still selected in the tree, 
        // this isn't a good state, leave.
        if( m_SelectedNode == null ) return;

        if( e.KeyCode == Keys.Left )
        {
            if( m_SelectedNode.IsExpanded && m_SelectedNode.Nodes.Count > 0 )
            {
                // Collapse an expanded node that has children
                m_SelectedNode.Collapse();
            }
            else if( m_SelectedNode.Parent != null )
            {
                // Node is already collapsed, try to select its parent.
                SelectSingleNode( m_SelectedNode.Parent );
            }
        }
        else if( e.KeyCode == Keys.Right )
        {
            if( !m_SelectedNode.IsExpanded )
            {
                // Expand a collapsed node's children
                m_SelectedNode.Expand();
            }
            else
            {
                // Node was already expanded, select the first child
                SelectSingleNode( m_SelectedNode.FirstNode );
            }
        }
        else if( e.KeyCode == Keys.Up )
        {
            // Select the previous node
            if( m_SelectedNode.PrevVisibleNode != null )
            {
                SelectNode( m_SelectedNode.PrevVisibleNode );
            }
        }
        else if( e.KeyCode == Keys.Down )
        {
            // Select the next node
            if( m_SelectedNode.NextVisibleNode != null )
            {
                SelectNode( m_SelectedNode.NextVisibleNode );
            }
        }
        else if( e.KeyCode == Keys.Home )
        {
            if( bShift )
            {
                if( m_SelectedNode.Parent == null )
                {
                    // Select all of the root nodes up to this point
                    if( this.Nodes.Count > 0 )
                    {
                        SelectNode( this.Nodes[0] );
                    }
                }
                else
                {
                    // Select all of the nodes up to this point under 
                    // this nodes parent
                    SelectNode( m_SelectedNode.Parent.FirstNode );
                }
            }
            else
            {
                // Select this first node in the tree
                if( this.Nodes.Count > 0 )
                {
                    SelectSingleNode( this.Nodes[0] );
                }
            }
        }
        else if( e.KeyCode == Keys.End )
        {
            if( bShift )
            {
                if( m_SelectedNode.Parent == null )
                {
                    // Select the last ROOT node in the tree
                    if( this.Nodes.Count > 0 )
                    {
                        SelectNode( this.Nodes[this.Nodes.Count - 1] );
                    }
                }
                else
                {
                    // Select the last node in this branch
                    SelectNode( m_SelectedNode.Parent.LastNode );
                }
            }
            else
            {
                if( this.Nodes.Count > 0 )
                {
                    // Select the last node visible node in the tree.
                    // Don't expand branches incase the tree is virtual
                    TreeNode ndLast = this.Nodes[0].LastNode;
                    while( ndLast.IsExpanded && ( ndLast.LastNode != null ) )
                    {
                        ndLast = ndLast.LastNode;
                    }
                    SelectSingleNode( ndLast );
                }
            }
        }
        else if( e.KeyCode == Keys.PageUp )
        {
            // Select the highest node in the display
            int nCount = this.VisibleCount;
            TreeNode ndCurrent = m_SelectedNode;
            while( ( nCount ) > 0 && ( ndCurrent.PrevVisibleNode != null ) )
            {
                ndCurrent = ndCurrent.PrevVisibleNode;
                nCount--;
            }
            SelectSingleNode( ndCurrent );
        }
        else if( e.KeyCode == Keys.PageDown )
        {
            // Select the lowest node in the display
            int nCount = this.VisibleCount;
            TreeNode ndCurrent = m_SelectedNode;
            while( ( nCount ) > 0 && ( ndCurrent.NextVisibleNode != null ) )
            {
                ndCurrent = ndCurrent.NextVisibleNode;
                nCount--;
            }
            SelectSingleNode( ndCurrent );
        }
        else
        {
            // Assume this is a search character a-z, A-Z, 0-9, etc.
            // Select the first node after the current node that
            // starts with this character
            string sSearch = ( (char) e.KeyValue ).ToString();

            TreeNode ndCurrent = m_SelectedNode;
            while( ( ndCurrent.NextVisibleNode != null ) )
            {
                ndCurrent = ndCurrent.NextVisibleNode;
                if( ndCurrent.Text.StartsWith( sSearch ) )
                {
                    SelectSingleNode( ndCurrent );
                    break;
                }
            }
        }
    }
    catch( Exception ex )
    {
        HandleException( ex );
    }
    finally
    {
        this.EndUpdate();
    }
}

#endregion

辅助函数

C#
#region Helper Methods

private void SelectNode( TreeNode node )
{
    try
    {
        this.BeginUpdate();

        if( m_SelectedNode == null || ModifierKeys == Keys.Control )
        {
            // Ctrl+Click selects an unselected node, 
            // or unselects a selected node.
            bool bIsSelected = m_SelectedNodes.Contains( node );
            ToggleNode( node, !bIsSelected );
        }
        else if( ModifierKeys == Keys.Shift )
        {
            // Shift+Click selects nodes between the selected node and here.
            TreeNode ndStart = m_SelectedNode;
            TreeNode ndEnd = node;

            if( ndStart.Parent == ndEnd.Parent )
            {
                // Selected node and clicked node have same parent, easy case.
                if( ndStart.Index < ndEnd.Index )
                {
                    // If the selected node is beneath 
                    // the clicked node walk down
                    // selecting each Visible node until we reach the end.
                    while( ndStart != ndEnd )
                    {
                        ndStart = ndStart.NextVisibleNode;
                        if( ndStart == null ) break;
                        ToggleNode( ndStart, true );
                    }
                }
                else if( ndStart.Index == ndEnd.Index )
                {
                    // Clicked same node, do nothing
                }
                else
                {
                    // If the selected node is above the clicked node walk up
                    // selecting each Visible node until we reach the end.
                    while( ndStart != ndEnd )
                    {
                        ndStart = ndStart.PrevVisibleNode;
                        if( ndStart == null ) break;
                        ToggleNode( ndStart, true );
                    }
                }
            }
            else
            {
                // Selected node and clicked node have same parent, hard case.
                // We need to find a common parent to determine if we need
                // to walk down selecting, or walk up selecting.

                TreeNode ndStartP = ndStart;
                TreeNode ndEndP = ndEnd;
                int startDepth = Math.Min( ndStartP.Level, ndEndP.Level );

                // Bring lower node up to common depth
                while( ndStartP.Level > startDepth )
                {
                    ndStartP = ndStartP.Parent;
                }

                // Bring lower node up to common depth
                while( ndEndP.Level > startDepth )
                {
                    ndEndP = ndEndP.Parent;
                }

                // Walk up the tree until we find the common parent
                while( ndStartP.Parent != ndEndP.Parent )
                {
                    ndStartP = ndStartP.Parent;
                    ndEndP = ndEndP.Parent;
                }

                // Select the node
                if( ndStartP.Index < ndEndP.Index )
                {
                    // If the selected node is beneath 
                    // the clicked node walk down
                    // selecting each Visible node until we reach the end.
                    while( ndStart != ndEnd )
                    {
                        ndStart = ndStart.NextVisibleNode;
                        if( ndStart == null ) break;
                        ToggleNode( ndStart, true );
                    }
                }
                else if( ndStartP.Index == ndEndP.Index )
                {
                    if( ndStart.Level < ndEnd.Level )
                    {
                        while( ndStart != ndEnd )
                        {
                            ndStart = ndStart.NextVisibleNode;
                            if( ndStart == null ) break;
                            ToggleNode( ndStart, true );
                        }
                    }
                    else
                    {
                        while( ndStart != ndEnd )
                        {
                            ndStart = ndStart.PrevVisibleNode;
                            if( ndStart == null ) break;
                            ToggleNode( ndStart, true );
                        }
                    }
                }
                else
                {
                    // If the selected node is above 
                    // the clicked node walk up
                    // selecting each Visible node until we reach the end.
                    while( ndStart != ndEnd )
                    {
                        ndStart = ndStart.PrevVisibleNode;
                        if( ndStart == null ) break;
                        ToggleNode( ndStart, true );
                    }
                }
            }
        }
        else
        {
            // Just clicked a node, select it
            SelectSingleNode( node );
        }

        OnAfterSelect( new TreeViewEventArgs( m_SelectedNode ) );
    }
    finally
    {
        this.EndUpdate();
    }
}

private void ClearSelectedNodes()
{
    try
    {
        foreach( TreeNode node in m_SelectedNodes )
        {
            node.BackColor = this.BackColor;
            node.ForeColor = this.ForeColor;
        }
    }
    finally
    {
        m_SelectedNodes.Clear();
        m_SelectedNode = null;
    }
}

private void SelectSingleNode( TreeNode node )
{
    if( node == null )
    {
        return;
    }

    ClearSelectedNodes();
    ToggleNode( node, true );
    node.EnsureVisible();
}

private void ToggleNode( TreeNode node, bool bSelectNode )
{
    if( bSelectNode )
    {
        m_SelectedNode = node;
        if( !m_SelectedNodes.Contains( node ) )
        {
            m_SelectedNodes.Add( node );
        }
        node.BackColor = SystemColors.Highlight;
        node.ForeColor = SystemColors.HighlightText;
    }
    else
    {
        m_SelectedNodes.Remove( node );
        node.BackColor = this.BackColor;
        node.ForeColor = this.ForeColor;
    }
}

private void HandleException( Exception ex )
{
    // Perform some error handling here.
    // We don't want to bubble errors to the CLR.
    MessageBox.Show( ex.Message );
}

#endregion

 

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州大学北校区院(文化路97号院)内