139

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
Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90
Luis
  • 1,840
  • 2
  • 14
  • 14
  • 1
    Related question: http://stackoverflow.com/questions/253937/recursive-control-search-with-linq – JYelton Aug 06 '10 at 15:37
  • I have made an [API Proposal: Add Descendants property for Control](https://github.com/dotnet/winforms/issues/5195) on github.com/dotnet/winforms for this. If you like it, please upvote it. – Olivier Jacot-Descombes Jul 05 '21 at 14:22

28 Answers28

259

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 :)

PsychoCoder
  • 10,570
  • 12
  • 44
  • 60
  • 25
    GetAll() defined here is a very good candidate for an extension method to class Control – Michael Bahig Feb 26 '12 at 18:34
  • I liked the way you used lambda expressions. Where to learn lambda expressions in detail? – Aditya Bokade Jul 17 '13 at 08:10
  • "'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? – soulblazer May 08 '15 at 16:29
  • 3
    @soulblazer Add System.Linq namespace. – Ivan-Mark Debono May 11 '15 at 09:20
  • var allCtl = GetAll(this.FindForm(), typeof(TextBox)); //this is a Usercontrol returns Nothing!! – bh_earth0 Aug 22 '17 at 10:22
  • WARNİNG!! when you cast . 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 – bh_earth0 Aug 22 '17 at 10:51
43

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;
}
JYelton
  • 35,664
  • 27
  • 132
  • 191
  • 1
    Thanks, C# or VB is fine for me. But the problems is that Controls.OfType 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 Aug 05 '10 at 23:41
  • I would expect a method called GetAllControls to return a collection of controls, which I would assign to ControlList. Just seems better practice. – ProfK Oct 09 '12 at 06:20
  • @ProfK I agree with you; changing example accordingly. – JYelton Oct 09 '12 at 15:45
14

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>());
    }
VictorEspina
  • 352
  • 2
  • 7
11

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)));
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Entiat
  • 111
  • 1
  • 2
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;
            }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Aditya Bokade
  • 1,708
  • 1
  • 28
  • 45
5

You can use a LINQ query to do this. This will query everything on the form that is type TextBox

var c = from controls in this.Controls.OfType<TextBox>()
              select controls;
PsychoCoder
  • 10,570
  • 12
  • 44
  • 60
  • 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 – Luis Aug 05 '10 at 23:44
  • Ignoring the lack of recursion, wouldn't `var c = this.Controls.OfType()` give the same result? – CoderDennis Aug 06 '10 at 18:13
  • 2
    @Dennis: Yes it would, it's a matter of preference (usually). See http://stackoverflow.com/questions/214500/which-linq-syntax-do-you-prefer-fluent-or-query-expression for an interesting discussion on the matter. – JYelton Aug 09 '10 at 15:58
4

I'd like to amend PsychoCoders answer: as the user wants to get all controls of a certain type we could use generics in the following way:

    public IEnumerable<T> FindControls<T>(Control control) where T : Control
    {
        // we can't cast here because some controls in here will most likely not be <T>
        var controls = control.Controls.Cast<Control>();

        return controls.SelectMany(ctrl => FindControls<T>(ctrl))
                                  .Concat(controls)
                                  .Where(c => c.GetType() == typeof(T)).Cast<T>();
    }

This way, we can call the function as follows:

private void Form1_Load(object sender, EventArgs e)
{
    var c = FindControls<TextBox>(this);
    MessageBox.Show("Total Controls: " + c.Count());
}
Adam
  • 63
  • 5
  • 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).Concat(enumerable) .Where(c => c.GetType() == typeof(T)).Cast(); – Randall Flagg Apr 28 '15 at 13:30
  • Is it not more efficient to use the `.OfType()` Linq method rather than `.Where(c => c.GetType() == typeof(T)).Cast();` to get the same effect? – IAmJersh Oct 24 '18 at 12:55
4

