Joan Venge
Joan Venge

Reputation: 331440

Finding a control on a Winforms using LINQ?

I am trying to find an elegant way to get controls on a Windows Forms form by name. For example:

MyForm.GetControl "MyTextBox"

...

But this has to make sure it goes through all the controls recursively.

What's the most elegant way to implement this using LINQ?

Upvotes: 8

Views: 10479

Answers (5)

Petko
Petko

Reputation: 11

I wonder why nobody suggested this.

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

Upvotes: 1

John K
John K

Reputation: 28917

"Elegant" Control Filter (No LINQ)

Thanks to C# 3 there are many elegant solutions. This one doesn't use LINQ query operators; it uses lambdas and delegates.

This filters all controls for given criteria (can filter against multiple criteria). Returns multiple matches. It allows for more than name detection.

    /// <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();
    }

Filter Usage...

It's quite flexible as far as usage

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.

Equivalent Extension Method

Extension methods add an heir of elegancy to applications also.

The exact same logic can be injected into an extension method like so.

static public class ControlExtensions {

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

Extension usage is:

// 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);

Another Extension to Return One Control or null

Calls the first extension method (see above) to get all matching controls, then returns the first one in the matches, otherwise null if the match list is empty.

This isn't efficient because it iterates over all controls even after finding the first match - but just playing around here for the sake of SO comments.

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

Upvotes: 14

Marc Gravell
Marc Gravell

Reputation: 1064044

LINQ isn't necessarily best-suited to unknown-depth recursion; just use regular code...

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

with:

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

Or if you don't like the recursion above:

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

Upvotes: 8

Lee
Lee

Reputation: 144206

I don't think you can create recursive linq queries directly, but you could create a recursive method using linq:

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

This should return a sequence containing every control contained in a control hierarchy. Then finding a match is straightforward:

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

Personally I'd avoid using linq in this way though.

Upvotes: 8

Henk Holterman
Henk Holterman

Reputation: 273711

Not so easy...

LINQ isn't very good at recursion and Control.Controls isn't LINQ enabled (needs Cast).

Sometimes a method is the best solution. Since you can write one that works for all Controls it will be even more reusable than a LINQ query. It could be an extension method.

Upvotes: 1

Related Questions