Introduction
An often performed exercise by developers is to recursively iterate through all controls within a control container (like a Form
or Panel
) as well as all their sub-containers.
The code I list here is a simple yet very efficient generic way of doing this. It is by all means no world shocking revolutionary break-through but it is nice in its sheer simplicity!!
The Task
Say an invalid command has taken place and you wish to set all TextBox
es on your form to show "n/a". So you will have to recursively find all TextBox
es in this Form
's container and all its other sub-containers that it may contain.
An Inefficient Though Often Used Approach
An often used method would be to create a separate routine that brings back a list of TextBox
controls that can then be set to "n/a" within the calling routine. Something like this...
public static List<Control> GetControlsOfType(Control ctrMain)
{
List<Control> list = new List<Control>();
foreach (Control c in ctrMain.Controls)
{
if (c.GetType().Equals(typeof(TextBox)))
list.Add(c);
if (c.Controls.Count > 0)
list.AddRange(GetControlsOfType(c));
}
return list;
}
... used like this:
private void button1_Click(object sender, EventArgs e)
{
foreach (Control c in Utils.GetControlsOfType(this))
c.Text = "n/a";
}
This technique works but has some ugly disadvantages:
- What if the need arises to find all
Button
s instead of TextBox
es within your Form
? You will have to duplicate your code or put a switch in it to distinguish between control Types.
- You are creating a new
List
that is probably used only once after which it is earmarked for Garbage Collection. If you do this often and potentially with many more items, then this could have a real impact on your memory footprint and code efficiency. Remember that you are after all creating and populating a List
with ALL the items only to set a Text
property after which you no longer need this long list and dispose of it!!
The Generic IEnumerable Approach
Now have a look at the code below. By using Generics, we tackle the first issue of being bound to a specific Type
. We are now forced to specify the Type
we wish to search for when calling the method by specifying <T>
.
Then by using the yield return statement and returning the generic IEnumerable<T>
instead of a fully populated temporary List
, we are able to dramatically reduce our memory footprint and make the code much more efficient!!
public delegate bool delTypeCheck(Type type);
public static IEnumerable<T> GetControlsOfType<T>
(Control ctrMain, delTypeCheck IsExemptCheck) where T : class
{
Type searchType = typeof(T);
foreach (Control c in ctrMain.Controls)
{
if (IsExemptCheck != null && IsExemptCheck(c.GetType()))
continue;
if (c is T) yield return (c as T);
if (c.Controls.Count > 0)
foreach (T t in GetControlsOfType<T>(c, IsExemptCheck))
yield return t;
}
}
This is used like this.
If we want to find all controls that derive from TextBoxBase
(which includes TextBox
es as well as RichTextBox
) and set the Text
property of all these to "n/a"
, then implement something like the code below:
foreach (Control c in Utils.GetControlsOfType<TextBoxBase>(this, null))
{
c.Text = "n/a";
}
In addition, exclude certain Types from examining.
Say you want to exclude searching through some Type
s of containers (in this case Panel
), then you can supply an anonymous delegate
to exclude these (see code below). The 'IsExemptCheck
' delegate
hands over the Type
that is being examined and asks you to return whether it should be excluded or not. This gives the caller much more scope to also point at a more complex function to determine whether to examine a control. In this case, we simply want to exclude Panel
types so a simple anonymous delegate
suffices.
foreach (Control c in Utils.GetControlsOfType<TextBoxBase>
(this, delegate(Type MyType) { return MyType.Equals(typeof(Panel)); }))
{
c.Text = "n/a";
}
Please note : Don't forget the constraint of 'where T : class'
. We put this on to ensure that we will only deal with reference types. Otherwise the statement 'yield return (c as T)'
would fail since casting the control c
to type T
can obviously only occur with Control types (which are reference types!).
Just a Word
With this example, I simply tried to bring across a different way of looking at tackling these kind of tasks.
Using it within a Form
to find TextBox
es, you may not directly notice a speed or memory improvement but then again try using it for an iteration of List
(s) that contains 1000s of entries and you'll soon spot the difference! :)
History
- 1st June, 2007: Article published
- 6th June, 2007: Article updated - statement change to (
c
is T
) - thanks to PIEBALDconsult!