37

If I wanted to find checked check boxes on an ASP.NET page I could use the following LINQ query.

var checkBoxes = this.Controls
                     .OfType<CheckBox>()
                     .TakeWhile<CheckBox>(cb => cb.Checked);

That works fine if the checkboxes are nested in the current control collection, but I'd like to know how to extend the search by drilling down into the control collections of the top-level controls.

The question was asked here:

Finding controls that use a certain interface in ASP.NET

And received non-LINQ answers, I already have my own version of a recursive control search on type and ID as extension methods, but I just wondered how easy this is to do in LINQ?

Community
  • 1
  • 1
PhilGriffin
  • 638
  • 1
  • 5
  • 10

3 Answers3

48

Take the type/ID checking out of the recursion, so just have a "give me all the controls, recursively" method, e.g.

public static IEnumerable<Control> GetAllControls(this Control parent)
{
    foreach (Control control in parent.Controls)
    {
        yield return control;
        foreach(Control descendant in control.GetAllControls())
        {
            yield return descendant;
        }
    }
}

That's somewhat inefficient (in terms of creating lots of iterators) but I doubt that you'll have a very deep tree.

You can then write your original query as:

var checkBoxes = this.GetAllControls()
                     .OfType<CheckBox>()
                     .TakeWhile<CheckBox>(cb => cb.Checked);

(EDIT: Changed AllControls to GetAllControls and use it properly as a method.)

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • That works, but if you were using third party controls and your own server controls as we do in my current workplace, you can get a good few levels of control nesting. I suppose an ASP.NET page is just an example of a more general problem, that of drilling down through nested collections in Linq. – PhilGriffin Oct 31 '08 at 15:52
  • I have used just this technique before. (If you want to get really sweet, you could do this as one expression with the Y Combinator!) BTW shouldn't AllControls be used as a method? – yfeldblum Oct 31 '08 at 16:03
  • 1
    @Phil: Yes, it may go down a few levels and be a little bit inefficient - but do you have any reason to believe it'll be a *significant* bottleneck? Do the simplest thing that works first, and then optimise if it's really a problem. (e.g. a single database call is likely to overwhelm this cost.) – Jon Skeet Oct 31 '08 at 16:07
  • @Jon, I've had a solution to the problem for years in the kind of recursive searches that are in the link in my question. Just trying to make my day more interesting reinventing the wheel, it is a Friday after all :) Thanks anyway. – PhilGriffin Oct 31 '08 at 17:48
  • Similar to new question: http://stackoverflow.com/questions/3419159/how-to-get-all-child-controls-of-a-winform-of-a-specific-type-button-textbox/3419209#3419209 -- I would be interested in knowing the correct way to use the `Descendants` LINQ function for recursive control selection. (Perhaps updating this question/answer also?) – JYelton Aug 06 '10 at 15:36
2
public static IEnumerable<Control> AllControls(this Control container)
{
    //Get all controls
    var controls = container.Controls.Cast<Control>();

    //Get all children
    var children = controls.Select(c => c.AllControls());

    //combine controls and children
    var firstGen = controls.Concat(children.SelectMany(b => b));

    return firstGen;
}

Now based on the above function, we can do something like this:

public static Control FindControl(this Control container, string Id)
{
    var child = container.AllControls().FirstOrDefault(c => c.ID == Id);
    return child;
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

My suggestion to make the AllControls recursive is:

    public static IEnumerable<Control> AllControls(this Control parent)
    {
        foreach (Control control in parent.Controls)
        {
             yield return control;
        }
        foreach (Control control in parent.Controls)
        {
            foreach (Control cc in AllControls(control)) yield return cc;
        }
    }

The second foreach looks weird, but this is the only way I know to "flatten" the recursive call.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131