using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Runtime;
using System.Windows.Forms;
namespace Dongke.WinForm.Controls
{
#region 枚举
///
/// NodeLineType enumeration
/// Type of dotted line to draw when drawing a node.
///
internal enum NodeLineType
{
None,
Straight,
WithChildren
}
#endregion
internal class TriStateTreeNode : TreeNode
{
#region 成员变量
///
/// 节点选中状态
///
private CheckState _nodeCheckState = CheckState.Unchecked;
///
/// checkbox是否显示
///
private bool _checkboxVisible = true;
///
/// 是否为容器
///
private bool _isContainer = false;
#endregion
#region 构造函数
// public constructor
public TriStateTreeNode() : base() { }
public TriStateTreeNode(string text) : base(text) { }
public TriStateTreeNode(string text, int imageIndex, int selectedImageIndex) : base(text, imageIndex, selectedImageIndex) { }
public TriStateTreeNode(string text, int imageIndex, int selectedImageIndex, TriStateTreeNode[] children) : base(text, imageIndex, selectedImageIndex, children) { }
public TriStateTreeNode(string text, TriStateTreeNode[] children) : base(text, children) { }
#endregion
#region 属性
///
/// Get / set if the node is checked or not.
///
[Browsable(false)]
new public bool Checked
{
get { return (this._nodeCheckState != CheckState.Unchecked); }
set { SetCheckedState(value ? CheckState.Checked : CheckState.Unchecked); }
}
public bool CheckboxVisible
{
get { return this._checkboxVisible; }
set { this._checkboxVisible = value; }
}
internal NodeLineType NodeLineType
{
get
{
// is this node bound to a treeview ?
if (null != this.TreeView)
{
// do we need to draw lines at all?
if (!this.TreeView.ShowLines) { return NodeLineType.None; }
if (this.CheckboxVisible) { return NodeLineType.None; }
if (this.Nodes.Count > 0)
{
return NodeLineType.WithChildren;
}
return NodeLineType.Straight;
}
// no treeview so this node will never been drawn at all
return NodeLineType.None;
}
}
///
/// Get's the node's current state, either checked / unchecked for non-container nodes,
/// or checked / unchecked / indeterminate for container nodes.
///
public CheckState CheckState
{
get { return this._nodeCheckState; }
}
internal void SetCheckedState(CheckState value)
{
CheckStateChanged(_nodeCheckState, value);
}
///
/// Determines if the node should act as a container
///
public bool IsContainer
{
get { return _isContainer; }
set { _isContainer = value; }
}
#endregion
#region 私有方法
///
/// This method is only called when a node's state has been changed through the
/// Checked property
///
/// Previous state
/// New state
private void CheckStateChanged(CheckState oldState, CheckState newState)
{
// States are equal?
if (newState != oldState)
{
// not equal.
// modify state
this._nodeCheckState = newState;
// change state of the children
if (this.Nodes != null && this.Nodes.Count > 0)
{
foreach (TreeNode node in this.Nodes)
{
TriStateTreeNode tsNode = node as TriStateTreeNode;
if (tsNode != null)
{
tsNode.ChangeChildState(newState);
}
}
}
// notify the parent of the changed state.
if (this.Parent != null)
{
TriStateTreeNode parentNode = this.Parent as TriStateTreeNode;
if (parentNode != null)
{
parentNode.ChildCheckStateChanged(this._nodeCheckState);
}
}
}
}
///
/// This method is only called by other nodes so it can be private.
/// Changes state of the node to the state provided.
///
///
private void ChangeChildState(CheckState newState)
{
// change state
this._nodeCheckState = newState;
// change state on the children
if (this.Nodes != null && this.Nodes.Count > 0)
{
foreach (TreeNode node in this.Nodes)
{
TriStateTreeNode tsNode = node as TriStateTreeNode;
if (tsNode != null)
{
tsNode.ChangeChildState(newState);
}
}
}
}
///
/// Private method that is called by one of the childnodes in order to report a
/// change in state.
///
/// New state of the childnode in question
private void ChildCheckStateChanged(CheckState childNewState)
{
bool notifyParent = false;
CheckState currentState = this._nodeCheckState;
CheckState newState = this._nodeCheckState;
// take action based on the child's new state
switch (childNewState)
{
case CheckState.Indeterminate: // child state changed to indeterminate
// if one of the children's state changes to indeterminate,
// it's parent should do the same, if it is a container too!.
if (IsContainer)
{
newState = CheckState.Indeterminate;
// the same is valid for this node's parent.
// check if this node's state has changed and inform the parent
// if this is the case
notifyParent = (newState != currentState);
}
break;
case CheckState.Checked:
// One of the child nodes was checked so we must check:
// 1) if the child node is the only child node, our state becomes checked too.
// 2) if there are children with a state other then checked, our state
// must become indeterminate if this is a container.
if (this.Nodes.Count == 1) // if there is only one child, our state changes too!
{
// set our state to checked too and set the flag for
// parent notification.
newState = CheckState.Checked;
notifyParent = true;
break;
}
// set to checked by default
// if this is not a container, there is no need to check further
newState = CheckState.Checked;
if (!IsContainer)
{
notifyParent = (newState != currentState);
break;
}
// traverse all child nodes to see if there are any with a state other then
// checked. if so, change state to indeterminate.
foreach (TreeNode node in this.Nodes)
{
TriStateTreeNode checkedNode = node as TriStateTreeNode;
if (checkedNode != null && checkedNode.CheckState != CheckState.Checked)
{
newState = CheckState.Indeterminate;
break;
}
}
// set notification flag if our state has to be changed too
notifyParent = (newState != currentState);
break;
case CheckState.Unchecked:
// For nodes that are no containers, a child being unchecked is not relevant.
// so we can exit at this point.
if (!IsContainer)
break;
// A child's state has changed to unchecked so check:
// 1) if this is the only child. if so, uncheck this node too, if it is a container, and set
// notification flag for the parent.
// 2) Check if there are child nodes with a state other then unchecked.
// if so, change our state to indeterminate.
if (this.Nodes.Count == 1)
{
// synchronize state with only child.
// set notification flag
newState = CheckState.Unchecked;
notifyParent = true;
break;
}
// set to unchecked by default
newState = CheckState.Unchecked;
// if there is a child with a state other then unchecked,
// our state must become indeterminate.
foreach (TreeNode node in this.Nodes)
{
TriStateTreeNode checkedNode = node as TriStateTreeNode;
if (checkedNode != null && checkedNode.CheckState != CheckState.Unchecked)
{
newState = CheckState.Indeterminate;
break;
}
}
// notify the parent only if our state is about to be changed too.
notifyParent = (newState != currentState);
break;
}
// should we notify the parent? ( has our state changed? )
if (notifyParent)
{
// change state
this._nodeCheckState = newState;
// notify parent
if (this.Parent != null)
{
TriStateTreeNode parentNode = this.Parent as TriStateTreeNode;
if (parentNode != null)
{
// call the same method on the parent.
parentNode.ChildCheckStateChanged(this._nodeCheckState);
}
}
}
}
#endregion
}
}