TriStateTreeNode.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. 
  2. using System;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Drawing;
  6. using System.Drawing.Design;
  7. using System.Runtime;
  8. using System.Windows.Forms;
  9. namespace Dongke.WinForm.Controls
  10. {
  11. #region 枚举
  12. /// <summary>
  13. /// NodeLineType enumeration
  14. /// Type of dotted line to draw when drawing a node.
  15. /// </summary>
  16. internal enum NodeLineType
  17. {
  18. None,
  19. Straight,
  20. WithChildren
  21. }
  22. #endregion
  23. internal class TriStateTreeNode : TreeNode
  24. {
  25. #region 成员变量
  26. /// <summary>
  27. /// 节点选中状态
  28. /// </summary>
  29. private CheckState _nodeCheckState = CheckState.Unchecked;
  30. /// <summary>
  31. /// checkbox是否显示
  32. /// </summary>
  33. private bool _checkboxVisible = true;
  34. /// <summary>
  35. /// 是否为容器
  36. /// </summary>
  37. private bool _isContainer = false;
  38. #endregion
  39. #region 构造函数
  40. // public constructor
  41. public TriStateTreeNode() : base() { }
  42. public TriStateTreeNode(string text) : base(text) { }
  43. public TriStateTreeNode(string text, int imageIndex, int selectedImageIndex) : base(text, imageIndex, selectedImageIndex) { }
  44. public TriStateTreeNode(string text, int imageIndex, int selectedImageIndex, TriStateTreeNode[] children) : base(text, imageIndex, selectedImageIndex, children) { }
  45. public TriStateTreeNode(string text, TriStateTreeNode[] children) : base(text, children) { }
  46. #endregion
  47. #region 属性
  48. /// <summary>
  49. /// Get / set if the node is checked or not.
  50. /// </summary>
  51. [Browsable(false)]
  52. new public bool Checked
  53. {
  54. get { return (this._nodeCheckState != CheckState.Unchecked); }
  55. set { SetCheckedState(value ? CheckState.Checked : CheckState.Unchecked); }
  56. }
  57. public bool CheckboxVisible
  58. {
  59. get { return this._checkboxVisible; }
  60. set { this._checkboxVisible = value; }
  61. }
  62. internal NodeLineType NodeLineType
  63. {
  64. get
  65. {
  66. // is this node bound to a treeview ?
  67. if (null != this.TreeView)
  68. {
  69. // do we need to draw lines at all?
  70. if (!this.TreeView.ShowLines) { return NodeLineType.None; }
  71. if (this.CheckboxVisible) { return NodeLineType.None; }
  72. if (this.Nodes.Count > 0)
  73. {
  74. return NodeLineType.WithChildren;
  75. }
  76. return NodeLineType.Straight;
  77. }
  78. // no treeview so this node will never been drawn at all
  79. return NodeLineType.None;
  80. }
  81. }
  82. /// <summary>
  83. /// Get's the node's current state, either checked / unchecked for non-container nodes,
  84. /// or checked / unchecked / indeterminate for container nodes.
  85. /// </summary>
  86. public CheckState CheckState
  87. {
  88. get { return this._nodeCheckState; }
  89. }
  90. internal void SetCheckedState(CheckState value)
  91. {
  92. CheckStateChanged(_nodeCheckState, value);
  93. }
  94. /// <summary>
  95. /// Determines if the node should act as a container
  96. /// </summary>
  97. public bool IsContainer
  98. {
  99. get { return _isContainer; }
  100. set { _isContainer = value; }
  101. }
  102. #endregion
  103. #region 私有方法
  104. /// <summary>
  105. /// This method is only called when a node's state has been changed through the
  106. /// Checked property
  107. /// </summary>
  108. /// <param name="oldState">Previous state</param>
  109. /// <param name="newState">New state</param>
  110. private void CheckStateChanged(CheckState oldState, CheckState newState)
  111. {
  112. // States are equal?
  113. if (newState != oldState)
  114. {
  115. // not equal.
  116. // modify state
  117. this._nodeCheckState = newState;
  118. // change state of the children
  119. if (this.Nodes != null && this.Nodes.Count > 0)
  120. {
  121. foreach (TreeNode node in this.Nodes)
  122. {
  123. TriStateTreeNode tsNode = node as TriStateTreeNode;
  124. if (tsNode != null)
  125. {
  126. tsNode.ChangeChildState(newState);
  127. }
  128. }
  129. }
  130. // notify the parent of the changed state.
  131. if (this.Parent != null)
  132. {
  133. TriStateTreeNode parentNode = this.Parent as TriStateTreeNode;
  134. if (parentNode != null)
  135. {
  136. parentNode.ChildCheckStateChanged(this._nodeCheckState);
  137. }
  138. }
  139. }
  140. }
  141. /// <summary>
  142. /// This method is only called by other nodes so it can be private.
  143. /// Changes state of the node to the state provided.
  144. /// </summary>
  145. /// <param name="newState"></param>
  146. private void ChangeChildState(CheckState newState)
  147. {
  148. // change state
  149. this._nodeCheckState = newState;
  150. // change state on the children
  151. if (this.Nodes != null && this.Nodes.Count > 0)
  152. {
  153. foreach (TreeNode node in this.Nodes)
  154. {
  155. TriStateTreeNode tsNode = node as TriStateTreeNode;
  156. if (tsNode != null)
  157. {
  158. tsNode.ChangeChildState(newState);
  159. }
  160. }
  161. }
  162. }
  163. /// <summary>
  164. /// Private method that is called by one of the childnodes in order to report a
  165. /// change in state.
  166. /// </summary>
  167. /// <param name="childNewState">New state of the childnode in question</param>
  168. private void ChildCheckStateChanged(CheckState childNewState)
  169. {
  170. bool notifyParent = false;
  171. CheckState currentState = this._nodeCheckState;
  172. CheckState newState = this._nodeCheckState;
  173. // take action based on the child's new state
  174. switch (childNewState)
  175. {
  176. case CheckState.Indeterminate: // child state changed to indeterminate
  177. // if one of the children's state changes to indeterminate,
  178. // it's parent should do the same, if it is a container too!.
  179. if (IsContainer)
  180. {
  181. newState = CheckState.Indeterminate;
  182. // the same is valid for this node's parent.
  183. // check if this node's state has changed and inform the parent
  184. // if this is the case
  185. notifyParent = (newState != currentState);
  186. }
  187. break;
  188. case CheckState.Checked:
  189. // One of the child nodes was checked so we must check:
  190. // 1) if the child node is the only child node, our state becomes checked too.
  191. // 2) if there are children with a state other then checked, our state
  192. // must become indeterminate if this is a container.
  193. if (this.Nodes.Count == 1) // if there is only one child, our state changes too!
  194. {
  195. // set our state to checked too and set the flag for
  196. // parent notification.
  197. newState = CheckState.Checked;
  198. notifyParent = true;
  199. break;
  200. }
  201. // set to checked by default
  202. // if this is not a container, there is no need to check further
  203. newState = CheckState.Checked;
  204. if (!IsContainer)
  205. {
  206. notifyParent = (newState != currentState);
  207. break;
  208. }
  209. // traverse all child nodes to see if there are any with a state other then
  210. // checked. if so, change state to indeterminate.
  211. foreach (TreeNode node in this.Nodes)
  212. {
  213. TriStateTreeNode checkedNode = node as TriStateTreeNode;
  214. if (checkedNode != null && checkedNode.CheckState != CheckState.Checked)
  215. {
  216. newState = CheckState.Indeterminate;
  217. break;
  218. }
  219. }
  220. // set notification flag if our state has to be changed too
  221. notifyParent = (newState != currentState);
  222. break;
  223. case CheckState.Unchecked:
  224. // For nodes that are no containers, a child being unchecked is not relevant.
  225. // so we can exit at this point.
  226. if (!IsContainer)
  227. break;
  228. // A child's state has changed to unchecked so check:
  229. // 1) if this is the only child. if so, uncheck this node too, if it is a container, and set
  230. // notification flag for the parent.
  231. // 2) Check if there are child nodes with a state other then unchecked.
  232. // if so, change our state to indeterminate.
  233. if (this.Nodes.Count == 1)
  234. {
  235. // synchronize state with only child.
  236. // set notification flag
  237. newState = CheckState.Unchecked;
  238. notifyParent = true;
  239. break;
  240. }
  241. // set to unchecked by default
  242. newState = CheckState.Unchecked;
  243. // if there is a child with a state other then unchecked,
  244. // our state must become indeterminate.
  245. foreach (TreeNode node in this.Nodes)
  246. {
  247. TriStateTreeNode checkedNode = node as TriStateTreeNode;
  248. if (checkedNode != null && checkedNode.CheckState != CheckState.Unchecked)
  249. {
  250. newState = CheckState.Indeterminate;
  251. break;
  252. }
  253. }
  254. // notify the parent only if our state is about to be changed too.
  255. notifyParent = (newState != currentState);
  256. break;
  257. }
  258. // should we notify the parent? ( has our state changed? )
  259. if (notifyParent)
  260. {
  261. // change state
  262. this._nodeCheckState = newState;
  263. // notify parent
  264. if (this.Parent != null)
  265. {
  266. TriStateTreeNode parentNode = this.Parent as TriStateTreeNode;
  267. if (parentNode != null)
  268. {
  269. // call the same method on the parent.
  270. parentNode.ChildCheckStateChanged(this._nodeCheckState);
  271. }
  272. }
  273. }
  274. }
  275. #endregion
  276. }
  277. }