Ответ 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);
});
}
}
ПримерExample
Чтобы проверить решение, вы можете заполнить 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;
};
}
}