Loop through all controls of a Form, even those in GroupBoxes

64,745

Solution 1

The Controls collection of Forms and container controls contains only the immediate children. In order to get all the controls, you need to traverse the controls tree and to apply this operation recursively

private void AddTextChangedHandler(Control parent)
{
    foreach (Control c in parent.Controls)
    {
        if (c.GetType() == typeof(TextBox)) {
            c.TextChanged += new EventHandler(C_TextChanged);
        } else {
            AddTextChangedHandler(c);
        }
    }
}

Note: The form derives (indirectly) from Control as well and all controls have a Controls collection. So you can call the method like this in your form:

AddTextChangedHandler(this);

A more general solution would be to create an extension method that applies an action recursively to all controls. In a static class (e.g. WinFormsExtensions) add this method:

public static void ForAllControls(this Control parent, Action<Control> action)
{
    foreach (Control c in parent.Controls) {
        action(c);
        ForAllControls(c, action);
    }
}

The static classes namespace must be "visible", i.e., add an appropriate using declaration if it is in another namespace.

Then you can call it like this, where this is the form; you can also replace this by a form or control variable whose nested controls have to be affected:

this.ForAllControls(c =>
{
    if (c.GetType() == typeof(TextBox)) {
        c.TextChanged += C_TextChanged;
    }
});

Solution 2

A few simple, general purpose tools make this problem very straightforward. We can create a simple method that will traverse an entire control's tree, returning a sequence of all of it's children, all of their children, and so on, covering all controls, not just to a fixed depth. We could use recursion, but by avoiding recursion it will perform better.

public static IEnumerable<Control> GetAllChildren(this Control root)
{
    var stack = new Stack<Control>();
    stack.Push(root);

    while (stack.Any())
    {
        var next = stack.Pop();
        foreach (Control child in next.Controls)
            stack.Push(child);
        yield return next;
    }
}

Using this we can get all of the children, filter out those of the type we need, and then attach the handler very easily:

foreach(var textbox in GetAllChildren().OfType<Textbox>())
    textbox.TextChanged += C_TextChanged;

Solution 3

Try this

AllSubControls(this).OfType<TextBox>().ToList()
    .ForEach(o => o.TextChanged += C_TextChanged);

where AllSubControls is

private static IEnumerable<Control> AllSubControls(Control control)
    => Enumerable.Repeat(control, 1)
       .Union(control.Controls.OfType<Control>()
                              .SelectMany(AllSubControls)
             );

LINQ is great!

Solution 4

Haven't seen anyone using linq and/or yield so here goes:

public static class UtilitiesX {

    public static IEnumerable<Control> GetEntireControlsTree(this Control rootControl)
    {
        yield return rootControl;
        foreach (var childControl in rootControl.Controls.Cast<Control>().SelectMany(x => x.GetEntireControlsTree()))
        {
            yield return childControl;
        }
    }

    public static void ForEach<T>(this IEnumerable<T> en, Action<T> action)
    {
        foreach (var obj in en) action(obj);
    }
}

You may then use it to your heart's desire:

someControl.GetEntireControlsTree().OfType<TextBox>().ForEach(x => x.Click += someHandler);

Solution 5

you can only loop through open forms in windows forms using form collection for example to set windows start position for all open forms:

public static void setStartPosition()
        {
            FormCollection fc = Application.OpenForms;

            foreach(Form f in fc)
            {
                f.StartPosition = FormStartPosition.CenterScreen;
            }
        }
Share:
64,745
RRM
Author by

RRM

Struggling with C#,SQL and Crystal Reports

Updated on September 28, 2020

Comments

  • RRM
    RRM almost 4 years

    I'd like to add an event to all TextBoxes on my Form:

    foreach (Control C in this.Controls)
    {
        if (C.GetType() == typeof(System.Windows.Forms.TextBox))
        {
            C.TextChanged += new EventHandler(C_TextChanged);
        }
    }
    

    The problem is that they are stored in several GroupBoxes and my loop doesn't see them. I could loop through controls of each GroupBox individually but is it possible to do it all in a simple way in one loop?

  • Walter Stabosz
    Walter Stabosz about 6 years
    I did not know that there was an alternative solution to recursion. Always learning.
  • Admin
    Admin over 3 years
    Nice explanation. Good Job!