利用TreeView实现C#工具箱效果

 最近看到不少程序、网页都有类似C#工具箱的效果,恰好新写一个进销存系统,也想使用这种效果,于是花了点时间仔细研究了一下。

  C#中并没有现存的控件可用,仔细观察C#工具箱的效果,开始设想用Graphics对象自绘,利用容器控件(GroupBox,Panel等等)做隐藏显示等功能,都觉得太麻烦。再看工具箱,除了外观以外,分明就是一个TreeView的基本功能。何不看看C#中TreeView控件新增了哪些东西。

  C#的TreeView新增了一个DrawNodo属性,看了一下文档,发现这个属性还比较熟悉,应该是和Win32 API中有关Comm32控件组DLL中的某些回调函数类似。以前曾经在VB中使用DLL自绘TreeView、ListView等控件,在C#中应该更容易。

  折腾了一番,终于完成了全部功能,记录下过程。

  先看DrawNode属性,C#文档中说明为:

   
  Normal TreeView 由操作系统绘制。 
  OwnerDrawAll TreeView 节点的所有元素均为手动绘制,包括图标、复选框、加号和减号以及连接节点的线。 
  OwnerDrawText TreeView 节点的标签部分均为手动绘制。其他节点元素由操作系统绘制,包括图标、复选框、加号和减号以及连接节点的线。 

  只要把属性设置为OwnerDrawAll,即可完全自绘节点外观。
       相关属性——DrawNode事件参数DrawTreeNodeEventArgs,事件参数中包含绘制节点的Graphics对象,节点边界Bounds属性,可以根据此属性获得要绘制的节点在TreeView控件中的坐标及大小。State属性,返回要绘制的节点状态,把它和枚举TreeNodeStates中的成员按位运算,即可获得要绘制的节点的当前状态。

  新建C# Windows应用程序,添加TreeView控件,命名为treeViewMenu,把DrawNode属性设为OwnerDrawAll,由于需要点击节点所在行即获得NodeClick行为,因此把FullRowSelect设为true,ShowLine设为false(当ShowLine属性为true时,FullRowSelect属性被忽略)。
  //增加节点,递归调用
       //table 来自数据库中设定好的菜单模块,结构为ID——节点代码,Name——节点名称,Parent——父节点代码
       //调用入口为:AddNote(treeViewMenu.Nodes,"0",table)
        private void AddNode(TreeNodeCollection nodes,string parent,DataTable table)
        {
            DataRow[] rows = table.Select("MainMenu='"+parent+"'");
            if (rows.Length == 0) return;
            for (int i = 0; i < rows.Length; i++)
            {
                TreeNode node = nodes.Add(rows[i]["Name"].ToString());
                node.Name = rows[i]["ID"].ToString();
                this.AddNode(node.Nodes, node.Name, table);
            }
        }

       节点的DrawNode事件代码
        private void treeViewMenu_DrawNode(object sender, DrawTreeNodeEventArgs e)
        {
            if (e.Node.Level == 0)      //如果是根节点,给节点画一个背景,并使用稍大的字体
            {
                Rectangle imgBounds = new Rectangle(new Point(0, e.Bounds.Top), new Size(treeViewMenu.Width, 18));
                Point textPoint = new Point(imgBounds.Left + 16, imgBounds.Top + 1);  //节点文本左上角坐标,预留了节点前加减号的位置。
                e.Graphics.DrawImage(nodeBg, imgBounds);      //画根节点背景。nodeBg是一个Bitmap对象,存放节点的背景图片,图片的高度应与节点的ItemHeight属性对应,我使用的图片为200*20,因此ItemHeight属性设为20。
                Pen pen = new Pen(Brushes.Blue);      //根节点字体颜色
                e.Graphics.DrawRectangle(pen, imgBounds.X + 4, imgBounds.Y + 2, 10, 10);
                e.Graphics.DrawLine(pen, imgBounds.X + 6, imgBounds.Top + 7, imgBounds.Left + 12, imgBounds.Top + 7);  //这两个语句画节点展开后的减号,
                if (!e.Node.IsExpanded)
                    e.Graphics.DrawLine(pen, imgBounds.X + 9, imgBounds.Top + 4, imgBounds.Left + 9, imgBounds.Top + 10);  //如果节点未展开,则在减号中添加一条线,变成加号
                e.Graphics.DrawString(e.Node.Text, new Font("宋体", 10), Brushes.Blue, textPoint);  //字体大小为11磅。
            }
            else
            {
                //画子节点,当AddNode方法执行完成后,DrawNode事件第一次触发,此时子节点并未显示,因此DrawNode不会绘制子节点。当首次展开一个根节点时,触发事件并执行下列代码,此时e.Bounds为空,无需绘制子节点。
                if (!e.Bounds.IsEmpty)      //如果子节点的Bounds属性不为空(Empty),绘制该节点。
                {
                    Point textPoint = new Point(e.Bounds.Left + 16, e.Bounds.Top + 4);  //子节点文本坐标
                    Pen pen;
                    Brush brush;
                    Rectangle box = new Rectangle(new Point(e.Bounds.Left, e.Bounds.Top), new Size(e.Bounds.Width - 1, e.Bounds.Height - 1));  //当鼠标在子节点上移动时,显示一个带颜色的方框。要达到此效果,需将节点的HotTranking属性设为true。
                    Rectangle fill = new Rectangle(e.Bounds.Left+1,e.Bounds.Top+1,e.Bounds.Width-2,e.Bounds.Height-2); //填充区域,比方框小一个象素。
                    if ((e.State & TreeNodeStates.Hot) != 0)   //判断鼠标指针是否在该节点上。
                    {
                         //定义方框的边框颜色和填充颜色
                        pen = new Pen(new SolidBrush(Color.FromArgb(49, 106, 197)));
                        brush = new SolidBrush(Color.FromArgb(193, 210, 238));
                    }
                    else
                    {
                         //使用背景色擦除之前所画的方框。
                        brush = new SolidBrush(treeViewMenu.BackColor);
                        pen = new Pen(new SolidBrush(treeViewMenu.BackColor));
                    }
                    e.Graphics.DrawRectangle(pen, box);
                    e.Graphics.FillRectangle(brush, fill);
                   //如果节点处于选中状态,绘制一个不同颜色的方框。
                    if ((e.State & TreeNodeStates.Selected) != 0)
                    {
                        brush = new SolidBrush(Color.FromArgb(49, 106, 197));
                        e.Graphics.FillRectangle(brush, fill);
                    }
                   //绘制子节点文本。
                    e.Graphics.DrawString(e.Node.Text, new Font("宋体", 9), Brushes.Black, textPoint);
                }
            }
        }

       节点绘制完成,我们还需要做一些善后,使它更加类似C#工具箱的效果。
       首先是单击展开节点,由于FullRowSelect设置为true,因此在节点行的任意位置单击,都将触发NodeMouseClick事件,而不仅仅是在标签上单击才触发该事件,这样我们可以在该事件达到我们想要的效果。
        private void treeViewMenu_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            //单击展开或收起节点
            if (e.Node.IsExpanded)
                e.Node.Collapse();
            else
                e.Node.Expand();
        }
       最后别忘了,把TreeView的属性ShowPlusMinus属性设置为false,即不显示控件本身在节点前的加减号。虽然节点是自绘的,看不见系统加上去的加减号,但鼠标点击该位置,仍然会使节点展开或收起,这和我们自己处理的单击展开收起节点会有冲突。

你可能感兴趣的:(treeview)