需求

在项目中需要对TreeView进行定制,要求比较简单,主要要求如下:

  1.  每个节点被造中时可以有自己的事件
  2. Winform中TreeView控件默认只支持树级别的CheckBox,再要求支持节点级别的CheckBox/RadioButton,即能为每个节点设置支不支持选择,选择的样式是复选还是单选。
  3. Winform中TreeView控件节点的默认显示样式是“CheckBox"+ “图片”+“节点文字”,现要求互换“CheckBox”和“图片”的位置,显示样式改为“图片”+“CheckBox”+“节点文字”。

总体显示效果如下图:

treeselect 选项被挡 treeview设置选中节点_System

设计思路

TreeView控件有DrawMode属 性,我们需要定义一个继承自TreeView的类(这里叫GTreeView吧),并在GTreeView的构造函数里设置DrawMode =TreeViewDrawMode .OwnerDrawText,以告诉系统我们需要自己绘制文字。然后在GTreeView的DrawNode事件中根据GTreeNode的 CheckBoxVisible,CheckBoxStyle属性来先绘制选择框,这里的选择框实际上是一个图片,根据属性的不同决定是不绘制还是绘制 RadioButtonChecked/RadioButtonUnchecked/CheckBoxChecked /CheckBoxUnchecked中的一个,绘制完图片后再绘制节点文本。

GTreeNode继承于TreeNode,主要添加了CheckBoxVisible, CheckBoxStyle属性和NodeSelected事件,新增加属性和事件的用途见名知义,不多解释。

Override TreeView的MouseUp事件,判断如果Mouse落后在选择框图片内,则改变节点的选中状态并重绘树,需要注意如果是单选节点的话还需要清空兄弟节点的选中状态,因为同一个父节点下只允许一个单选节点为选中状态。

Override TreeView的AfterSelected事件,如果当前选中的节点有NodeSelected EventHanlder则直接调用并返回,否则调用AfterSelected的默认处理。

代码


using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using CustomTreeView.Properties;

