Breadth first search of C# Control Collection

1.2k views Asked by At

I'm trying to write an extension method for System.Web.UI.Control which will search its ControlCollection for an instance of specific Type, and return the first instance found. Depth-first is simple, however I want to search breadth first so that higher collections take priority.

My current method is flawed and will exit early, returning null in certain cases where the entire search wasn't completed. I'm hoping I'm close to the right solution but need some fresh eyes on it. Any suggestions?

public static T FindFirstControlOfType<T>(this Control rootControl, bool searchRecursively) where T : Control
{
    // list for container controls
    List<Control> controlsWithChildren = new List<Control>();

    // iterate the current control collection first
    foreach (Control child in rootControl.Controls)
    {
        if (child.GetType().IsAssignableFrom(typeof(T)))
        {
            return (T)child;
        }

        // track those controls containing children
        if (child.HasControls())
        {
            controlsWithChildren.Add(child);
        }
    }

    // if recursion is enabled, search the child nodes
    if (searchRecursively)
    {
        foreach (Control control in controlsWithChildren)
        {
            return FindFirstControlOfType<T>(control, true);
        }
    }

    // if never found, return null
    return null;
}

EDIT - Working Solution based on the marked answer, for anyone interested:

public static T FindFirstControlOfType<T>(this Control rootControl, bool searchNestedControls) where T : Control
{
    Queue<Control> queue = new Queue<Control>();

    EnqueueChildControls(queue, rootControl);
    while (queue.Count > 0)
    {
        Control current = queue.Dequeue();

        if (current.GetType() == typeof(T))
        {
            return (T)current;
        }

        if (searchNestedControls)
        {
            EnqueueChildControls(queue, current);
        }
    }
    return null;

}

private static void EnqueueChildControls(Queue<Control> queue, Control control)
{
    foreach (Control current in control.Controls)
    {
        queue.Enqueue(current);
    }
}
2

There are 2 answers

5
Andre Loker On BEST ANSWER

Breadth first search (pseudo code)

Queue fringe 

fringe.Enqueue(rootControl)

while(fringe.Count > 0)
{
    current = fringe.Dequeue()
    Visit(current)
    fringe.EnqueueAll(current.Children)
}

There's no need for recursion for breadth first search.

Visit is whatever you want to do with the current object. If you're searching for something then Visit would mean: if(current matches criteria) return current

Update: Working example: https://gist.github.com/1960108

0
Chris Shain On

Well the main problem is here:

foreach (Control control in controlsWithChildren)
{
    return FindFirstControlOfType<T>(control, true);
}

You are returning the results of the recursive search on the very first child with children, regardless of whether that search succeeded. Breadth-first search is a widely-discussed topic, but it boils down to using a queue:

Queue<Control> controlsWithChildren = new Queue<Control>();

and (usually) a loop rather than recursion.