Retrieve all WinForms controls using generics

Sometimes you just want to grab all controls of a specific type in a Form (or inside any container like Panel, GroupBox, TabPage, user control, etc.). Typical asks:

“How do I clear all the text boxes on this form?”
“How do I disable every button inside this panel?”

Below are two small approaches:

  • Version 1 (easy): a tiny recursive extension method that returns a List<T>.
  • Version 2 (LINQ twist): a composable version that returns IEnumerable<T> and avoids building an internal list.

Version 1 — The simple recursive method

We’ll add an extension method on Control that walks the tree and collects matches. It’s short, readable, and gets the job done.

using System.Collections.Generic;
using System.Windows.Forms;

public static class ControlExtensions
{
    public static List<T> GetControls<T>(this Control container) where T : Control
    {
        var controls = new List<T>();

        foreach (Control c in container.Controls)
        {
            if (c is T)
                controls.Add((T)c);

            // Recurse into children
            controls.AddRange(GetControls<T>(c));
        }

        return controls;
    }
}

Usage

// Clear all text boxes on the current form
this.GetControls<TextBox>().ForEach(tb => tb.Text = string.Empty);

// Disable all buttons inside a specific panel
myPanel.GetControls<Button>().ForEach(b => b.Enabled = false);

Applying several actions

You can centralize formatting/logic in a helper, then call it for each control:

// Apply a consistent look-and-feel to text boxes
this.GetControls<TextBox>().ForEach(ApplyFormat);

private void ApplyFormat(TextBox tb)
{
    tb.BackColor = System.Drawing.Color.LightGray;
    tb.ForeColor = System.Drawing.Color.Red;
    tb.Text = "hello";
    tb.TextAlign = HorizontalAlignment.Center;
}

Or inline with a lambda:

this.GetControls<TextBox>().ForEach(tb =>
{
    tb.BackColor = System.Drawing.Color.LightGray;
    tb.ForeColor = System.Drawing.Color.Red;
    tb.Text = "hello";
    tb.TextAlign = HorizontalAlignment.Center;
});

Version 2 — A LINQ-powered variant

If you prefer pipelines, we can lean into LINQ by first exposing Control.ControlCollection as IEnumerable<Control>, then composing from there. This version doesn’t allocate an internal list; it just streams items.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;

public static class ControlLinqExtensions
{
    // Make Control.ControlCollection LINQ-friendly
    public static IEnumerable<Control> AsEnumerable(this Control.ControlCollection @this)
    {
        foreach (var control in @this)
            yield return (Control)control;
    }

    // Get all controls of type T (exact type match, see note below)
    public static IEnumerable<T> GetAllControls<T>(this Control @this) where T : Control
    {
        return @this.Controls.AsEnumerable().Where(x => x.GetType() == typeof(T)).Select(y => (T)y)
            .Union(
                @this.Controls.AsEnumerable()
                    .SelectMany(child => GetAllControls<T>(child))
                    .Select(y => (T)y)
            );
    }

    // Optional: ForEach for IEnumerable<T>
    public static void ForEach<T>(this IEnumerable<T> @this, Action<T> action)
    {
        foreach (var t in @this) action(t);
    }
}

Usage

// Disable all text boxes on the current form
this.GetAllControls<TextBox>().ForEach(tb => tb.Enabled = false);

// Apply multiple changes inline
this.GetAllControls<TextBox>().ForEach(tb =>
{
    tb.BackColor = System.Drawing.Color.LightGray;
    tb.ForeColor = System.Drawing.Color.Red;
    tb.Text = "hello";
    tb.TextAlign = HorizontalAlignment.Center;
});

Notes & tiny improvements

  • The example uses x.GetType() == typeof(T) which matches the exact type only.
    If you want to include subclasses (e.g., a custom MyTextBox : TextBox), swap the filter to:

    // Include derived types too
    return @this.Controls.AsEnumerable()
        .OfType<T>() // equivalent to "is T" + cast
        .Union(@this.Controls.AsEnumerable().SelectMany(child => child.GetAllControls<T>()));
  • Returning IEnumerable<T> is nice for composability (filter more, Take(n), etc.), while the List<T> variant is convenient for a quick .ForEach(...).


Which one should I use?

  • Want straightforward and familiar? Use Version 1 (List<T>)—it’s very readable and easy to step through in a debugger.
  • Prefer lazy, pipeline-y code? Use Version 2 (LINQ)—no internal list, and it composes nicely with other LINQ operators.

Either way, you’ll be able to grab controls quickly and apply whatever operation you need.

Happy coding! 😄

Retrieve all WinForms controls using generics

Author

Lluis Franco

Publish Date

11 - 09 - 2010