How to get ALL child controls of a Windows Forms form of a specific type (Button/Textbox)?

226,898

Solution 1

Here's another option for you. I tested it by creating a sample application, I then put a GroupBox and a GroupBox inside the initial GroupBox. Inside the nested GroupBox I put 3 TextBox controls and a button. This is the code I used (even includes the recursion you were looking for)

public IEnumerable<Control> GetAll(Control control,Type type)
{
    var controls = control.Controls.Cast<Control>();

    return controls.SelectMany(ctrl => GetAll(ctrl,type))
                              .Concat(controls)
                              .Where(c => c.GetType() == type);
}

To test it in the form load event I wanted a count of all controls inside the initial GroupBox

private void Form1_Load(object sender, EventArgs e)
{
    var c = GetAll(this,typeof(TextBox));
    MessageBox.Show("Total Controls: " + c.Count());
}

And it returned the proper count each time, so I think this will work perfectly for what you're looking for :)

Solution 2

In C# (since you tagged it as such) you could use a LINQ expression like this:

List<Control> c = Controls.OfType<TextBox>().Cast<Control>().ToList();

Edit for recursion:

In this example, you first create the list of controls and then call a method to populate it. Since the method is recursive, it doesn't return the list, it just updates it.

List<Control> ControlList = new List<Control>();
private void GetAllControls(Control container)
{
    foreach (Control c in container.Controls)
    {
        GetAllControls(c);
        if (c is TextBox) ControlList.Add(c);
    }
}

It may be possible to do this in one LINQ statement using the Descendants function, though I am not as familiar with it. See this page for more information on that.

Edit 2 to return a collection:

As @ProfK suggested, a method that simply returns the desired controls is probably better practice. To illustrate this I have modified the code as follows:

private IEnumerable<Control> GetAllTextBoxControls(Control container)
{
    List<Control> controlList = new List<Control>();
    foreach (Control c in container.Controls)
    {
        controlList.AddRange(GetAllTextBoxControls(c));
        if (c is TextBox)
            controlList.Add(c);
    }
    return controlList;
}

Solution 3

This is an improved version of the recursive GetAllControls() that actually works on private vars:

    private void Test()
    {
         List<Control> allTextboxes = GetAllControls(this);
    }
    private List<Control> GetAllControls(Control container, List<Control> list)
    {
        foreach (Control c in container.Controls)
        {
            if (c is TextBox) list.Add(c);
            if (c.Controls.Count > 0)
                list = GetAllControls(c, list);
        }

        return list;
    }
    private List<Control> GetAllControls(Control container)
    {
        return GetAllControls(container, new List<Control>());
    }

Solution 4

I combined a bunch of the previous ideas into one extension method. The benefits here are that you get the correctly typed enumerable back, plus inheritance is handled correctly by OfType().

public static IEnumerable<T> FindAllChildrenByType<T>(this Control control)
{
    IEnumerable<Control> controls = control.Controls.Cast<Control>();
    return controls
        .OfType<T>()
        .Concat<T>(controls.SelectMany<Control, T>(ctrl => FindAllChildrenByType<T>(ctrl)));
}

Solution 5

It might be the ancient technique, but it works like charm. I used recursion to change the color of all labels of the control. It works great.

internal static void changeControlColour(Control f, Color color)
{
    foreach (Control c in f.Controls)
    {

        // MessageBox.Show(c.GetType().ToString());
        if (c.HasChildren)
        {
            changeControlColour(c, color);
        }
        else
            if (c is Label)
            {
                Label lll = (Label)c;
                lll.ForeColor = color;
            }
    }
}
Share:
226,898
Luis
Author by

Luis

Updated on January 09, 2022