A clean and easy solution (C#):

static class Utilities {
    public static List<T> GetAllControls<T>(this Control container) where T : Control {
        List<T> controls = new List<T>();
        if (container.Controls.Count > 0) {
            controls.AddRange(container.Controls.OfType<T>());
            foreach (Control c in container.Controls) {
                controls.AddRange(c.GetAllControls<T>());
            }
        }

        return controls;
    }
}

Get all textboxes:

List<TextBox> textboxes = myControl.GetAllControls<TextBox>();
Omar
  • 16,329
  • 10
  • 48
  • 66
3

Here is my Extension method. It's very efficient and it's lazy.

Usage:

var checkBoxes = tableLayoutPanel1.FindChildControlsOfType<CheckBox>();

foreach (var checkBox in checkBoxes)
{
    checkBox.Checked = false;
}

The code is:

public static IEnumerable<TControl> FindChildControlsOfType<TControl>(this Control control) where TControl : Control
    {
        foreach (var childControl in control.Controls.Cast<Control>())
        {
            if (childControl.GetType() == typeof(TControl))
            {
                yield return (TControl)childControl;
            }
            else
            {
                foreach (var next in FindChildControlsOfType<TControl>(childControl))
                {
                    yield return next;
                }
            }
        }
    }
Jone Polvora
  • 2,184
  • 1
  • 22
  • 33
3

You can use the below Code

public static class ExtensionMethods
{
    public static IEnumerable<T> GetAll<T>(this Control control)
    {
        var controls = control.Controls.Cast<Control>();

        return controls.SelectMany(ctrl => ctrl.GetAll<T>())
                                  .Concat(controls.OfType<T>());
    }
}
3

Don't forget that you can also have a TextBox within other controls other than container controls too. You can even add a TextBox to a PictureBox.

So you also need to check if

someControl.HasChildren = True

in any recursive function.

This is the result I had from a layout to test this code:

TextBox13   Parent = Panel5
TextBox12   Parent = Panel5
TextBox9   Parent = Panel2
TextBox8   Parent = Panel2
TextBox16   Parent = Panel6
TextBox15   Parent = Panel6
TextBox14   Parent = Panel6
TextBox10   Parent = Panel3
TextBox11   Parent = Panel4
TextBox7   Parent = Panel1
TextBox6   Parent = Panel1
TextBox5   Parent = Panel1
TextBox4   Parent = Form1
TextBox3   Parent = Form1
TextBox2   Parent = Form1
TextBox1   Parent = Form1
tbTest   Parent = myPicBox

Try this with one Button and one RichTextBox on a form.

Option Strict On
Option Explicit On
Option Infer Off

Public Class Form1

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim pb As New PictureBox
        pb.Name = "myPicBox"
        pb.BackColor = Color.Goldenrod
        pb.Size = New Size(100, 100)
        pb.Location = New Point(0, 0)
        Dim tb As New TextBox
        tb.Name = "tbTest"
        pb.Controls.Add(tb)
        Me.Controls.Add(pb)

        Dim textBoxList As New List(Of Control)
        textBoxList = GetAllControls(Of TextBox)(Me)

        Dim sb As New System.Text.StringBuilder
        For index As Integer = 0 To textBoxList.Count - 1
            sb.Append(textBoxList.Item(index).Name & "   Parent = " & textBoxList.Item(index).Parent.Name & System.Environment.NewLine)
        Next

        RichTextBox1.Text = sb.ToString
    End Sub

    Private Function GetAllControls(Of T)(ByVal searchWithin As Control) As List(Of Control)

        Dim returnList As New List(Of Control)

        If searchWithin.HasChildren = True Then
            For Each ctrl As Control In searchWithin.Controls
                If TypeOf ctrl Is T Then
                    returnList.Add(ctrl)
                End If
                returnList.AddRange(GetAllControls(Of T)(ctrl))
            Next
        ElseIf searchWithin.HasChildren = False Then
            For Each ctrl As Control In searchWithin.Controls
                If TypeOf ctrl Is T Then
                    returnList.Add(ctrl)
                End If
                returnList.AddRange(GetAllControls(Of T)(ctrl))
            Next
        End If
        Return returnList
    End Function

End Class
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
2

Using reflection:

// Return a list with all the private fields with the same type
List<T> GetAllControlsWithTypeFromControl<T>(Control parentControl)
{
    List<T> retValue = new List<T>();
    System.Reflection.FieldInfo[] fields = parentControl.GetType().GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
    foreach (System.Reflection.FieldInfo field in fields)
    {
      if (field.FieldType == typeof(T))
        retValue.Add((T)field.GetValue(parentControl));
    }
}

List<TextBox> ctrls = GetAllControlsWithTypeFromControl<TextBox>(this);
MiMenda
  • 81
  • 1
  • 4
2

Here is my extension method for Control, using LINQ, as an adaptation of @PsychoCoder version:

It takes a list of type instead that allows you to not need multiple calls of GetAll to get what you want. I currently use it as an overload version.

public static IEnumerable<Control> GetAll(this Control control, IEnumerable<Type> filteringTypes)
{
    var ctrls = control.Controls.Cast<Control>();

    return ctrls.SelectMany(ctrl => GetAll(ctrl, filteringTypes))
                .Concat(ctrls)
                .Where(ctl => filteringTypes.Any(t => ctl.GetType() == t));
}

Usage:

//   The types you want to select
var typeToBeSelected = new List<Type>
{
    typeof(TextBox)
    , typeof(MaskedTextBox)
    , typeof(Button)
};

//    Only one call
var allControls = MyControlThatContainsOtherControls.GetAll(typeToBeSelected);

//    Do something with it
foreach(var ctrl in allControls)
{
    ctrl.Enabled = true;
}
gfache
  • 606
  • 6
  • 15
1

Here is the Solution.

https://stackoverflow.com/a/19224936/1147352

I have written this piece of code and selected only the panels, you can add more switches or ifs. in it

Community
  • 1
  • 1
DareDevil
  • 5,249
  • 6
  • 50
  • 88
1
public List<Control> GetAllChildControls(Control Root, Type FilterType = null)
{
    List<Control> AllChilds = new List<Control>();
    foreach (Control ctl in Root.Controls) {
        if (FilterType != null) {
            if (ctl.GetType == FilterType) {
                AllChilds.Add(ctl);
            }
        } else {
            AllChilds.Add(ctl);
        }
        if (ctl.HasChildren) {
            GetAllChildControls(ctl, FilterType);
        }
    }
    return AllChilds;
}
Top Systems
  • 951
  • 12
  • 24
1
   IEnumerable<Control> Ctrls = from Control ctrl in Me.Controls where ctrl is TextBox | ctrl is GroupBox select ctr;

Lambda Expressions

IEnumerable<Control> Ctrls = Me.Controls.Cast<Control>().Where(c => c is Button | c is GroupBox);
Memo Arfaa
  • 36
  • 3
  • Please add more to your answer that explains what is happening and how it's related to the question. – Fencer04 Dec 05 '16 at 20:31
1

Create Method

public static IEnumerable<Control> GetControlsOfType<T>(Control control)
{
    var controls = control.Controls.Cast<Control>();
    return controls.SelectMany(ctrl => GetControlsOfType<T>(ctrl)).Concat(controls).Where(c => c is T);
}

And use it Like

Var controls= GetControlsOfType<TextBox>(this);//You can replace this with your control
Lucifer
  • 1,594
  • 2
  • 18
  • 32
1

I'm saldy using VB so, I wrote an extension method. That retrieve all children and sub children of a control

Imports System.Runtime.CompilerServices
Module ControlExt

<Extension()>
Public Function GetAllChildren(Of T As Control)(parentControl As Control) As IEnumerable(Of T)
    Dim controls = parentControl.Controls.Cast(Of Control)
    Return controls.SelectMany(Of Control)(Function(ctrl) _
        GetAllChildren(Of T)(ctrl)) _
        .Concat(controls) _
        .Where(Function(ctrl) ctrl.GetType() = GetType(T)) _
    .Cast(Of T)
End Function

End Module

Then you can use it like, where "btnList" is a control

btnList.GetAllChildren(Of HtmlInputRadioButton).FirstOrDefault(Function(rb) rb.Checked)

In this case, it will select the selected radio button.

Iannick
  • 146
  • 1
  • 11
0

I modified from @PsychoCoder. All controls could be found now (include nested).

public static IEnumerable<T> GetChildrens<T>(Control control)
{
  var type = typeof (T);

  var allControls = GetAllChildrens(control);

  return allControls.Where(c => c.GetType() == type).Cast<T>();
}

private static IEnumerable<Control> GetAllChildrens(Control control)
{
  var controls = control.Controls.Cast<Control>();
  return controls.SelectMany(c => GetAllChildrens(c))
    .Concat(controls);
}
ted
  • 41
  • 1
0

Here is a tested and working generic solution:

I have a large number UpDownNumeric controls, some in the main form, some in groupboxes within the form. I want only the one last selected control to change back-color to green, for which I first set all others to white, using this method: (can also expand to grandchildren)

    public void setAllUpDnBackColorWhite()
    {
        //To set the numericUpDown background color of the selected control to white: 
        //and then the last selected control will change to green.

        foreach (Control cont in this.Controls)
        {
           if (cont.HasChildren)
            {
                foreach (Control contChild in cont.Controls)
                    if (contChild.GetType() == typeof(NumericUpDown))
                        contChild.BackColor = Color.White;
            }
            if (cont.GetType() == typeof(NumericUpDown))
                cont.BackColor = Color.White;
       }
    }   
samtal
  • 131
  • 2
  • 12
0

You can try this if you want :)

    private void ClearControls(Control.ControlCollection c)
    {
        foreach (Control control in c)
        {
            if (control.HasChildren)
            {
                ClearControls(control.Controls);
            }
            else
            {
                if (control is TextBox)
                {
                    TextBox txt = (TextBox)control;
                    txt.Clear();
                }
                if (control is ComboBox)
                {
                    ComboBox cmb = (ComboBox)control;
                    if (cmb.Items.Count > 0)
                        cmb.SelectedIndex = -1;
                }

                if (control is CheckBox)
                {
                    CheckBox chk = (CheckBox)control;
                    chk.Checked = false;
                }

                if (control is RadioButton)
                {
                    RadioButton rdo = (RadioButton)control;
                    rdo.Checked = false;
                }

                if (control is ListBox)
                {
                    ListBox listBox = (ListBox)control;
                    listBox.ClearSelected();
                }
            }
        }
    }
    private void btnClear_Click(object sender, EventArgs e)
    {
        ClearControls((ControlCollection)this.Controls);
    }
