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
ожидается, что это произойдет:
взгляните на скриншот приложения. A, G, L и T есть. Если я сниму галочку, скажи:, L,
- T должно быть снято как T ребенок L.
- G и A должен быть снят флажок, поскольку у них не останется детей.
что происходит:
этот код работает нормально, если я щелкните любой узел. если я дважды щелкните узел, который становится проверенным / непроверенным, но одно и то же изменение не отражается на родителе и потомках.
двойной щелчок также замораживает приложение на некоторое время.
как я могу исправить эту проблему и получить ожидаемое поведение?
1 ответов
вот основные проблемы, которые нужно решить здесь:
-
запретить
AfterCkeck
обработчик событий от повторения логики рекурсивно.когда вы меняете
Checked
свойство узла вAfterCheck
, это приведет к другимAfterCheck
событие, которое может привести нас к переполнению стека или, по крайней мере, ненужному после событий проверки или непредсказуемого результата в нашем алгоритме. -
исправить
DoubleClick
на галочки ошибку вTreeView
.когда вы дважды щелкните по
CheckBox
наTreeView
, theChecked
стоимостью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;
};
}
}