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



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

Введите описание изображения здесь

Например, в этой позиции 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

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

Взгляните на скриншот приложения. А, G, L, и еще Т проверяются. Если я сниму галочку, скажите, L,
- Т должно быть непроверено как Т является ли ребенок L.
- G и еще А они должны быть бесконтрольны, так как у них не останется детей.

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

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

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

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

222   1  

1 ответ:

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

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

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

  • Исправлена ошибка DoubleClick на флажках в TreeView.

    Когда вы дважды щелкните на CheckBox в TreeView значение 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);
});

Скачать

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

Подробный Ответ

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

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

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

Вот реализация:

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;
        };
    }
}
    Ничего не найдено.

Добавить ответ:
Отменить.