rashi
  • 1
  • 1
  • 1
    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. – leigero Aug 13 '15 at 13:54
  • The question didn't say anything about clearing the form. – LarsTech Aug 13 '15 at 21:20
  • Yes, doesn't answer "the question", but is a nice addition to it. Thank you! –  May 26 '16 at 17:27
0

This may work:

Public Function getControls(Of T)() As List(Of T)
    Dim st As New Stack(Of Control)
    Dim ctl As Control
    Dim li As New List(Of T)

    st.Push(Me)

    While st.Count > 0
        ctl = st.Pop
        For Each c In ctl.Controls
            st.Push(CType(c, Control))
            If c.GetType Is GetType(T) Then
                li.Add(CType(c, T))
            End If
        Next
    End While

    Return li
End Function

I think the function to get all controls you are talking about is only available to WPF.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex Rouillard
  • 777
  • 8
  • 9
0

Although several other users have posted adequate solutions, I'd like to post a more general approach that may be more useful.

This is largely based on JYelton's response.

public static IEnumerable<Control> AllControls(
    this Control control, 
    Func<Control, Boolean> filter = null) 
{
    if (control == null)
        throw new ArgumentNullException("control");
    if (filter == null)
        filter = (c => true);

    var list = new List<Control>();

    foreach (Control c in control.Controls) {
        list.AddRange(AllControls(c, filter));
        if (filter(c))
            list.Add(c);
    }
    return list;
}
JamesFaix
  • 8,050
  • 9
  • 37
  • 73
