gpt4 book ai didi

c# - WinForms TreeView 检查/取消检查层次结构

转载 作者:可可西里 更新时间:2023-11-01 08:24:20 25 4
gpt4 key购买 nike

以下代码旨在根据需要递归检查或取消检查父节点或子节点。

enter image description here

例如,在这个位置,一个 , G , L , 和 电话 如果我们取消选中其中任何一个节点,则必须取消选中它们。

enter image description here

以下代码的问题是,每当我双击任何节点时,算法都无法实现其目的。

树搜索算法从这里开始:

    // 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

预计发生:

看一下应用程序的屏幕截图。 一个 , G , L , 和 电话 被检查。如果我取消选中,例如 L ,
- 电话 应该取消选中 电话 的 child L .
- G 一个 应该取消检查,因为他们将没有 child 。

发生了什么:

如果我单击任何节点,此应用程序代码工作正常。如果我双击一个节点,该节点将被选中/取消选中,但相同的更改不会反射(reflect)在父节点和子节点上。

双击还会卡住应用程序一段时间。

如何解决此问题并获得预期的行为?

最佳答案

这些是这里要解决的主要问题:

  • 预防 AfterCkeck事件处理程序以递归方式重复逻辑。

    当你改变时Checked AfterCheck 中节点的属性,它导致另一个 AfterCheck在我们的算法中检查事件或不可预测的结果后,可能会导致堆栈溢出或至少不必要的事件。
  • 修复 DoubleClick关于 TreeView 中的复选框错误.

    当您双击 CheckBox 时在 TreeView , Checked Node 的值将更改两次并设置为双击前的原始状态,但 AfterCheck事件将引发一次。
  • 获取节点后代和祖先的扩展方法

    我们需要创建方法来获取节点的后代和祖先。为此,我们将为 TreeNode 创建扩展方法。类。
  • 实现算法

    解决上述问题后,正确的算法将通过点击产生我们期望的结果。这是期望:

    当您选中/取消选中节点时:
  • 该节点的所有后代都应更改为相同的检查状态。
  • 祖先中的所有节点,如果其后代中至少有一个 child ,则应检查,否则,应取消选中。

  • 在我们解决上述问题并创建 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);
    });

    下载

    您可以从以下存储库下载工作示例:
  • r-aghaei/TreeViewCheckUnCheckHierarchyExample
  • Zip File

  • 详细解答

    预防 AfterCkeck递归重复逻辑的事件处理程序

    事实上我们并没有停止 AfterCheck 引发事件处理程序 AfterCheck .相反,我们检测 AfterCheck由用户或我们在处理程序中的代码引发。为此,我们可以查看 Action事件 arg 的属性:

    To prevent the event from being raised multiple times, add logic to your event handler that only executes your recursive code if the Action property of the TreeViewEventArgs is not set to TreeViewAction.Unknown.


    private void exTreeView1_AfterCheck(object sender, TreeViewEventArgs e)
    {
    if (e.Action != TreeViewAction.Unknown)
    {
    // Changing Checked
    }
    }

    修复 DoubleClick关于 TreeView 中的复选框错误

    正如在 this post 中也提到的, TreeView 中存在一个错误, 当您双击 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事件,因此当您选中/取消选中节点时:
  • 该节点的所有后代都应更改为相同的检查状态。
  • 祖先中的所有节点,如果在其后代中有一个 child 被选中,则应该被选中,否则,应该被取消选中。

  • 这是实现:
    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);
    });
    }
    }

    示例

    要测试解决方案,您可以填写 TreeView有以下数据:
    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 中的代码:

    ExTreeView
    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;
    }
    }
    }
    }

    AfterSelect
    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;
    };
    }
    }

    关于c# - WinForms TreeView 检查/取消检查层次结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50494226/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com