namespace GADPlatSystem.Tools
{
///<summary>
/// 自定义TreeView控件,支持节点级别的复选、单选;
/// 节点显示样式改为Icon+CheckBox+NodeText的样式
///</summary>
publicclass GTreeView : TreeView
    {
#region Private Variables

privateconstint checkBoxImageWidth =16;
privateconstint checkBoxImageHeight =16;
private Image imgChecked;
private Image imgUnchecked;
private Image imgRBChecked;
private Image imgRBUnchecked;
private System.ComponentModel.IContainer components; 
#endregion

#region Constructs
public GTreeView()
        {
            InitializeComponent();

// 初始化CheckBox相关的图片
            imgUnchecked = Resource.TreeNodeUnchecked;
            imgChecked = Resource.TreeNodeChecked;
            imgRBUnchecked = Resource.TreeNodeRBUnchecked;
            imgRBChecked = Resource.TreeNodeRBChecked;

// 设置TreeView为自己绘制文本和图标并绑定相关的事件
this.DrawMode = TreeViewDrawMode.OwnerDrawText;
this.DrawNode +=new DrawTreeNodeEventHandler(GTreeView_DrawNode);
this.MouseUp +=new MouseEventHandler(GTreeView_MouseUp);
        }

#endregion

#region Event Hanlder Methods

///<summary>
/// 判断如果点击的是Checkbox图片,则改变节点的IsCheck属性值
/// 需要注意的是同一父节点下只允许一个RadioButton类型的节点为选中状态
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
void GTreeView_MouseUp(object sender, MouseEventArgs e)
        {
            GTreeNode node = GetNodeAt(e.X, e.Y) as GTreeNode;
if (node ==null||!node.CheckBoxVisible)
            {
return;
            }
            Rectangle checkboxImgRect =new Rectangle(node.Bounds.X, node.Bounds.Y, checkBoxImageWidth, checkBoxImageHeight);     //节点区域

// 如果点击的是checkbox图片
if (checkboxImgRect.Contains(e.X, e.Y))
            {
                node.Checked =!node.Checked;

// 如果是单选,则设置同级别的其它单选项为unchecked.
if (node.Parent !=null&& node.CheckBoxStyle == GTreeNode.CheckBoxStyleEnum.RadioButton)
                {
foreach (TreeNode siblingNode in node.Parent.Nodes)
                    {
                        var siblingGNode = siblingNode as GTreeNode;
if (siblingGNode ==null)
                        {
continue;
                        }
if (siblingGNode.Name != node.Name && siblingGNode.CheckBoxStyle == GTreeNode.CheckBoxStyleEnum.RadioButton && siblingGNode.Checked)
                        {
                            siblingGNode.Checked =false;
                        }

                    }
                }
            }
        }

///<summary>
/// 绘制节点的图标和文字,样式为Icon+CheckBox+NodeText
/// Checkbox支持节点级别的单选、复选
///</summary>
///<param name="sender"></param>
///<param name="e"></param>
void GTreeView_DrawNode(object sender, DrawTreeNodeEventArgs e)
        {
            GTreeNode node = e.Node as GTreeNode;
if (node ==null)
            {
return;
            }

            Rectangle nodeRect = e.Node.Bounds;    

// 如果需要显示CheckBox,则绘制
if (node.CheckBoxVisible)
            {
                Point drawPt =new Point(nodeRect.Location.X, nodeRect.Location.Y);     //绘制图标的起始位置
                Size imgSize =new Size(checkBoxImageWidth, checkBoxImageHeight);        //图片大小
                Rectangle imgRect =new Rectangle(drawPt, imgSize);

if (e.Node.Checked)
                {
if (node.CheckBoxStyle == GTreeNode.CheckBoxStyleEnum.CheckBox)
                    {
                        e.Graphics.DrawImage(imgChecked, imgRect);
                    }
else
                    {
                        e.Graphics.DrawImage(imgRBChecked, imgRect);

                    }
                }
else
                {
if (node.CheckBoxStyle == GTreeNode.CheckBoxStyleEnum.CheckBox)
                    {
                        e.Graphics.DrawImage(imgUnchecked, imgRect);
                    }
else
                    {
                        e.Graphics.DrawImage(imgRBUnchecked, imgRect);
                    }
                }
            }



//-----------------------绘制文本 -------------------------------
            Rectangle textRec;
if (node.CheckBoxVisible)
            {
                textRec =new Rectangle(nodeRect.X + checkBoxImageWidth +2, nodeRect.Y, nodeRect.Width, nodeRect.Height);
            }
else
            {
                textRec = nodeRect;
            }

            Font nodeFont = e.Node.NodeFont;
if (nodeFont ==null)
                nodeFont = ((TreeView)sender).Font;
            Brush textBrush = SystemBrushes.WindowText;

if ((e.State & TreeNodeStates.Focused) !=0)
            {
                e.Graphics.FillRectangle(SystemBrushes.Highlight, textRec);
            }
            e.Graphics.DrawString(e.Node.Text, nodeFont, textBrush, Rectangle.Inflate(textRec, 2, 0));


        }

///<summary>
/// 节点被选中后,如果节点有事件处理程序,则调用 
///</summary>
///<param name="e"></param>
protectedoverridevoid OnAfterSelect(TreeViewEventArgs e)
        {
            var gNode = e.Node as GTreeNode;
if (gNode !=null&& gNode.NodeSelected !=null)
            {
                TreeViewEventArgs arg =new TreeViewEventArgs(e.Node);
                gNode.NodeSelected(this, arg);
return;
            }
base.OnAfterSelect(e);
        }

#endregion

#region Private Methods
privatevoid InitializeComponent()
        {
this.SuspendLayout();
// 
// GTreeView
//
this.LineColor = System.Drawing.Color.Black;
this.ResumeLayout(false);

        } 
#endregion

    }
}