Comments

  • Luis
    Luis over 2 years

    I need to get all controls on a form that are of type x. I'm pretty sure I saw that code once in the past that used something like this:

    dim ctrls() as Control
    ctrls = Me.Controls(GetType(TextBox))
    

    I know I can iterate over all controls getting children using a recursive function, but is there something easier or more straightforward, maybe like the following?

    Dim Ctrls = From ctrl In Me.Controls Where ctrl.GetType Is Textbox
    
  • Luis
    Luis almost 14 years
    Thanks, C# or VB is fine for me. But the problems is that Controls.OfType<TExtbox> only returns the childs of the current control(in my case the Form), and I want in a single call to get ALL controls in the Forma "recursively" (chiilds, sub-childs, sub-sub-childs,.....) in asingle collection.
  • Luis
    Luis almost 14 years
    Thanks, but the same problema as the ther answer, it only returns the chidls but not the subchilds, etc, and I want all ensted controls. I'm pretty sure I saw that it is posible with a single method call that is new in .NET 3.5 or 4.0, remember I saw that in a demo somewehre
  • CoderDennis
    CoderDennis almost 14 years
    Ignoring the lack of recursion, wouldn't var c = this.Controls.OfType<TextBox>() give the same result?
  • JYelton
    JYelton almost 14 years
    @Dennis: Yes it would, it's a matter of preference (usually). See stackoverflow.com/questions/214500/… for an interesting discussion on the matter.
  • Michael Bahig
    Michael Bahig about 12 years
    GetAll() defined here is a very good candidate for an extension method to class Control
  • ProfK
    ProfK over 11 years
    I would expect a method called GetAllControls to return a collection of controls, which I would assign to ControlList. Just seems better practice.
  • JYelton
    JYelton over 11 years
    @ProfK I agree with you; changing example accordingly.
  • Aditya Bokade
    Aditya Bokade almost 11 years
    I liked the way you used lambda expressions. Where to learn lambda expressions in detail?
  • Randall Flagg
    Randall Flagg about 9 years
    this is the best(and fastest according to my tests) solution in my opinion on this page. But I would suggest that you change controls to an array: var enumerable = controls as Control[] ?? controls.ToArray(); and then change to: return enumerable.SelectMany(FindControls<T>).Concat(enumerable) .Where(c => c.GetType() == typeof(T)).Cast<T>();
  • soulblazer
    soulblazer about 9 years
    This doesn't work if the child control has children of its own.
  • soulblazer
    soulblazer about 9 years
    "'System.Windows.Forms.Control.ControlCollection' does not contain a definition for 'Cast' and no extension method 'Cast' accepting a first argument of type 'System.Windows.Forms.Control.ControlCollection' could be found (are you missing a using directive or an assembly reference?)" I'm on .NET 4.5 and "Controls" has no "Cast" function / method / whatever. What am I missing?
  • Ivan-Mark Debono
    Ivan-Mark Debono about 9 years
    @soulblazer Add System.Linq namespace.
  • leigero
    leigero almost 9 years
    Simply posting code does little to help the OP understand their problem or your solution. You should almost ALWAYS include some sort of explanation to accompany your code.
  • LarsTech
    LarsTech almost 9 years
    The question didn't say anything about clearing the form.
  • Admin
    Admin almost 8 years
    Yes, doesn't answer "the question", but is a nice addition to it. Thank you!
  • Fencer04
    Fencer04 over 7 years
    Please add more to your answer that explains what is happening and how it's related to the question.
  • bh_earth0
    bh_earth0 over 6 years
    var allCtl = GetAll(this.FindForm(), typeof(TextBox)); //this is a Usercontrol returns Nothing!!
  • bh_earth0
    bh_earth0 over 6 years
    WARNİNG!! when you cast<Textbox> . you exclude splitcontainer , panel, etc containers that MAY CONTAİN your Desired Textbox . so you wont be able to get deep nested ones . to avoid this. cast after you completely returned the list like @JYeltons answers
  • TheHitchenator
    TheHitchenator over 5 years
    Is it not more efficient to use the .OfType<T>() Linq method rather than .Where(c => c.GetType() == typeof(T)).Cast<T>(); to get the same effect?
  • ChrisPBacon
    ChrisPBacon over 4 years
    This will only find controls directly in the control collection of "Me" and not find Button controls that are within any child containers as the poster was trying to imply by "ALL".
  • Jone Polvora
    Jone Polvora about 4 years
    this is a cleaner version that is lazy, can be enumerated and fetched on demand.