zoukankan      html  css  js  c++  java
  • 利用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,即不显示控件本身在节点前的加减号。虽然节点是自绘的,看不见系统加上去的加减号,但鼠标点击该位置,仍然会使节点展开或收起,这和我们自己处理的单击展开收起节点会有冲突。

  • 相关阅读:
    JSP标签介绍
    JSP四大作用域属性范围
    JSP九大内置对象及四个作用域
    maven:Fatal error compiling: 无效的目标发行版: 1.8.0_45 -> [Help 1]
    浅谈Session与Cookie的区别与联系
    Servlet入门实践
    安卓常用布局基本属性
    安卓常用布局
    Android开发中Handler的经典总结
    三种方法写监听事件
  • 原文地址:https://www.cnblogs.com/zhwl/p/2809733.html
Copyright © 2011-2022 走看看