0
    public static IEnumerable<T> GetAllControls<T>(this Control control) where T : Control
    {
        foreach (Control c in control.Controls)
        {
            if (c is T)
                yield return (T)c;
            foreach (T c1 in c.GetAllControls<T>())
                yield return c1;
        }
    }
Koray
  • 1,768
  • 1
  • 27
  • 37
0
    public IEnumerable<T> GetAll<T>(Control control) where T : Control
    {
        var type = typeof(T);
        var controls = control.Controls.Cast<Control>().ToArray();
        foreach (var c in controls.SelectMany(GetAll<T>).Concat(controls))
            if (c.GetType() == type) yield return (T)c;
    }
0

For anyone looking for a VB version of Adam's C# code written as an extension of the Control class:

''' <summary>Collects child controls of the specified type or base type within the passed control.</summary>
''' <typeparam name="T">The type of child controls to include. Restricted to objects of type Control.</typeparam>
''' <param name="Parent">Required. The parent form control.</param>
''' <returns>An object of type IEnumerable(Of T) containing the control collection.</returns>
''' <remarks>This method recursively calls itself passing child controls as the parent control.</remarks>
<Extension()>
Public Function [GetControls](Of T As Control)(
    ByVal Parent As Control) As IEnumerable(Of T)

    Dim oControls As IEnumerable(Of Control) = Parent.Controls.Cast(Of Control)()
    Return oControls.SelectMany(Function(c) GetControls(Of T)(c)).Concat(oControls.Where(Function(c) c.GetType() Is GetType(T) Or c.GetType().BaseType Is GetType(T))
End Function

NOTE: I've added BaseType matching for any derived custom controls. You can remove this or even make it an optional parameter if you wish.

Usage

Dim oButtons As IEnumerable(Of Button) = Me.GetControls(Of Button)()
SteveCinq
  • 1,920
  • 1
  • 17
  • 22
0

VISUAL BASIC VB.NET For some of us, who refuse to port 230,000+ lines of code to c# here is my contribution, if only an specific type required, just add a 'where' as needed.

Private Shared Function getAll(control As Control) As IEnumerable(Of Control)
    Return control.Controls.Cast(Of Control) _
        .SelectMany(Function(f) getAll(f).Concat(control.Controls.Cast(Of Control)))
End Function
fcm
  • 1,247
  • 15
  • 28
-1

Simply:

For Each ctrl In Me.Controls.OfType(Of Button)()
   ctrl.Text = "Hello World!"
Next
Leebeedev
  • 2,126
  • 22
  • 31
  • 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". – ChrisPBacon Feb 04 '20 at 15:01