利用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,即不顯示控件本身在節點前的加減號。雖然節點是自繪的,看不見系統加上去的加減號,但鼠標點擊該位置,仍然會使節點展開或收起,這和我們自己處理的單擊展開收起節點會有沖突。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM