三态 TreeView - 第二部分






4.85/5 (14投票s)
支持 3 态逻辑的带复选框的 TreeView

引言
我寻找带有三态复选框的Treeview
,然后找到了这篇文章。它正确地解释了逻辑,并承诺了第 2 部分,其中应该解决关于所有者绘制复选框的问题。
但是,由于某种原因,第 2 部分从未被编写。但是作者允许观众自由编写第 2 部分。所以我感到很自由。 ;)
重复逻辑
用户只能选中或取消选中Treenode
s - 不能设置为不确定状态。 选中/取消选中一个节点会将所有子节点设置为该新状态。 如果ParentNode
包含不同状态的节点,那么它将显示不确定状态。
代码
原理是使用带有 3 个图像的StateImageList
属性:Unchecked
,Checked
,Indeterminate
。 双重逻辑由Treeview
很好地完成。 Treeview
正确使用前两个图像来显示Checked
/Unchecked
。 尽管如此,我还是仔细设置了正确的StateImageIndices
,尽管这不是必需的(对于双重逻辑)。
但是我需要它来保持 3 种状态。 当需要绘制时,我只需要绘制不确定的Checkbox
。
一个问题是,我需要使用TreeViewDrawMode.OwnerDrawAll
来确定要绘制的节点。 但我不想完全绘制节点,因为这非常困难(Checkbox
,可选图标,SelectedIcon,Text,SelectedText,Focus)。 我只想在必要时添加我不确定的Checkbox
。
不幸的是,DrawMode.OwnerDrawAll
禁用了_Paint
事件,并且没有“AfterDrawNode
”事件。 所以我不得不对窗口消息进行子类化,观察WM_PAINT
窗口消息何时传递。 在那一刻,我可以绘制我不确定的Checkbox
,它们不会被Treeview
覆盖绘制。
所以在这里你可以看到ThreeStateTreeview
最重要的部分,我希望它被注释得足够好,以至于更多的解释是多余的。
protected override void OnAfterCheck(TreeViewEventArgs e) {
/* Logic: All children of an (un)checked Node inherit its Checkstate
* Parents recompute their state: if all children of a parent have same state,
* that one will be taken over as parents state - otherwise take Indeterminate
*/
if(_skipCheckEvents) return;/* changing any Treenodes .Checked-Property will raise
another Before- and After-Check. Skip'em */
_skipCheckEvents = true;
try {
TreeNode nd = e.Node;
/* uninitialized Nodes have StateImageIndex -1,
* so I associate StateImageIndex as follows:
* -1: Unchecked
* 0: Checked
* 1: Indeterminate
* That corresponds to the System.Windows.Forms.Checkstate - enumeration,
* but 1 less.
* Furthermore I ordered the images in that manner
*/
int state = nd.StateImageIndex == 0 ? -1 : 0; /* this state is already toggled.
Note: -1 (Unchecked) and 1 (Indeterminate) both toggle to 0,
that means: Checked */
if((state == 0) != nd.Checked) return; //suppress redundant AfterCheck-event
InheritCheckstate(nd, state); // inherit Checkstate to children
// Parents recompute their state
nd = nd.Parent;
while(nd != null) {
// At Indeterminate (==1) skip the children-query -
// every parent becomes Indeterminate
if(state != 1) {
foreach(TreeNode ndChild in nd.Nodes) {
if(ndChild.StateImageIndex != state) {
state = 1;
break;
}
}
}
AssignState(nd, state);
nd = nd.Parent;
}
base.OnAfterCheck(e);
} finally { _skipCheckEvents = false; }
}
private void AssignState(TreeNode nd, int state) {
bool ck = state == 0;
bool stateInvalid = nd.StateImageIndex != state;
if(stateInvalid) nd.StateImageIndex = state;
if(nd.Checked != ck) {
nd.Checked = ck; // changing .Checked-Property raises
// Invalidating internally
} else if(stateInvalid) {
// in general: the less and small the invalidated area, the less flickering
// so avoid calling Invalidate() if possible, and only call, if really needed.
this.Invalidate(GetCheckRect(nd));
}
}
private void InheritCheckstate(TreeNode nd, int state) {
AssignState(nd, state);
foreach(TreeNode ndChild in nd.Nodes) {
InheritCheckstate(ndChild, state);
}
}
public System.Windows.Forms.CheckState GetState(TreeNode nd) {
// compute the System.Windows.Forms.CheckState from a StateImageIndex
// is not that complicated
return (CheckState)nd.StateImageIndex + 1;
}
protected override void OnDrawNode(DrawTreeNodeEventArgs e) {
// here nothing is drawn. Only collect Indeterminated Nodes,
// to draw them later (in WndProc())
// because drawing Treenodes properly (Text, Icon(s) Focus, Selection...)
// is very complicated
if(e.Node.StateImageIndex == 1) _indeterminateds.Add(e.Node);
e.DrawDefault = true;
base.OnDrawNode(e);
}
protected override void WndProc(ref Message m) {
const int WM_Paint = 15;
base.WndProc(ref m);
if(m.Msg == WM_Paint) {
// at that point built-in drawing is completed -
// and I paint over the Indeterminate-Checkboxes
foreach(TreeNode nd in _indeterminateds) {
_graphics.DrawImage(_imgIndeterminate, GetCheckRect(nd).Location);
}
_indeterminateds.Clear();
}
}
致谢
- 三态树形控件 - 第 1 部分 - 虽然我没有使用该代码中的任何一行,但它给了我一个关于如何将
Checked
属性与StateImageIndex
的 3 个选项同步的想法,以及如何在更新Treenode
状态时避免多次 Before-/After-Check-Events。
历史
- 2009 年 4 月 1 日:首次发布
- 2010 年 5 月 18 日:Bug 修复:Christo667 报告了一个隐藏很好的 bug,当以编程方式将节点的
Checked
属性设置为与其之前相同的值时(请参见消息板)。 该错误的根本原因是,在这种情况下,常见的TreeView
会引发冗余的 Before-/After-Checked-Events,而ThreeStateTreeview
会切换节点外观,即使它不应该这样做。
现在ThreeStateTreeview
抑制了这些冗余事件。 这也可能是对常见TreeView
的 bug 解决方法。
谢谢你,Christo! - 修复了双击节点复选框时 Standard-Treeview 的错误。