WinForms TreeView проверка / снятие иерархии

следующий код предназначен для рекурсивной проверки или отмены проверки родительских или дочерних узлов по мере необходимости.

enter image description here

например, на этой позиции,A, G, L и T узлы должны быть сняты, если мы проверяем любую из них.

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

ожидается, что это произойдет:

взгляните на скриншот приложения. A, G, L и T есть. Если я сниму галочку, скажи:, L,
- T должно быть снято как T ребенок L.
- G и A должен быть снят флажок, поскольку у них не останется детей.

что происходит:

этот код работает нормально, если я щелкните любой узел. если я дважды щелкните узел, который становится проверенным / непроверенным, но одно и то же изменение не отражается на родителе и потомках.

двойной щелчок также замораживает приложение на некоторое время.

как я могу исправить эту проблему и получить ожидаемое поведение?

1 ответов


вот основные проблемы, которые нужно решить здесь:

  • запретить AfterCkeck обработчик событий от повторения логики рекурсивно.

    когда вы меняете Checked свойство узла в AfterCheck, это приведет к другим AfterCheck событие, которое может привести нас к переполнению стека или, по крайней мере, ненужному после событий проверки или непредсказуемого результата в нашем алгоритме.

  • исправить DoubleClick на галочки ошибку в TreeView.

    когда вы дважды щелкните по CheckBox на TreeView, the Checked стоимостью 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);
});

скачать

вы можете скачать рабочий пример из следующего репозитория:

Подробный Ответ!--106-->

запретить AfterCkeck обработчик событий от повторения логики рекурсивно

на самом деле мы не остановим AfterCheck обработчик событий от raising AfterCheck. Вместо этого, мы обнаруживаем, если AfterCheck вызывается пользователем или нашим кодом внутри обработчика. Для этого мы можем проверить Action свойство события arg:

чтобы предотвратить событие, чтобы быть поднятым несколько раз добавьте логику в обработчик событий, который выполняет только рекурсивный код, если Action свойства TreeViewEventArgs не установлено в TreeViewAction.Unknown.

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

исправить DoubleClick на галочки ошибку, в TreeView

Как также упоминалось в этот пост, есть ошибка в TreeView, когда вы дважды щелкаете на CheckBox на TreeView, the Checked стоимостью Node изменится дважды и будет установлен в исходное состояние перед двойным щелчком мыши, но AfterCheck событие будет расти один раз.

чтобы решить эту проблему, вы можете hanlde 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 событие, поэтому, когда вы установите/снимите узел:

  • все потомки этого узла следует изменять в том же состоянии.
  • все узлы в предках, следует проверить, есть ли в списке один ребенок в их потомках, в противном случае, следует снять флажок.

здесь реализация:

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