C# 在遍历子控件时避免无限循环
我正在编写一个简单的扩展方法来对一个控件及其所有子控件执行一个操作,我想知道是否需要担心两次碰到同一个控件 安全:C# 在遍历子控件时避免无限循环,c#,.net,winforms,controls,C#,.net,Winforms,Controls,我正在编写一个简单的扩展方法来对一个控件及其所有子控件执行一个操作,我想知道是否需要担心两次碰到同一个控件 安全: 实现这一点的(可能)综合方法: public static class ControlExtensions { public static void Traverse(this Control control, Action<Control> action) { Traverse(control, action, TraversalMe
实现这一点的(可能)综合方法:
public static class ControlExtensions
{
public static void Traverse(this Control control, Action<Control> action)
{
Traverse(control, action, TraversalMethod.DepthFirst);
}
public static void Traverse(this Control control, Action<Control> action, TraversalMethod method)
{
switch (method)
{
case TraversalMethod.DepthFirst:
TraverseDepth(control, action);
break;
case TraversalMethod.BreadthFirst:
TraverseBreadth(control, action);
break;
case TraversalMethod.ReversedDepthFirst:
TraverseDepthReversed(control, action);
break;
case TraversalMethod.ReversedBreadthFirst:
TraverseBreadthReversed(control, action);
break;
}
}
private static void TraverseDepth(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseBreadth(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseDepthReversed(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
private static void TraverseBreadthReversed(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
}
公共静态类控制扩展
{
公共静态无效遍历(此控件、操作)
{
遍历(控制、操作、遍历方法深度优先);
}
公共静态无效遍历(此控件、操作、遍历方法)
{
开关(方法)
{
案例遍历方法。深度优先:
横向深度(控制、动作);
打破
案例遍历方法。宽度优先:
TraverseBreadth(控制、操作);
打破
案例遍历方法。ReversedDepthFirst:
横越(控制、动作);
打破
案例遍历方法。反向宽度优先:
TraverseBreadthReversed(控制、操作);
打破
}
}
私有静态void transversedepth(控制、操作)
{
堆栈控件=新堆栈();
队列=新队列();
控制。推(控制);
while(controls.Count!=0)
{
control=controls.Pop();
foreach(Control.Controls中的控件子级)
控件。推(子);
排队(控制);
}
while(queue.Count!=0)
action.Invoke(queue.Dequeue());
}
私有静态void TraverseBreadth(控制、操作)
{
队列控制=新队列();
队列=新队列();
控制。排队(控制);
while(controls.Count!=0)
{
control=controls.Dequeue();
foreach(Control.Controls中的控件子级)
控件。排队(子级);
排队(控制);
}
while(queue.Count!=0)
action.Invoke(queue.Dequeue());
}
私有静态void transversedethreversed(控制、操作)
{
堆栈控件=新堆栈();
堆栈=新堆栈();
控制。推(控制);
while(controls.Count!=0)
{
control=controls.Pop();
foreach(Control.Controls中的控件子级)
控件。推(子);
堆栈推送(控制);
}
while(stack.Count!=0)
action.Invoke(stack.Pop());
}
私有静态void TraverseBreadthReversed(控制、操作)
{
队列控制=新队列();
堆栈=新堆栈();
控制。排队(控制);
while(controls.Count!=0)
{
control=controls.Dequeue();
foreach(Control.Controls中的控件子级)
控件。排队(子级);
堆栈推送(控制);
}
while(stack.Count!=0)
action.Invoke(stack.Pop());
}
}
每个孩子都有一个家长,所以不必担心。控件实际上只能有一个家长。实际上没有理由跟踪“handled”,因为您只能在控件上执行一次方法
现在,如果您使用的框架允许控件有多个父控件(我不知道有任何.NET框架允许这样做),那么可能需要这样做。但是,如果您使用的是Windows窗体(看起来就是这样)或WPF,您可以将其简化为:
private static void Traverse(this Control control, Action<Control> action)
{
foreach (Control child in control.Controls)
Traverse(child, action);
action(control);
}
私有静态无效遍历(此控件、操作)
{
foreach(Control.Controls中的控件子级)
遍历(子、动作);
行动(控制);
}
您需要使用递归
public sub DoStuffToControlAndChildren(TargetControl as Control)
'Insert code to do stuff to TargetControl here
if TargetControl.Controls.count = 0 then
return
end if
For each ChildControl in TargetControl.Controls
DoStuffToControlAndChildren(ChildControl)
next
end sub
啊,但是如果启动控件没有父控件呢?我看到过这种情况,顺便说一句,我们正在递归地查看给定控件的子控件。这意味着我们从不查看父级,而是使用堆栈跟踪该沿袭。因此,当第一个控件是无父控件时,它将正常工作。在这种情况下,您将遇到其他问题。很难想象一个安全的方法会在一个旨在“对一个控件及其所有子控件执行操作”的方法中重新生成控件@Steven:而且,如果你要更改父对象,你在foreach中会遇到问题,即使“处理”到位了……这一点很好。更改父项将有效地更改每个集合的内容,这是foreach的一个禁忌。我想唯一的选择将是通常的解决方法:迭代一次以构建待办事项列表,然后遍历并实现该列表。如果一个孩子有多个父项,child.Parent会返回什么?哪个家长会包含孩子?事件/击键会在哪里发生?@Rob:所有的优点。可以说,可能存在不同类型的包含,例如一个用于事件的树,另一个用于显示(z顺序等)。在这种情况下,某些控件可能出现在一个集合中,但另一个集合中不出现,而其余控件则同时出现在两个集合中。在某种程度上,WPF实际上是这样做的。“控件”的逻辑树和可视化树并不一定在所有实例中都相同。。。看:@Reed:有趣。如果我理解正确,它看起来像是有效地保留了一棵树,但提供了一个经过过滤的“逻辑”视图以及一个完整的“视觉”视图。@Steven:“逻辑”树基本上就是您在代码中设置的。然而,由于所有内容都是在WPF中模板化的,“视觉树”或实际渲染的内容可能完全不同。在树的各层之间可以存在更多的“项”。我通过保持堆栈/队列来避免递归。@Daniel:树到底有多深?如果答案是“不太”,那么递归就可以了。答案是“不太”,但我喜欢堆栈和队列的优雅(IMO),一个
public static class ControlExtensions
{
public static void Traverse(this Control control, Action<Control> action)
{
Traverse(control, action, TraversalMethod.DepthFirst);
}
public static void Traverse(this Control control, Action<Control> action, TraversalMethod method)
{
switch (method)
{
case TraversalMethod.DepthFirst:
TraverseDepth(control, action);
break;
case TraversalMethod.BreadthFirst:
TraverseBreadth(control, action);
break;
case TraversalMethod.ReversedDepthFirst:
TraverseDepthReversed(control, action);
break;
case TraversalMethod.ReversedBreadthFirst:
TraverseBreadthReversed(control, action);
break;
}
}
private static void TraverseDepth(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseBreadth(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Queue<Control> queue = new Queue<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
queue.Enqueue(control);
}
while (queue.Count != 0)
action.Invoke(queue.Dequeue());
}
private static void TraverseDepthReversed(Control control, Action<Control> action)
{
Stack<Control> controls = new Stack<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Push(control);
while (controls.Count != 0)
{
control = controls.Pop();
foreach (Control child in control.Controls)
controls.Push(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
private static void TraverseBreadthReversed(Control control, Action<Control> action)
{
Queue<Control> controls = new Queue<Control>();
Stack<Control> stack = new Stack<Control>();
controls.Enqueue(control);
while (controls.Count != 0)
{
control = controls.Dequeue();
foreach (Control child in control.Controls)
controls.Enqueue(child);
stack.Push(control);
}
while (stack.Count != 0)
action.Invoke(stack.Pop());
}
}
private static void Traverse(this Control control, Action<Control> action)
{
foreach (Control child in control.Controls)
Traverse(child, action);
action(control);
}
public sub DoStuffToControlAndChildren(TargetControl as Control)
'Insert code to do stuff to TargetControl here
if TargetControl.Controls.count = 0 then
return
end if
For each ChildControl in TargetControl.Controls
DoStuffToControlAndChildren(ChildControl)
next
end sub