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