WinForms TreeView проверка / снятие флажка иерархии - PullRequest
0 голосов
/ 23 мая 2018

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

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 Ответ

0 голосов
/ 01 июня 2018

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

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

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

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

    При двойном щелчке по CheckBox в TreeView значение CheckedNode изменится дважды и будет установлен в исходное состояние до двойного щелчка, но событие 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:

Чтобы предотвратить многократное возбуждение события, добавьте логику в ваш обработчик событий, который выполняет ваш рекурсивный код, только если *Свойство 1095 * для 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 Support

Поскольку в .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;
        };
    }
}
...