【发布时间】:2010-01-07 23:59:34
【问题描述】:
我试图找到一种优雅的方式来按名称获取Windows Forms 表单上的控件。例如:
MyForm.GetControl "MyTextBox"
...
但这必须确保它递归地遍历所有控件。
使用LINQ 实现此功能的最优雅方式是什么?
【问题讨论】:
我试图找到一种优雅的方式来按名称获取Windows Forms 表单上的控件。例如:
MyForm.GetControl "MyTextBox"
...
但这必须确保它递归地遍历所有控件。
使用LINQ 实现此功能的最优雅方式是什么?
【问题讨论】:
感谢 C# 3,有许多优雅的解决方案。这个不使用 LINQ 查询运算符;它使用 lambda 和委托。
这会过滤给定条件的所有控件(可以过滤多个条件)。返回多个匹配项。它允许的不仅仅是名称检测。
/// <summary>
/// Recurses through all controls, starting at given control,
/// and returns an array of those matching the given criteria.
/// </summary>
public Control[] FilterControls(Control start, Func<Control, bool> isMatch) {
var matches = new List<Control>();
Action<Control> filter = null;
(filter = new Action<Control>(c => {
if (isMatch(c))
matches.Add(c);
foreach (Control c2 in c.Controls)
filter(c2);
}))(start);
return matches.ToArray();
}
就使用而言非常灵活
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.
扩展方法也为应用程序增添了优雅的继承者。
可以将完全相同的逻辑注入到这样的扩展方法中。
static public class ControlExtensions {
static public Control[] FilterControls(this Control start, Func<Control, bool> 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);
null
调用第一个扩展方法(见上文)获取所有匹配控件,然后返回匹配中的第一个,否则如果匹配列表为空,则返回 null。
这效率不高,因为即使在找到第一个匹配项之后它也会遍历所有控件 - 但只是为了 SO cmets 而在这里玩。
static public Control FilterControlsOne(this Control start, Func<Control, bool> isMatch) {
Control[] arrMatches = ControlExtensions.FilterControls(start, isMatch);
return arrMatches.Length == 0 ? null : arrMatches[0];
}
【讨论】:
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<Control>();
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;
}
【讨论】:
Control c = myForm.FindControl("MyTextBox");,不是吗?
我认为您不能直接创建递归 linq 查询,但您可以使用 linq 创建递归方法:
public IEnumerable<Control> FlattenHierarchy(this Control c)
{
return new[] { c }.Concat(c.Controls.Cast<Control>().SelectMany(child => child.FlattenHierarchy()));
}
这应该返回一个序列,其中包含控件层次结构中包含的每个控件。然后找到一个匹配很简单:
public Control FindInHierarchy(this Control control, string controlName)
{
return control.FlattenHierarchy().FirstOrDefault(c => c.Name == controlName);
}
我个人会避免以这种方式使用 linq。
【讨论】:
没那么容易……
LINQ 不太擅长递归并且(需要 Cast)。 Control.Controls 没有启用 LINQ
有时一种方法是最好的解决方案。由于您可以编写一个适用于所有控件的控件,因此它比 LINQ 查询更具可重用性。它可能是一种扩展方法。
【讨论】:
我想知道为什么没有人建议这个。
var control = Controls.Find("MyTextBox",false);
TextBox searchedTexBox = control[0] as TextBox;
【讨论】: