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