6

I have a Form named A.

A contains lots of different controls, including a main GroupBox. This GroupBox contains lots of tables and others GroupBoxes. I want to find a control which has e.g. tab index 9 in form A, but I don't know which GroupBox contains this control.

How can I do this?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Shailesh
  • 79
  • 1
  • 1
  • 3
  • i'd like to use the Map extension method from Marc for this kind of recursion: http://stackoverflow.com/questions/141467/recursive-list-flattening/229442#229442 – Oliver Apr 29 '10 at 07:27

5 Answers5

13

With recursion...

public static IEnumerable<T> Descendants<T>( this Control control ) where T : class
{
    foreach (Control child in control.Controls) {

        T childOfT = child as T;
        if (childOfT != null) {
            yield return (T)childOfT;
        }

        if (child.HasChildren) {
            foreach (T descendant in Descendants<T>(child)) {
                yield return descendant;
            }
        }
    }
}

You can use the above function like:

var checkBox = (from c in myForm.Descendants<CheckBox>()
                where c.TabIndex == 9
                select c).FirstOrDefault();

That will get the first CheckBox anywhere within the form that has a TabIndex of 9. You can obviously use whatever criteria you want.

If you aren't a fan of LINQ query syntax, the above could be re-written as:

var checkBox = myForm.Descendants<CheckBox>()
                     .FirstOrDefault(x=>x.TabIndex==9);
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Josh
  • 68,005
  • 14
  • 144
  • 156
  • Wow, that is an extreme, overly complicated way to find a control with a tab index of 9. Kudos for effort though. – Ed S. Apr 29 '10 at 06:43
  • 2
    Your method only finds a control with a tab index of 9. Mine is an extension method that effectively enables LINQ to WinForms. It's the same approach used by LINQ to XML. And it's hardly complicated, it's simple recursion like many of the other answers. The only difference being I'm filtering on type which makes the predicate easier to write. – Josh Apr 29 '10 at 06:46
  • Ok, I can add an int parameter and now it does that. Seriously though, you will never need any of that, you just need to find a control with a tab index equal to n, and people have to stare at that for at least a few seconds to even figure out what it does. Also, I would prefer to write Find( this, 9 ) instead of a query, but I didn't mean to be rude, this is a place for having some fun with code after all. – Ed S. Apr 29 '10 at 06:49
  • 3
    +1 for checking on the type and the yield return will also ensure that no more controls that need to be evaluated will be evaluated. Could also easily be applied to many other conditions without modifying the extension method and could check on properties specific to the type (like `IsChecked`) as well! @Ed Swangren what if you don't want to check on TabIndex anymore? A query (or predicate) is not a bad idea. – Cornelius Apr 29 '10 at 06:55
  • I guess you're not used to LINQ. I've shown a general method of finding one or more controls "n" levels deep that meet a given criteria. It's quite powerful (even for a 7 line function) and yes, I used it quite a bit when I used to write WinForms apps and I have a similar method for WPF now. – Josh Apr 29 '10 at 06:56
  • 1
    @Ed: the elegance of this solution appears when, in a near future, the need to find a control that has no child controls appears. You can reuse the code, just call it with a different comparison, instead of extending the code base with a new, nearly identical method for that new case. – Fredrik Mörk Apr 29 '10 at 06:57
  • Yeesh, mine was just a quick and dirty example. Honestly, if I saw that in production code and it was only ever used to find a tab index I would cringe a bit, but whatever, I never said it was wrong in any way, just overly engineered. – Ed S. Apr 29 '10 at 06:59
  • 1
    @EdS. Good luck on always writing a different piece of code that only does what it needs to that day. With Josh's code, you write it once only, test it once only, and use it whenever you need to find any control on any form. – ProfK Oct 09 '12 at 06:17
  • @ProfK: I don't know that I agree with the me of three years ago on this one, but I will say this; there is a fine line between writing reusable code and writing code you will never need. The latter is a complete waste of time (and money). And I don't need luck; I've done just fine so far. Better than fine actually. – Ed S. Jun 27 '13 at 17:30
  • I have to set styles for the different controls, so I need to get all controls, then check for each, `if (control is Button){//paint it pink etc}` so how would I get ALL controls, so I could then loop through and check each one? I could do the above answer for each control `type` I need, but that seems inefficient. And get all of type `` doesn't seem to work. – n00dles Jul 10 '17 at 15:16
4

Recursively search through your form's Controls collection.

void FindAndSayHi(Control control)
{
    foreach (Control c in control.Controls)
    {
        Find(c.Controls);
        if (c.TabIndex == 9)
        {
            MessageBox.Show("Hi");
        }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ed S.
  • 122,712
  • 22
  • 185
  • 265
2
void iterateControls(Control ctrl)
{
    foreach(Control c in ctrl.Controls)
    {
        iterateControls(c);
    }
}
thelost
  • 6,638
  • 4
  • 28
  • 44
2

You can make a method like this:

public static Control GetControl(Control.ControlCollection controlCollection, Predicate<Control> match)
{
    foreach (Control control in controlCollection)
    {
        if (match(control))
        {
            return control;
        }

        if (control.Controls.Count > 0)
        {
            Control result = GetControl(control.Controls, match);
            if (result != null)
            {
                return result;
            }
        }
    }

    return null;
}

...that is used like this:

Control control = GetControl(this.Controls, ctl => ctl.TabIndex == 9);

Note however that TabIndex is a tricky case, since it starts at 0 within each container, so there may be several controls in the same form having the same TabIndex value.

Either way, the method above can be used for checking pretty much any property of the controls:

Control control = GetControl(this.Controls, ctl => ctl.Text == "Some text");
Fredrik Mörk
  • 155,851
  • 29
  • 291
  • 343
0

I hate recursion, so I always use a stack for this sort of thing. This assigns a common event handler to the CheckedChanged event of every RadioButton control in the current control hierarchy:

Stack<Control> controlStack = new Stack<Control>();
foreach (Control c in this.Controls)
{
    controlStack.Push(c);
}
Control ctl;
while (controlStack.Count > 0 && (ctl = controlStack.Pop()) != null)
{
    if (ctl is RadioButton)
    {
        (ctl as RadioButton).CheckedChanged += new EventHandler(rb_CheckedChanged);
    }
    foreach (Control child in ctl.Controls)
    {
        controlStack.Push(child);
    }
}

You could easily retrofit Josh Einstein's extension method to work this way.

Paul Smith
  • 3,104
  • 1
  • 32
  • 45