【问题标题】:Finding a control on a Winforms using LINQ?使用 LINQ 在 Winforms 上查找控件?
【发布时间】:2010-01-07 23:59:34
【问题描述】:

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

MyForm.GetControl "MyTextBox"

...

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

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

【问题讨论】:

    标签: c# .net winforms linq


    【解决方案1】:

    “优雅”控制过滤器(无 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];
        }
    

    【讨论】:

    • 感谢jdk,真的很好。唯一的事情是我需要得到一个结果或空值。得到一个数组更好。所以也许我应该使用你的过滤器方法来编写另一个返回单个控件或 null 的方法。这用于事件处理等,因此我需要获取单个控件并将事件添加到其中。
    • 添加了一个扩展以返回 1 Control 或 null。我不希望这是被接受的解决方案,只是在 C# 中获得一些乐趣并将结果留在这里供细读。
    【解决方案2】:

    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");,不是吗?
    • @Brad 很可能; 3年前的事了,记忆模糊了
    【解决方案3】:

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

    【讨论】:

    • 这可能是最好的解决方案 - 特别是考虑到 FlattenHierarchy 可以重新制定为通用方法。为什么要避免以这种方式使用 LINQ?
    • @albahari - 是的,你的想法可能是对的 - 我最初认为它可能效率很低,但可能没关系。
    • +1 因为您按照要求使用 LINQ 进行了操作,这与您自己的更好判断相反
    【解决方案4】:

    没那么容易……

    LINQ 不太擅长递归并且Control.Controls 没有启用 LINQ(需要 Cast)。

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

    【讨论】:

      【解决方案5】:

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-04-25
        • 2022-12-13
        • 1970-01-01
        相关资源
        最近更新 更多