C# WinForm 自绘TreeView

C# WinForm 自绘TreeView

1. 问题提出

TreeView 控件很常用,常见的功能及UI方面的需求总结起来有如下几点:

(1). 自定义节点高度:这是继承TreeView不能实现的功能

(2). Tree失去焦点后当前节点仍然需要突出显示:C# WinForm中无此功能

(3). 自定义展开闭合图标、节点图标,自定义节点背景前景

(4). 多列显示

(5). 其他

(6). 原文地址:http://blog.csdn.net/ljfblog


2. 基本思路

如果直接继承TreeView,1.(1)无法实现或很难实现,1.(4)很难实现。

所以这里使用DataGridView来曲线救国,主要要解决的技术问题显而易见,就是节点从属、展开闭合的处理。

本文中的源码不能直接运行,需要您认真查看代码理解来龙去脉后替换通用函数和资源文件。而且本文中的代码也只是给个思路,细节和功能带完善,同时也未免会有疏漏Bug。


3. 关键问题

3.1 DataGridView共享行的处理

为了提高运行效率,微软使用共享行来节省内存开销。比如我们插入了一个节点,运行时你会发现它的Index是-1,也没有列信息。这也是LDataGridViewTreeViewNode中会有ListedRow和NodeRow属性的原因。


3.2 节点从属

这里仿照TreeView,使用LDataGridViewTreeViewNode和LDataGridViewTreeViewNodeCollection两个主要的类来实现。参考.Net4.5.1 TreeView源码第148行,我们也使用了一个辅助的根节点来减少处理过程中的例外。当然,要使用递归调用来解决所有父子节点的遍历问题。相关代码见4节。


3.3 展开闭合的处理

每个节点有三个状态:展开显示的、展开但隐藏的、闭合的。在展开闭合的函数中处理,使展开闭合逻辑大体和TreeView相似。本文中的代码在初始状态是全部显示的,需要读者进一步完善。在LDataGridViewTreeViewNode中实现,相关代码见4节。


3.4 绘制

使用了DataGridView绘制不是什么问题,有了3.2,3.3的基础,对缩进、图标、文字的绘制都很容易实现。在LDataGridViewTreeView中实现,相关代码见4节。


3.5 大量数据显示

大量数据显示,本控件不会有太高的效率,如果非使用不可,请异步处理(比如默认所有根节点是闭合的,实际闭合在里面的只有一个“正在读取”的节点),注意释放内存。


3.6 事件处理

本文源码没有实现诸如NodeExpanded,TextChanged之类的事件处理。请自行修改。


3.7 高分屏

如果不考虑高分屏处理,请替换相关函数成像素数字,如LCom.PointToPixelH(20)


