以下是需要解决的主要问题:
-
预防
AfterCkeck
事件处理程序以递归方式重复逻辑。
当你改变的时候
Checked
中节点的属性
AfterCheck
,它导致另一个
事后检查
可能导致堆栈溢出的事件,或者至少在检查事件后不必要的事件,或者算法中不可预测的结果。
-
固定
DoubleClick
在复选框中插入
TreeView
.
当你双击
CheckBox
在里面
树视图
, the
选中的
的值
Node
将更改两次,并在双击前设置为原始状态,但
事后检查
事件将引发一次。
-
获取节点后代和祖先的扩展方法
我们需要创建方法来获取节点的后代和祖先。为此,我们将为
TreeNode
班级。
-
实现算法
在解决了上述问题之后,正确的算法将产生我们所期望的点击结果。期望值如下:
选中/取消选中节点时:
-
该节点的所有后代都应更改为相同的检查状态。
-
如果祖先节点的后代中至少有一个子节点被选中,则应选中该节点中的所有节点;否则,应取消选中该节点。
在我们解决上述问题并创造
Descendants
和
Ancestors
穿过这棵树,我们就够了
事后检查
事件并具有以下逻辑:
e.Node.Descendants().ToList().ForEach(x =>
{
x.Checked = e.Node.Checked;
});
e.Node.Ancestors().ToList().ForEach(x =>
{
x.Checked = x.Descendants().ToList().Any(y => y.Checked);
});
下载
您可以从以下存储库下载一个工作示例:
详细答案
预防
后甲板
递归重复逻辑的事件处理程序
事实上,我们不会停止
AfterCheck
引发的事件处理程序
事后检查
. 相反,我们检测
事后检查
由用户或处理程序内的代码引发。为此,我们可以检查
Action
事件参数的属性:
要防止多次引发事件,请将逻辑添加到
仅在以下情况下执行递归代码的事件处理程序:
行动
的属性
TreeViewEventArgs
未设置为
TreeViewAction.Unknown
.
private void exTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
// Changing Checked
}
}
固定
双击
在复选框中插入
树视图
如中所述
this post
,有一个错误
树视图
,当您双击
复选框
在里面
树视图
, the
选中的
的值
结点
将更改两次,并在双击前设置为原始状态,但
事后检查
事件将引发一次。
为了解决这个问题,你可以
WM_LBUTTONDBLCLK
消息并选中双击复选框,忽略它:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
public class ExTreeView : TreeView
{
private const int WM_LBUTTONDBLCLK = 0x0203;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDBLCLK)
{
var info = this.HitTest(PointToClient(Cursor.Position));
if (info.Location == TreeViewHitTestLocations.StateImage)
{
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
}
获取节点后代和祖先的扩展方法
要获取节点的后代和祖先,我们需要创建一些扩展方法来在
事后检查
要实现算法:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
public static class Extensions
{
public static List<TreeNode> Descendants(this TreeView tree)
{
var nodes = tree.Nodes.Cast<TreeNode>();
return nodes.SelectMany(x => x.Descendants()).Concat(nodes).ToList();
}
public static List<TreeNode> Descendants(this TreeNode node)
{
var nodes = node.Nodes.Cast<TreeNode>().ToList();
return nodes.SelectMany(x => Descendants(x)).Concat(nodes).ToList();
}
public static List<TreeNode> Ancestors(this TreeNode node)
{
return AncestorsInternal(node).ToList();
}
private static IEnumerable<TreeNode> AncestorsInternal(TreeNode node)
{
while (node.Parent != null)
{
node = node.Parent;
yield return node;
}
}
}
实现算法
使用上述扩展方法,我将处理
事后检查
事件,因此当您选中/取消选中节点时:
-
该节点的所有后代都应更改为相同的检查状态。
-
如果在其子代中有一个子代被选中,则应选中祖先中的所有节点,否则应取消选中。
实现方法如下:
private void exTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
e.Node.Descendants().ToList().ForEach(x =>
{
x.Checked = e.Node.Checked;
});
e.Node.Ancestors().ToList().ForEach(x =>
{
x.Checked = x.Descendants().ToList().Any(y => y.Checked);
});
}
}
例子
要测试解决方案,可以填充
树视图
使用以下数据:
private void Form1_Load(object sender, EventArgs e)
{
exTreeView1.Nodes.Clear();
exTreeView1.Nodes.AddRange(new TreeNode[] {
new TreeNode("1", new TreeNode[] {
new TreeNode("11", new TreeNode[]{
new TreeNode("111"),
new TreeNode("112"),
}),
new TreeNode("12", new TreeNode[]{
new TreeNode("121"),
new TreeNode("122"),
new TreeNode("123"),
}),
}),
new TreeNode("2", new TreeNode[] {
new TreeNode("21", new TreeNode[]{
new TreeNode("211"),
new TreeNode("212"),
}),
new TreeNode("22", new TreeNode[]{
new TreeNode("221"),
new TreeNode("222"),
new TreeNode("223"),
}),
})
});
exTreeView1.ExpandAll();
}
.NET 2支持
由于.NET 2没有LINQ扩展方法,对于那些对.NET 2中的功能感兴趣的人(包括原始海报),这里是.NET 2.0中的代码:
外部视图
using System;
using System.Collections.Generic;
using System.Windows.Forms;
public class ExTreeView : TreeView
{
private const int WM_LBUTTONDBLCLK = 0x0203;
protected override void WndProc(ref Message m)
{
if (m.Msg == WM_LBUTTONDBLCLK) {
var info = this.HitTest(PointToClient(Cursor.Position));
if (info.Location == TreeViewHitTestLocations.StateImage) {
m.Result = IntPtr.Zero;
return;
}
}
base.WndProc(ref m);
}
public IEnumerable<TreeNode> Ancestors(TreeNode node)
{
while (node.Parent != null) {
node = node.Parent;
yield return node;
}
}
public IEnumerable<TreeNode> Descendants(TreeNode node)
{
foreach (TreeNode c1 in node.Nodes) {
yield return c1;
foreach (TreeNode c2 in Descendants(c1)) {
yield return c2;
}
}
}
}
选择后
private void exTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown) {
foreach (TreeNode x in exTreeView1.Descendants(e.Node)) {
x.Checked = e.Node.Checked;
}
foreach (TreeNode x in exTreeView1.Ancestors(e.Node)) {
bool any = false;
foreach (TreeNode y in exTreeView1.Descendants(x))
any = any || y.Checked;
x.Checked = any;
};
}
}