今天我在试着作一个C#树形视图(TreeView)控件,要求在每一个节点前面添加一个可用于打勾的复选框,并要求复选框有上下级联动的效果。如今在网上能查到挺多知足这类功能的代码,本来我也觉得这是一件挺简单的事情,不过实际状况并不是如此。node
咱们创建一个C#窗体应用程序,主窗体取名FormMain,在里面放置一个Dock为Fill的TreeView控件treeTest。注意该控件的CheckBoxes属性要设置为True才能显示复选框。
c#
在FormMain中写入代码以下:windows
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TreeViewCheckTest { public partial class FormMain : Form { public FormMain() { InitializeComponent(); } private void FormMain_Load(object sender, EventArgs e) { //生成测试数据 TreeNode treeNode11 = new TreeNode("蜉蝣目"); treeNode11.Nodes.Add("等蜉科"); treeNode11.Nodes.Add("四节蜉科"); treeNode11.Nodes.Add("扁蜉科"); treeNode11.Nodes.Add("蜉蝣科"); treeNode11.Nodes.Add("河花蜉科"); TreeNode treeNode12 = new TreeNode("蜚蠊目"); treeNode12.Nodes.Add("姬蠊科"); treeNode12.Nodes.Add("硕蠊科"); treeNode12.Nodes.Add("地鳖科"); treeNode12.Nodes.Add("隐尾蠊科"); TreeNode treeNode13 = new TreeNode("螳螂目"); treeNode13.Nodes.Add("螳科"); treeNode13.Nodes.Add("花螳科"); treeNode13.Nodes.Add("锥头螳科"); treeNode13.Nodes.Add("细足螳科"); TreeNode treeNode14 = new TreeNode("其余类型昆虫"); TreeNode treeNode1 = new TreeNode("昆虫纲"); treeNode1.Nodes.AddRange( new TreeNode[] { treeNode11, treeNode12, treeNode13, treeNode14 }); treeTest.Nodes.Add(treeNode1); treeTest.ExpandAll(); } /// <summary> /// 关联锁 /// </summary> bool needSetRelateCheck = true; /// <summary> /// 树节点前复选框发生变化时触发 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void treeViewEnhanced1_AfterCheck(object sender, TreeViewEventArgs e) { if (!needSetRelateCheck) { return; } //一、判断当前操做节点勾仍是不勾 //若是勾,当前节点下全部子节点都要勾上 //若是不勾,下面子节点所有不勾 TreeNode node = e.Node; if (node.Checked) { node.Nodes.OfType<TreeNode>().ToList().ForEach(x => x.Checked = true); } else { node.Nodes.OfType<TreeNode>().ToList().ForEach(x => x.Checked = false); } //二、若是当前节点被勾选,若是当前节点被勾选,则其上溯全部祖先节点都要勾 //不然判断当前节点全部兄弟节点是否有勾,有则父节点要勾,没有则父节点不勾 needSetRelateCheck = false; //为本方法上锁,确保连带影响不会运行本事件中代码 if (node.Checked) { while (node.Parent != null) { node = node.Parent; node.Checked = true; } } else { while (node.Parent != null) { node = node.Parent; bool hasCheckedChild = false; foreach (TreeNode child in node.Nodes) { if (child.GetHashCode() == e.Node.GetHashCode()) { continue; } if (child.Checked) { hasCheckedChild = true; break; } } if (!hasCheckedChild) { node.Checked = false; } else { break; } } } needSetRelateCheck = true; //为本方法解锁 } } }
原则上这段代码知足如下功能:ide
勾选一个节点时,该节点的全部子节点都被勾选函数
取消勾选一个节点时,该节点的全部子节点都被取消勾选测试
勾选一个节点时,若是该节点的全部兄弟都被勾选,则该节点的父节点也应被勾选spa
取消勾选一个节点时,若是该节点的全部兄弟节点都未被勾选,则该节点的父节点也应被取消勾选code
运行后效果以下:orm
原本觉得这样就行了,结果发生了意想不到的事情:事件
在我用鼠标点击一个复选框时,若是点击间隔时间太短(达到相似双击的速度),则TreeView的复选框会显示出现没法正确联动的问题。
后来我上网查了一些资料,终于找到了一个大牛给出的缘由,参见:
大牛的答案中说这是一个WindowsVista版本的BUG,不过我如今的32位Win7也会出现此问题。
解决这个问题的方法是将TreeView封装一层,并重写一下WndProc函数:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms; namespace TreeViewCheckTest { class TreeViewEnhanced : TreeView { protected override void WndProc(ref Message m) { if (m.Msg == 0x203) { m.Result = IntPtr.Zero; } else base.WndProc(ref m); } } }
实现了TreeViewEnhanced类后,将FormMain中的TreeView控件替换为咱们刚刚实现的TreeViewEnhanced就好啦!
END