4.关键源码

    public class LDataGridViewTreeViewNode:DataGridViewRow
    {
        #region Class
        public enum VisibleState
        {
            ExpandVisible,
            ExpandHidden,
            Collapse
        }
        #endregion

        #region Fileds


        private LDataGridViewTreeView treeView;
        private LDataGridViewTreeViewNode parent;
        private LDataGridViewTreeViewNodeCollection nodes;
        private string name = "";
        private int nodeIndex=-1;
        private string text="";
        private VisibleState nodeVisibleState;
        private Font font;
        private int nodeHeight;
        private Image image;
        private Color backColor = Color.Empty;
        private Color selectionBackColor = Color.Empty;
        private Color selectionForeColor = Color.Empty;
        private Color foreColor = Color.Empty;

        #endregion Fileds

        #region Constructor

        public LDataGridViewTreeViewNode()
        {
            this.LDataGridViewTreeViewNode_Init("", "");
        }

        public LDataGridViewTreeViewNode(string name,string text)
        {
            this.LDataGridViewTreeViewNode_Init(name, text);
        }
        internal LDataGridViewTreeViewNode(LDataGridViewTreeView treeView)
        {
            this.treeView = treeView;
            this.LDataGridViewTreeViewNode_Init("","");
        }
        private void LDataGridViewTreeViewNode_Init(string name, string text)
        {
            this.name = name;
            this.text = text;
        }

        #endregion

        #region Public Properties

        public int Level
        {
            get
            {
                if (this.parent == null) return -1;
                return this.parent.Level+1;
            }
        }
        public string Name
        {
            get
            {
                return name;
            }
            set
            {
                this.name = value;
            }
        }
        public string Text
        {
            get
            {
                return this.text;
            }
            set
            {
                this.text = value;
                if(this.TreeView != null && this.ListedRow != null)
                    this.ListedRow.Cells[this.TreeView.NodeTextColumnName].Value = this.text;
            }
        }
        public LDataGridViewTreeViewNodeCollection Nodes
        {
            get
            {
                if(this.nodes== null)
                {
                    this.nodes = new LDataGridViewTreeViewNodeCollection(this);
                }
                return this.nodes;
            }
        }
        public LDataGridViewTreeViewNode Parent
        {
            get { return this.parent; }
            internal set
            {
                this.parent = value;
            }
        }
        public int NodeIndex
        {
            get
            {
                return this.nodeIndex;
            }
            internal set
            {
                this.nodeIndex = value;
            }
        }
        public int ListedIndex
        {
            get { return this.ListedRow==null?-1: this.ListedRow.Index; }
        }
        public LDataGridViewTreeView TreeView
        {
            get
            {
                if(this.treeView == null)
                {
                    LDataGridViewTreeViewNode node = this;
                    while (node.parent != null)
                        node = node.parent;
                    this.treeView = node.treeView;
                }
                return treeView;
            }
        }
        public bool IsExpanded
        {
            get
            {
                if (this.Nodes.Count == 0) return true;
                return this.Nodes[0].nodeVisibleState != VisibleState.Collapse;
            }
        }
        public LDataGridViewTreeViewNode ListedRow
        {
            get;
            internal set;
        }
        public LDataGridViewTreeViewNode NodeRow
        {
            get;
            private set;
        }
        public int NodeHeight
        {
            get { return this.nodeHeight; }
            set
            {
                this.nodeHeight = value;
                if (this.TreeView != null)
                {
                    if (this.nodeHeight <= 0)
                    {
                        this.ListedRow.Height = this.TreeView.RowTemplate.Height;
                    }
                    else
                    {
                        this.ListedRow.Height = this.nodeHeight;
                    }
                }
            }
        }
        internal Rectangle LevelSquare(Rectangle rLevel)
        {
            Rectangle rLevelSquare = rLevel;
            rLevelSquare.X = rLevel.Right - this.TreeView.LevelIndentWidth;
            rLevelSquare.Width = this.TreeView.LevelIndentWidth;
            return rLevelSquare;
        }
        internal Rectangle LevelSquare()
        {
            return this.LevelSquare(this.LevelRectangle);
        }
        public VisibleState NodeVisibleState
        {
            get
            {
                return this.nodeVisibleState;
            }
            private set
            {
                this.nodeVisibleState = value;
                if (this.nodeVisibleState == VisibleState.ExpandVisible)
                {
                    if(this.ListedRow.Visible==false)
                        this.ListedRow.Visible = true;
                }
                else if(this.ListedRow.Visible)
                {
                    this.ListedRow.Visible = false;
                }

#if DEBUG
                if(this.TreeView.dgvDebug != null)
                    this.TreeView.dgvDebug.Rows[this.ListedIndex].Cells[0].Value = this.NodeVisibleState.ToString();
#endif
            }
        }
        public Image Image
        {
            get
            {
                return this.image;
            }
            set
            {
                this.image = value;
                this.Invalidate();
            }
        }
        public Color BackColor
        {
            get
            {
                return this.backColor;
            }
            set
            {
                this.backColor = value;
                this.Invalidate();
            }
        }
        public Color SelectionBackColor
        {
            get
            {
                return this.selectionBackColor;
            }
            set
            {
                this.selectionBackColor = value;
                this.Invalidate();
            }
        }

        public Color ForeColor
        {
            get
            {
                return this.foreColor;
            }
            set
            {
                this.foreColor = value;
                this.Invalidate();
            }
        }
        public Color SelectionForeColor
        {
            get
            {
                return this.selectionForeColor;
            }
            set
            {
                this.selectionForeColor = value;
                this.Invalidate();
            }
        }

        private void Invalidate()
        {
            if (this.TreeView != null)
                this.TreeView.Invalidate();
        }

        public Rectangle LevelRectangle
        {
            get
            {
                if (this.ListedRow == null) return Rectangle.Empty;

                Rectangle r = new Rectangle(0, 0, this.TreeView.LevelIndentWidth*(1+this.Level), this.ListedRow.Height);

                return r;

            }
        }
        public bool HasNodes { get; internal set; }
        public Font Font
        {
            get { return this.font; }
            set
            {
                this.font = value;
                if (this.TreeView != null)
                    this.TreeView.Invalidate();
               
            }
        }

        #endregion Public Properties

        #region Public Methods

        public override string ToString()
        {
            return
                "[Name:"+(string.IsNullOrEmpty(this.name) ? "NULL" : this.name) + "]" +
                "[ListedIndex:" + this.ListedIndex + "]" +
                "[NodeIndex:" + this.NodeIndex + "]" +
                "[Level:" + this.Level + "]";
        }
        public void Expand()
        {
            Expand(this);
        }
        public void Collapse()
        {
            Collapse(this);
        }

        #endregion Public Methods

        #region Internal Methods

        internal void AddToGridRows()
        {
            if(this.TreeView != null)
            {
                this.AddToGridRows(this);
            }
        }

        #endregion Internal Methods

        #region Private Methods

        private void Expand(LDataGridViewTreeViewNode node)
        {
            foreach (LDataGridViewTreeViewNode n in node.Nodes)
            {
                if (node.Equals(this))
                {
                    n.NodeVisibleState = VisibleState.ExpandVisible;
                }
                else
                {
                    if(n.Parent.NodeVisibleState == VisibleState.ExpandVisible && 
                        n.NodeVisibleState == VisibleState.ExpandHidden)
                    {
                        n.NodeVisibleState = VisibleState.ExpandVisible;
                    }
                }

                Expand(n);
            }
        }
        private void Collapse(LDataGridViewTreeViewNode node)
        {
            foreach (LDataGridViewTreeViewNode n in node.Nodes)
            {
                if (node.Equals(this))
                {
                    n.NodeVisibleState = VisibleState.Collapse;
                }
                else
                {
                    if (n.NodeVisibleState == VisibleState.ExpandVisible)
                        n.NodeVisibleState = VisibleState.ExpandHidden;
                }
                Collapse(n);
            }
        }
        private void AddToGridRows(LDataGridViewTreeViewNode node)
        {
            int index = GetGridIndex(node);
            if (node.Font == null)
                node.Font = this.TreeView.DefaultCellStyle.Font;

            node.TreeView.Rows.Insert(index, node);
            node.ListedRow = node.TreeView.Rows[index] as LDataGridViewTreeViewNode;
            node.ListedRow.NodeRow = node;

            node.ListedRow.Name = node.name+"_L";
            node.NodeVisibleState = VisibleState.ExpandVisible;
            node.NodeHeight = node.nodeHeight;

            for (int i = 0; i < node.Nodes.Count; i++)
            {
                LDataGridViewTreeViewNode sn = node.Nodes[i];
                sn.treeView = this.TreeView;
                AddToGridRows(sn);
            }
        }

        private bool GetNodeVisible(LDataGridViewTreeViewNode node)
        {
            bool v = true;
            LDataGridViewTreeViewNode n = node;
            while (n.Parent != null)
            {
                if (n.Parent.IsExpanded == false)
                {
                    v = false;
                    break;
                }
                n = n.parent;
            }
            return v;
        }
        private int GetGridIndex(LDataGridViewTreeViewNode node)
        {
            LDataGridViewTreeViewNode parentNode = node.Parent;
            int c = parentNode.ListedIndex+1;
            for(int i = 0; i < node.NodeIndex; i++)
            {
                int nc = GetAllFollowNodesCount(parentNode.Nodes[i]);
                c += nc+1;
            }
            return c;
        }
        private int GetAllFollowNodesCount(LDataGridViewTreeViewNode node)
        {
            int c = 0;
            GetAllFollowNodesCount(node, ref c);
            return c;
        }
        private void GetAllFollowNodesCount(LDataGridViewTreeViewNode node, ref int count)
        {
            count += node.Nodes.Count;
            foreach (LDataGridViewTreeViewNode n in node.Nodes)
            {
                GetAllFollowNodesCount(n, ref count);
            }
        }
        
        #endregion Private Methods
    }
    public class LDataGridViewTreeViewNodeCollection:List
    {
        private LDataGridViewTreeViewNode owner;
        public LDataGridViewTreeViewNodeCollection(LDataGridViewTreeViewNode owner)
        {
            this.owner = owner;
        }
        public new void Add(LDataGridViewTreeViewNode node)
        {
            base.Add(node);
            node.Parent=this.owner;
            node.NodeIndex=this.Count - 1;
            node.AddToGridRows();
            owner.HasNodes = true;
        }
        public new void AddRange(IEnumerable< LDataGridViewTreeViewNode> nodes)
        {
            for(int i = 0; i < nodes.Count(); i++)
            {
                this.Add(nodes.ElementAt(i));
            }
        }
    }
    public class LDataGridViewTreeView:LDataGridView
    {
        private LDataGridViewTreeViewNode rootNode;
        private static StringFormat sfmt = null;

        public LDataGridView dgvDebug;
        private int levelIndentWidth=40;

        public LDataGridViewTreeView()
        {
            if(sfmt == null)
            {
                sfmt = new StringFormat();
                sfmt.Alignment = StringAlignment.Near;
                sfmt.LineAlignment = StringAlignment.Center;
            }
            this.rootNode = new LDataGridViewTreeViewNode(this);
            this.RootNode.Name = "__RootNode__";
        }

        internal LDataGridViewTreeViewNode RootNode
        {
            get { return this.rootNode; }
        }
        public LDataGridViewTreeViewNodeCollection Nodes
        {
            get
            {
                return this.rootNode.Nodes;
            }
        }

        public string NodeTextColumnName { get; set; }
        public int LevelIndentWidth
        {
            get { return this.levelIndentWidth; }
            set { this.levelIndentWidth = value; this.Invalidate(); }
        }
        public Image ImageExpandedArrow { get; set; }
        public Image ImageCollapseArrow { get; set; }
        public Image ImageNoChildArrow { get; set; }


        protected override void OnColumnAdded(DataGridViewColumnEventArgs e)
        {
            base.OnColumnAdded(e);
            e.Column.SortMode = DataGridViewColumnSortMode.NotSortable;
        }
        protected override void OnCellMouseDoubleClick(DataGridViewCellMouseEventArgs e)
        {
            base.OnCellMouseDoubleClick(e);
            if(e.RowIndex >= 0 && e.ColumnIndex >= 0)
            {
                LDataGridViewTreeViewNode n = this.Rows[e.RowIndex] as LDataGridViewTreeViewNode;
                n = n.NodeRow;
                if (n.IsExpanded)
                    n.Collapse();
                else
                    n.Expand();
            }
        }
        protected override void OnCellMouseClick(DataGridViewCellMouseEventArgs e)
        {
            base.OnCellMouseClick(e);
            if(e.RowIndex>=0 && e.ColumnIndex >=0 && e.Button == MouseButtons.Left)
            {
                DataGridViewColumn col = this.Columns[e.ColumnIndex];
                if (col.Name == this.NodeTextColumnName)
                {
                    LDataGridViewTreeViewNode node = this.Rows[e.RowIndex] as LDataGridViewTreeViewNode;
                    node = node.NodeRow;
                    if (node.LevelSquare().Contains(e.Location))
                    {
                        if (node.IsExpanded)
                            node.Collapse();
                        else
                            node.Expand();
                    }
                }

            }
        }
        protected override void OnCellPainting(DataGridViewCellPaintingEventArgs e)
        {
            base.OnCellPainting(e);
            if(e.RowIndex>=0 && e.ColumnIndex >= 0)
            {
                if(this.Columns[e.ColumnIndex].Name == this.NodeTextColumnName)
                {
                    this.OnCellPainting(e,e.RowIndex,e.ColumnIndex);
                }

            }
        }
        private void OnCellPainting(DataGridViewCellPaintingEventArgs e,int row,int col)
        {
            LDataGridViewTreeViewNode node = this.Rows[e.RowIndex] as LDataGridViewTreeViewNode;
            node = node.NodeRow;

            Rectangle r = e.CellBounds;
            Rectangle rText = r;
            Graphics g = e.Graphics;
            g.PageUnit = GraphicsUnit.Pixel;

            bool selected = (e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected;
            Color foreColor = selected ?
               (node.SelectionForeColor == Color.Empty ? this.DefaultCellStyle.SelectionForeColor : node.SelectionForeColor)
                : (node.ForeColor == Color.Empty ? this.DefaultCellStyle.ForeColor : node.ForeColor);

            Color backColor = selected ?
               (node.SelectionBackColor == Color.Empty ? this.DefaultCellStyle.SelectionBackColor : node.SelectionBackColor)
                : (node.BackColor == Color.Empty ? this.DefaultCellStyle.BackColor : node.BackColor);

            e.PaintBackground(e.ClipBounds, selected);
            g.FillRectangle(new SolidBrush(backColor), r);

            Rectangle rLevel = node.LevelRectangle;
            rLevel.Location = r.Location;

            Rectangle rLevelSquare = node.LevelSquare(rLevel);
            Image levelImage = node.HasNodes?(node.IsExpanded?this.ImageExpandedArrow:this.ImageCollapseArrow):this.ImageNoChildArrow;
            if(levelImage != null)
            {
                g.DrawImage(levelImage, new PointF(rLevelSquare.X + (rLevelSquare.Width- levelImage.Width) / 2, rLevelSquare.Y + (rLevelSquare.Height-levelImage.Height) / 2));
            }
            rText.X = rLevel.Right;
            rText.Width = r.Width - rLevel.Width;

            if (node.Image != null)
            {
                Rectangle rImage = rLevelSquare;
                rImage.X += rImage.Width;
                g.DrawImage(node.Image, new PointF(rImage.X + (rImage.Width - node.Image.Width) / 2, rImage.Y + (rImage.Height - node.Image.Height) / 2));

                rText.X = rImage.Right;
                rText.Width = r.Width - rLevel.Width - rImage.Width;
            }
                        

            g.DrawString(node.Text, node.Font, new SolidBrush(foreColor), rText, sfmt);

            e.Handled = true;
        }

    }
    public partial class FormLDataGridViewTreeView : Form
    {
        public FormLDataGridViewTreeView()
        {
            InitializeComponent();

            this.tv.NodeTextColumnName = "Title";
            this.tv.RowTemplate.Height = LCom.PointToPixelH(20);
            int imageHeight = this.tv.RowTemplate.Height - this.tv.RowTemplate.Height / 3;
            this.tv.LevelIndentWidth = imageHeight;

            this.tv.ImageCollapseArrow = LRes.GetImage(imageHeight, LImages.ArrowToRight);
            this.tv.ImageExpandedArrow = LRes.GetImage(imageHeight, LImages.ArrowToDown);

            this.tv.Columns.Add("Title", "Title");
            this.tv.Columns[0].Width = this.tv.Width / 2;

            LDataGridViewTreeViewNode node1 = new LDataGridViewTreeViewNode("Node1","01");
            node1.Image = LRes.GetImage(imageHeight, LImages.CreateFile);
            node1.BackColor = Color.LightGray;
            node1.SelectionBackColor = Color.Gray;
            node1.ForeColor = Color.Black;
            node1.SelectionForeColor = Color.White;

            node1.Font = Skin.FormFontBold;
            node1.NodeHeight = LCom.PointToPixelH(35);

            LDataGridViewTreeViewNode node1_1 = new LDataGridViewTreeViewNode("Node1-1", "01-01");
            LDataGridViewTreeViewNode node1_2 = new LDataGridViewTreeViewNode("Node1-2", "01-02");
            LDataGridViewTreeViewNode node1_3 = new LDataGridViewTreeViewNode("Node1-3", "01-03");
            node1.Nodes.Add(node1_1);
            node1.Nodes.Add(node1_2);
            node1.Nodes.Add(node1_3);
            this.tv.Nodes.Add(node1);

            LDataGridViewTreeViewNode node2 = new LDataGridViewTreeViewNode("Node2", "02");
            node2.Image = LRes.GetImage(imageHeight, LImages.Form);
            this.tv.Nodes.Add(node2);

            LDataGridViewTreeViewNode node3 = new LDataGridViewTreeViewNode("Node3", "03");
            node3.Image = LRes.GetImage(imageHeight, LImages.Home);
            node3.BackColor = Color.LightPink;
            node3.SelectionBackColor = Color.Pink;

            node3.Collapse();

            LDataGridViewTreeViewNode node3_1 = new LDataGridViewTreeViewNode("Node3-1", "03-01");
            LDataGridViewTreeViewNode node3_2 = new LDataGridViewTreeViewNode("Node3-2", "03-02");
            LDataGridViewTreeViewNode node3_3 = new LDataGridViewTreeViewNode("Node3-3", "03-03");

            node3_3.Collapse();

            LDataGridViewTreeViewNode node3_3_1 = new LDataGridViewTreeViewNode("Node3-3-1", "03-03_01");
            LDataGridViewTreeViewNode node3_3_2 = new LDataGridViewTreeViewNode("Node3-3-2", "03-03_02");
            LDataGridViewTreeViewNode node3_3_3 = new LDataGridViewTreeViewNode("Node3-3-3", "03-03_03");

            LDataGridViewTreeViewNode node3_3_3_1 = new LDataGridViewTreeViewNode("Node3-3-3-1", "03-03_03-1");
            LDataGridViewTreeViewNode node3_3_3_2 = new LDataGridViewTreeViewNode("Node3-3-3-2", "03-03_03-2");
            LDataGridViewTreeViewNode node3_3_3_3 = new LDataGridViewTreeViewNode("Node3-3-3-3", "03-03_03-3");
            node3_3_3.Nodes.AddRange(new LDataGridViewTreeViewNode[] { node3_3_3_1, node3_3_3_2, node3_3_3_3 });

            LDataGridViewTreeViewNode node3_3_4 = new LDataGridViewTreeViewNode("Node3-3-4", "03-03_04");
            node3_3.Nodes.AddRange(new LDataGridViewTreeViewNode[] { node3_3_1, node3_3_2, node3_3_3, node3_3_4 });

            LDataGridViewTreeViewNode node3_4 = new LDataGridViewTreeViewNode("Node3-4", "03-04");
            LDataGridViewTreeViewNode node3_5 = new LDataGridViewTreeViewNode("Node3-5", "03-05");
            LDataGridViewTreeViewNode node3_6 = new LDataGridViewTreeViewNode("Node3-6", "03-06");
            node3.Nodes.AddRange(new LDataGridViewTreeViewNode[] { node3_1 , node3_2, node3_3, node3_4, node3_5, node3_6 });
            this.tv.Nodes.Add(node3);

            LDataGridViewTreeViewNode node4 = new LDataGridViewTreeViewNode("Node4", "04");
            node4.Image = LRes.GetImage(imageHeight, LImages.SendReceive);

            this.tv.Nodes.Add(node4);


            this.dgvDebug.Columns.Add("NodeVisibleState", "NodeVisibleState");
            for(int i = 0; i < this.tv.Rows.Count; i++)
            {
                this.dgvDebug.Rows.Add();
            }
            this.tv.dgvDebug = this.dgvDebug;
        }
    }


5. 效果图
C# WinForm 自绘TreeView_第1张图片

你可能感兴趣的:(C#,WinForm,TreeView)