1

If I have a form with this container for example:

+---------------------------------panel 1----+
|                                            |
|   +------------------panel 2---+           |
|   |                            |           |
|   | textbox1                   |           |
|   | combobox1        checkBox1 |           |
|   +----------------------------+           |
|                                            |
|   +------------tableLayoutPanel1-+         |
|   |                              |         |
|   | textbox2                     |         |
|   +------------------------------+         |
|                                            |
|   +-------------FlowLayoutPanel1-+         |
|   |textbox3  Combobox2           |         |
|   +------------------------------+         |
|                                            |
+--------------------------------------------+

I already have a function for getting all controls of a certain type from a given container (with a recursive call to get even controls contained) :

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)));
    }

This works fine (here it returns textbox1, combobox1, checkbox1, textbox2, textbox3, combobox2)

Now, I want a new function with a similar behavior : get all controls from a container but which are not included in a certain type of container. In my example the function could return all controls contained in panel1 which are never contained in a tableLayoutPanel (here textbox1, combobox1, checkbox1, textbox3, combobox2).

I've tried :

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

And :

public static IEnumerable<T> FindAllChildrenByType2<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)))
            .Where<T>(ctrl => ctrl.GetType() != typeof(TableLayoutPanel));
    }

with the same result : I get a list of all controls even those which must have been excluded (textBox2 in the TableLayoutPanel in the example).

Any idea of where I got lost?

Jason Aller
  • 3,541
  • 28
  • 38
  • 38
LokiQan
  • 73
  • 7
  • What's the issue with what you've tried? – Ivan Stoev Nov 07 '16 at 15:56
  • I get all the controls even those in the tableLayoutPanel. – LokiQan Nov 08 '16 at 14:19
  • I can not reproduce the problem. `.Concat(controls .Where(ctrl => ctrl.GetType() != typeof(TableLayoutPanel)) .SelectMany(ctrl => FindAllChildrenByType(ctrl)) );` seems to exclude the textbox2 as you desire. How do you call this? Please see my [fiddle](https://dotnetfiddle.net/ihVf4w) – Georg Patscheider Nov 15 '16 at 17:30
  • In fact i've created an example to illustrate my issue and i've not tried to execute the program with it. In real, i use the function with a user control to initialize certain properties of contained controls and i've investigate the enumeration returned by the call to see that supposed ignored controls were added to the list. I suppose the bug is hidding elsewhere and i will search deeper... – LokiQan Nov 18 '16 at 13:49

1 Answers1

1

Based on this nice answer that uses a non-recursive method to traverse a tree structure I came up with this method:

public static IEnumerable<TControl> GetChildControls<TControl>(this Control root, 
    Type excludedContainer = null)
        where TControl : Control
{
    var queue = new Queue<Control>();
    queue.Enqueue(root);

    while (queue.Any())
    {
        var next = queue.Dequeue();
        
        if (!next.GetType().Equals(excludedContainer))
        foreach (Control child in next.Controls)
        {
            queue.Enqueue(child);
        }
        if (!next.HasChildren && next is TControl typed)
        {
            yield return typed;
        }
    }
}

Non-recursive methods are always preferred because they won't exhaust the call stack (although in this case that wouldn't be likely anyway).

It does essentially the same thing as the original: collect controls level-by-level in a while loop. I only replaced Stack by Queue so the controls are returned from top to bottom. And of course added the conditions you were looking for: filtered type and excluded container type.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291