使用LINQ查找Winforms的控件?

时间:2010-01-07 23:59:35

标签: c# .net winforms linq

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

MyForm.GetControl "MyTextBox"

...

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

使用LINQ实现此目的的最优雅方式是什么?

5 个答案:

答案 0 :(得分:14)

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

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

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

    /// <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评论而在这里玩。

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

答案 1 :(得分:8)

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;
}

答案 2 :(得分:7)

我认为你不能直接创建递归的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。

答案 3 :(得分:1)

不那么容易......

LINQ不太擅长递归Control.Controls未启用LINQ (需要Cast)。

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

答案 4 :(得分:0)

我想知道为什么没有人建议这样做。

 var control = Controls.Find("MyTextBox",false);
 TextBox searchedTexBox = control[0] as TextBox;