哇咔咔,好长时间没来博客园写点东西了,主要是感觉水平有限没啥可写的,而最近做一个项目也感觉没多少自己原创的东西,想想最近这个项目中唯一值得一写的东西就是研究了个winform的三态树,遂想把源码贴上来分享一下。三态树就是子节点全部选中、部分选中、全部未选中时父节点有三种选中状态,windows系统的好多功能都用这种三态树,但是让人郁闷的是winform却没有直接提供这种控件,只提供了一个很普通的treeview。严格说来这个也不算我的原创,我只是从codeproject上面找到一个源码,并修复了一些bug以及根据自己的需求修改了部分代码,但是考虑到当时我中文搜索并没有搜索到自己想要的,而是通过google英文搜索才找到的,所以觉得还是有必要来贴一下这个三态树。顺便赞一下google的英文搜索,解决了我的好多技术问题,而百度一般是搜出来一堆垃圾。细节实现就不说了,大家直接看代码吧,看懂应该是没问题的,当初我也是看着代码琢磨的,完全忽略了人家好心写的一些英文注释。不过还是大体说一下,这个就是重写了treeview,成了一个自定义控件,并且自己定义了一个属性,就是可以选择是否要作为三态树使用,所以这个是完全可以当初普通树用的,使用的话就是修改一下命名空间将代码文件加到项目,编译一下便生成了自定义控件了,拖动即可使用。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Windows.Forms.VisualStyles; using System.Data; using DevExpress.XtraEditors; namespace NXADMSLIB.Lib_GIS { public delegate void EventCheckHandler(EventCheckArg e); /// <summary> /// Provides a tree view /// control supporting /// tri-state checkboxes. /// </summary> public class TriStateTreeView : TreeView { public event EventCheckHandler NodeChecked; // ~~~ fields ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ImageList _ilStateImages; bool _bUseTriState; bool _bCheckBoxesVisible; bool _bPreventCheckEvent; public bool PreventCheckEvent { set { _bPreventCheckEvent = value; } get { return _bPreventCheckEvent; } } // ~~~ constructor ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// <summary> /// Creates a new instance /// of this control. /// </summary> public TriStateTreeView() : base() { CheckBoxState cbsState; Graphics gfxCheckBox; Bitmap bmpCheckBox; _ilStateImages = new ImageList(); // first we create our state image cbsState = CheckBoxState.UncheckedNormal; // list and pre-init check state. for (int i = 0; i <= 2; i++) { // let's iterate each tri-state bmpCheckBox = new Bitmap(16, 16); // creating a new checkbox bitmap gfxCheckBox = Graphics.FromImage(bmpCheckBox); // and getting graphics object from switch (i) { // it... case 0: cbsState = CheckBoxState.UncheckedNormal; break; case 1: cbsState = CheckBoxState.CheckedNormal; break; case 2: cbsState = CheckBoxState.MixedNormal; break; } CheckBoxRenderer.DrawCheckBox(gfxCheckBox, new Point(2, 2), cbsState); // ...rendering the checkbox and... gfxCheckBox.Save(); _ilStateImages.Images.Add(bmpCheckBox); // ...adding to sate image list. _bUseTriState = true; } } // ~~~ properties ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// <summary> /// Gets or sets to display /// checkboxes in the tree /// view. /// </summary> [Category("Appearance")] [Description("Sets tree view to display checkboxes or not.")] [DefaultValue(false)] public new bool CheckBoxes { get { return _bCheckBoxesVisible; } set { _bCheckBoxesVisible = value; base.CheckBoxes = _bCheckBoxesVisible; this.StateImageList = _bCheckBoxesVisible ? _ilStateImages : null; } } [Browsable(false)] public new ImageList StateImageList { get { return base.StateImageList; } set { base.StateImageList = value; } } /// <summary> /// Gets or sets to support /// tri-state in the checkboxes /// or not. /// </summary> [Category("Appearance")] [Description("Sets tree view to use tri-state checkboxes or not.")] [DefaultValue(true)] public bool CheckBoxesTriState { get { return _bUseTriState; } set { _bUseTriState = value; } } // ~~~ functions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /// <summary> /// Refreshes this /// control. /// </summary> public override void Refresh() { Stack<TreeNode> stNodes; TreeNode tnStacked; base.Refresh(); if (!CheckBoxes) // nothing to do here if return; // checkboxes are hidden. base.CheckBoxes = false; // hide normal checkboxes... stNodes = new Stack<TreeNode>(this.Nodes.Count); // create a new stack and foreach (TreeNode tnCurrent in this.Nodes) // push each root node. stNodes.Push(tnCurrent); while (stNodes.Count > 0) { // let's pop node from stack, tnStacked = stNodes.Pop(); // set correct state image if (tnStacked.StateImageIndex == -1) // index if not already done tnStacked.StateImageIndex = tnStacked.Checked ? 1 : 0; // and push each child to stack for (int i = 0; i < tnStacked.Nodes.Count; i++) // too until there are no stNodes.Push(tnStacked.Nodes[i]); // nodes left on stack. } } protected override void OnLayout(LayoutEventArgs levent) { base.OnLayout(levent); Refresh(); } protected override void OnAfterExpand(TreeViewEventArgs e) { base.OnAfterExpand(e); foreach (TreeNode tnCurrent in e.Node.Nodes) // set tree state image if (tnCurrent.StateImageIndex == -1) // to each child node... tnCurrent.StateImageIndex = tnCurrent.Checked ? 1 : 0; } protected override void OnAfterCheck(TreeViewEventArgs e) { base.OnAfterCheck(e); if (_bPreventCheckEvent) return; OnNodeMouseClick(new TreeNodeMouseClickEventArgs(e.Node, MouseButtons.None, 0, 0, 0)); } protected override void OnNodeMouseClick(TreeNodeMouseClickEventArgs e) { Stack<TreeNode> stNodes; TreeNode tnBuffer; bool bMixedState; int iSpacing; int iIndex; base.OnNodeMouseClick(e); _bPreventCheckEvent = true; iSpacing = ImageList == null ? 0 : 18; // if user clicked area if ((e.X > e.Node.Bounds.Left - iSpacing || // *not* used by the state e.X < e.Node.Bounds.Left - (iSpacing + 16)) && // image we can leave here. e.Button != MouseButtons.None) { return; } tnBuffer = e.Node; // buffer clicked node and if (e.Button == MouseButtons.Left) // flip its check state. tnBuffer.Checked = !tnBuffer.Checked; tnBuffer.StateImageIndex = tnBuffer.Checked ? // set state image index 1 : tnBuffer.StateImageIndex;// tnBuffer.StateImageIndex; // correctly. OnAfterCheck(new TreeViewEventArgs(tnBuffer, TreeViewAction.ByMouse)); stNodes = new Stack<TreeNode>(tnBuffer.Nodes.Count); // create a new stack and stNodes.Push(tnBuffer); // push buffered node first. do { // let's pop node from stack, tnBuffer = stNodes.Pop(); // inherit buffered node's tnBuffer.Checked = e.Node.Checked; tnBuffer.StateImageIndex = tnBuffer.Checked ? // set state image index 1 : 0;// tnBuffer.StateImageIndex; // correctly.// check state and push for (int i = 0; i < tnBuffer.Nodes.Count; i++) // each child on the stack stNodes.Push(tnBuffer.Nodes[i]); // until there is no node } while (stNodes.Count > 0); // left. bMixedState = false; tnBuffer = e.Node; // re-buffer clicked node. while (tnBuffer.Parent != null) { // while we get a parent we foreach (TreeNode tnChild in tnBuffer.Parent.Nodes) // determine mixed check states bMixedState |= (tnChild.Checked != tnBuffer.Checked | // and convert current check tnChild.StateImageIndex == 2); // state to state image index. iIndex = (int)Convert.ToUInt32(tnBuffer.Checked); // set parent's check state and tnBuffer.Parent.Checked = bMixedState || (iIndex > 0); // state image in dependency if (bMixedState) // of mixed state. tnBuffer.Parent.StateImageIndex = CheckBoxesTriState ? 2 : 1; else tnBuffer.Parent.StateImageIndex = iIndex; tnBuffer = tnBuffer.Parent; // finally buffer parent and bMixedState = false; } // loop here. _bPreventCheckEvent = false; //将点击事件传达以对地图做进一步操作 //this.NodeChecked(new EventCheckArg(e.Node)); if (this.NodeChecked != null) { this.NodeChecked(new EventCheckArg(e.Node)); } } //三态树的初始构建 public void CreateTree(DataTable table, string id, string name, string pid) { bool bMixedState = false; TreeNode tnBuffer; TreeNode firstChild; int iIndex; if (table == null) { XtraMessageBox.Show("空树"); return; } this.Nodes.Clear(); this.PreventCheckEvent = true;//设置节点checked状态时不触发点击事件 foreach (DataRow row in table.Rows) { if (row[pid].ToString() == string.Empty) { TreeNode root = new TreeNode(); root.Tag = row; root.Text = row[name].ToString(); this.Nodes.Add(root); CreateChildren(root, table, id, name, pid); tnBuffer = root; if (tnBuffer.Nodes.Count == 0)//没有子节点,直接确定状态 { iIndex = (int)Convert.ToUInt32(tnBuffer.Checked); tnBuffer.StateImageIndex = iIndex; } else//有子节点,根据子节点状态确定状态 { firstChild = tnBuffer.Nodes[0]; bMixedState = false; foreach (TreeNode tnChild in tnBuffer.Nodes) // determine mixed check states { bMixedState = (tnChild.Checked != firstChild.Checked | // and convert current check tnChild.StateImageIndex == 2); // state to state image index. if (bMixedState) break; } iIndex = (int)Convert.ToUInt32(firstChild.Checked); // set parent's check state and tnBuffer.Checked = bMixedState || (iIndex > 0); // state image in dependency if (bMixedState) // of mixed state. tnBuffer.StateImageIndex = CheckBoxesTriState ? 2 : 1; else tnBuffer.StateImageIndex = iIndex; } } } } //递归构建子树 private void CreateChildren(TreeNode pNode, DataTable table, string id, string name, string pid) { bool bMixedState = false; TreeNode tnBuffer; TreeNode firstChild; int iIndex; foreach (DataRow row in table.Rows) { if (row[pid].ToString() == (pNode.Tag as DataRow)["id"].ToString()) { TreeNode node = new TreeNode(); node.Tag = row; node.Text = row[name].ToString(); if (row["visible"] == DBNull.Value)//不是叶子节点 { pNode.Nodes.Add(node); } else { node.Checked = row["visible"].ToString() == "1"; pNode.Nodes.Add(node); } CreateChildren(node, table, id, name, pid); tnBuffer = node; if (tnBuffer.Nodes.Count == 0)//没有子节点,直接确定状态 { iIndex = (int)Convert.ToUInt32(tnBuffer.Checked); tnBuffer.StateImageIndex = iIndex; } else//有子节点,根据子节点状态确定状态 { firstChild = tnBuffer.Nodes[0]; bMixedState = false; foreach (TreeNode tnChild in tnBuffer.Nodes) // determine mixed check states { bMixedState = (tnChild.Checked != firstChild.Checked | // and convert current check tnChild.StateImageIndex == 2); // state to state image index. if (bMixedState) break; } iIndex = (int)Convert.ToUInt32(firstChild.Checked); // set parent's check state and tnBuffer.Checked = bMixedState || (iIndex > 0); // state image in dependency if (bMixedState) // of mixed state. tnBuffer.StateImageIndex = CheckBoxesTriState ? 2 : 1; else tnBuffer.StateImageIndex = iIndex; } } } } } public class EventCheckArg : EventArgs { private TreeNode node; public TreeNode Node { set { node = value; } get { return node; } } public EventCheckArg(TreeNode node) { this.Node = node; } } }