以下代码旨在根据需要以递归方式检查或取消检查父节点或子节点。
例如,在此位置,必须取消选中 A , G , L 和 T 节点如果我们取消检查其中任何一个。
以下代码的问题是,每当我双击任何节点时算法都无法实现其目的。
树搜索算法从这里开始:
// stack is used to traverse the tree iteratively.
Stack<TreeNode> stack = new Stack<TreeNode>();
private void treeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
TreeNode selectedNode = e.Node;
bool checkedStatus = e.Node.Checked;
// suppress repeated even firing
treeView1.AfterCheck -= treeView1_AfterCheck;
// traverse children
stack.Push(selectedNode);
while(stack.Count > 0)
{
TreeNode node = stack.Pop();
node.Checked = checkedStatus;
System.Console.Write(node.Text + ", ");
if (node.Nodes.Count > 0)
{
ICollection tnc = node.Nodes;
foreach (TreeNode n in tnc)
{
stack.Push(n);
}
}
}
//traverse parent
while(selectedNode.Parent!=null)
{
TreeNode node = selectedNode.Parent;
node.Checked = checkedStatus;
selectedNode = selectedNode.Parent;
}
// "suppress repeated even firing" ends here
treeView1.AfterCheck += treeView1_AfterCheck;
string str = string.Empty;
}
驱动程序
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
#region MyRegion
private void button1_Click(object sender, EventArgs e)
{
TreeNode a = new TreeNode("A");
TreeNode b = new TreeNode("B");
TreeNode c = new TreeNode("C");
TreeNode d = new TreeNode("D");
TreeNode g = new TreeNode("G");
TreeNode h = new TreeNode("H");
TreeNode i = new TreeNode("I");
TreeNode j = new TreeNode("J");
TreeNode k = new TreeNode("K");
TreeNode l = new TreeNode("L");
TreeNode m = new TreeNode("M");
TreeNode n = new TreeNode("N");
TreeNode o = new TreeNode("O");
TreeNode p = new TreeNode("P");
TreeNode q = new TreeNode("Q");
TreeNode r = new TreeNode("R");
TreeNode s = new TreeNode("S");
TreeNode t = new TreeNode("T");
TreeNode u = new TreeNode("U");
TreeNode v = new TreeNode("V");
TreeNode w = new TreeNode("W");
TreeNode x = new TreeNode("X");
TreeNode y = new TreeNode("Y");
TreeNode z = new TreeNode("Z");
k.Nodes.Add(x);
k.Nodes.Add(y);
l.Nodes.Add(s);
l.Nodes.Add(t);
l.Nodes.Add(u);
n.Nodes.Add(o);
n.Nodes.Add(p);
n.Nodes.Add(q);
n.Nodes.Add(r);
g.Nodes.Add(k);
g.Nodes.Add(l);
i.Nodes.Add(m);
i.Nodes.Add(n);
j.Nodes.Add(b);
j.Nodes.Add(c);
j.Nodes.Add(d);
a.Nodes.Add(g);
a.Nodes.Add(h);
a.Nodes.Add(i);
a.Nodes.Add(j);
treeView1.Nodes.Add(a);
treeView1.ExpandAll();
button1.Enabled = false;
}
#endregion
预计会发生:
查看应用程序的屏幕截图。 A , G , L ,以及<选中strong> T 。如果我取消选中,请 L ,
- T 应取消选中,因为 T 是 L 的孩子即可。
- G 和 A 应该取消选中,因为他们没有孩子。
发生了什么:
如果我单击任何节点,此应用程序代码可以正常工作。如果我双击某个节点,该节点将变为选中/取消选中,但相同的更改不会反映在父节点和子节点上。
双击也会冻结应用程序一段时间。
如何解决此问题并获得预期的行为?
答案 0 :(得分:10)
这些是要解决的主要问题:
防止AfterCkeck
事件处理程序以递归方式重复逻辑。
当您在Checked
中更改节点的AfterCheck
属性时,会导致另一个AfterCheck
事件,这可能导致我们发生堆栈溢出或至少在检查事件后不必要或不可预测的结果我们的算法。
修复DoubleClick
中的复选框错误TreeView
。
当您双击CheckBox
中的TreeView
时,Checked
的{{1}}值将更改两次,并会在双击之前设置为原始状态,但Node
事件将提升一次。
获取节点后代和祖先的扩展方法
我们需要创建方法来获取节点的后代和祖先。为此,我们将为AfterCheck
类创建扩展方法。
实施算法
在解决上述问题后,正确的算法将导致我们期望的点击。这是期望:
检查/取消选中某个节点时:
在我们修复了上述问题并创建TreeNode
和Descendants
来遍历树后,我们就足以处理Ancestors
事件并具有以下逻辑:
AfterCheck
下载强>
您可以从以下存储库下载工作示例:
阻止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
事件处理程序提升AfterCkeck
。相反,我们检测用户或处理程序内的代码是否引发了AfterCheck
。为此,我们可以检查事件arg的AfterCheck
属性:
要防止多次引发事件,请添加逻辑 你的事件处理程序只执行你的递归代码
Action
的{{1}}属性未设置为Action
。
TreeViewEventArgs
修复TreeViewAction.Unknown
private void exTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
{
if (e.Action != TreeViewAction.Unknown)
{
// Changing Checked
}
}
正如this post中所述,DoubleClick
中存在一个错误,当您双击TreeView
中TreeView
的{{1}}值时CheckBox
将更改两次,并在双击前设置为原始状态,但TreeView
事件将提升一次。
要解决此问题,您可以查看Checked
消息并检查是否双击复选框,忽略它:
Node
获取节点后代和祖先的扩展方法
要获取节点的后代和祖先,我们需要创建一些在AfterCheck
中使用的扩展方法来实现该算法:
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);
}
}
事件,因此当您选中/取消选中某个节点时:
以下是实施:
AfterCheck
示例强>
要测试解决方案,您可以使用以下数据填充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;
}
}
}
:
AfterCheck
由于.NET 2没有linq扩展方法,对于那些有兴趣在.NET 2中使用该功能的人(包括原始海报),这里是.NET 2.0中的代码:
<强> ExTreeView 强>
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);
});
}
}
<强> AfterSelect 强>
TreeView