使用LINQ查找Winforms的控件?

我试图通过名称找到一种优雅的方式来获取Windows窗体表单上的控件。 例如:

MyForm.GetControl "MyTextBox" ... 

但这必须确保它递归地遍历所有控件。

使用LINQ实现这一点的最优雅方法是什么?

LINQ不一定最适合未知深度递归; 只需使用常规代码……

 public static Control FindControl(this Control root, string name) { if(root == null) throw new ArgumentNullException("root"); foreach(Control child in root.Controls) { if(child.Name == name) return child; Control found = FindControl(child, name); if(found != null) return found; } return null; } 

有:

 Control c = myForm.GetControl("MyTextBox"); 

或者,如果您不喜欢上面的递归:

 public Control FindControl(Control root, string name) { if (root == null) throw new ArgumentNullException("root"); var stack = new Stack(); stack.Push(root); while (stack.Count > 0) { Control item = stack.Pop(); if (item.Name == name) return item; foreach (Control child in item.Controls) { stack.Push(child); } } return null; } 

“优雅”控制滤波器(无LINQ)

感谢C#3,有许多优雅的解决方案。 这个不使用LINQ查询运算符; 它使用lambdas和delegates。

这会过滤给定条件的所有控件(可以根据多个条件进行过滤)。 返回多个匹配项。 它允许不仅仅是名称检测。

  ///  /// Recurses through all controls, starting at given control, /// and returns an array of those matching the given criteria. ///  public Control[] FilterControls(Control start, Func isMatch) { var matches = new List(); Action filter = null; (filter = new Action(c => { if (isMatch(c)) matches.Add(c); foreach (Control c2 in c.Controls) filter(c2); }))(start); return matches.ToArray(); } 

filter使用…

就使用而言,它非常灵活

 Control[] foundControls = null; // Find control with Name="tabs1". foundControls = FilterControls(this, c => c.Name != null && c.Name.Equals("tabs1")); // Find all controls that start with ID="panel*... foundControls = FilterControls(this, c => c.Name != null && c.Name.StartsWith("panel")); // Find all Tab Pages in this form. foundControls = FilterControls(this, c => c is TabPage); Console.Write(foundControls.Length); // is an empty array if no matches found. 

等效扩展方法

扩展方法也为应用程序添加了优雅的inheritance人。

可以将完全相同的逻辑注入到这样的扩展方法中。

 static public class ControlExtensions { static public Control[] FilterControls(this Control start, Func isMatch) { // Put same logic here as seen above (copy & paste) } } 

扩展用法是:

 // Find control with Name="tabs1" in the Panel. panel1.FilterControls(c => c.Name != null && c.Name.Equals("tabs1")); // Find all panels in this form this.FilterControls(c => c is Panel); 

返回一个Control或null另一个扩展

调用第一个扩展方法(见上文)获取所有匹配的控件,然后返回匹配中的第一个,否则返回null,如果匹配列表为空。

这样做效率不高,因为即使在找到第一场比赛后它也会迭代所有控件 – 但是为了SO注释只是在这里玩。

  static public Control FilterControlsOne(this Control start, Func isMatch) { Control[] arrMatches = ControlExtensions.FilterControls(start, isMatch); return arrMatches.Length == 0 ? null : arrMatches[0]; } 

我不认为你可以直接创建递归linq查询,但你可以使用linq创建一个递归方法:

 public IEnumerable FlattenHierarchy(this Control c) { return new[] { c }.Concat(c.Controls.Cast().SelectMany(child => child.FlattenHierarchy())); } 

这应该返回一个包含控件层次结构中包含的每个控件的序列。 然后找到匹配很简单:

 public Control FindInHierarchy(this Control control, string controlName) { return control.FlattenHierarchy().FirstOrDefault(c => c.Name == controlName); } 

就个人而言,我会以这种方式避免使用linq。

不那么容易……

LINQ不太擅长递归Control.Controls不支持LINQ (需要Cast)。

有时一种方法是最好的解决方案。 由于您可以编写适用于所有控件的控件,因此它比LINQ查询更具可重用性。 它可能是一种扩展方法。