FindControl mal anders: iterativ, rekursiv, generisch mit Bedingungen!
In ASP.NET müssen ab und zu Controls gefunden werden, meistens ist das der Fall wenn sie dynamisch hinzugefügt worden sind oder sich in einem wiederholendem Template befinden.
In ein paar diesen Fällen braucht es auch eine rekursive Suche nach einem bestimmten Control, ggf. mit anderen Bedingungen als der ID selber.
Okay, ein einfaches Beispiel für die <Control>.FindControl(<ID>)-Methode von ASP.NET:
Label label = (Label)<Container>.FindControl(<ID>)
Der Container stellt das Control dar, in dem oder dessen Kindern sich das gesuchte Control befindet.
FindControl gibt ein Objekt vom Typ “Control” zurück, um auf konkretere Eigenschaften / Methoden Zugriff zu bekommen, muss in den konkreten Typ gecastet werden, hier bspw. “Label”.
Geht das nicht irgendwie einfacher?
…doch das geht einfacher!
Praktisches Beispiel mit der originalen Variante:
Button b = Form.FindControl("btnSubmit") as Button;
Beispiel mit der der Extension Methode die ich gleich vorstellen werde:
Button button = Form.FindControl<Button>("btnTest");
Was ist der Unterschied?
Wir geben den gesuchten Typen an und ersparen und so einen Cast.
Wenn nichts gefunden wurde, kommt wie gehabt “null” zurück.
Die Extension Methode muss in einer statischen Klasse stehen und sieht wie folgt aus:
/// <summary>
/// Findet ein Control anhand einer ID und gibt es typisiert zurück.
/// (Casting entfällt im Unterschied zum Standard-FindControl.
/// </summary>
/// <typeparam name="T">Typ des zu findenen Controls.</typeparam>
/// <param name="source">Die Quelle für die Suche.</param>
/// <param name="id">Die gesuchte Control ID.</param>
/// <returns>Das gefundene Control oder null.</returns>
public static T FindControl<T>(this Control source, string id) where T : Control {
return source.FindControl(id) as T;
}
Okay, was ist jetzt wenn ich innerhalb eines Baumes suchen muss / will?
Dabei gibt es zwei Möglichkeiten, die iterative oder die rekursive:
Rekursiv:
public static Control FindControlRecursive(this Control root, string id)
{
if (root.ID == id)
{
return root;
}
foreach (Control c in root.Controls)
{
Control t = FindControlRecursive(c, id);
if (t != null)
{
return t;
}
}
return null;
}
Iterativ:
public static Control FindControlIterative(this Control root, string id)
{
if (root.ID == id)
{
return root;
}
Stack<Control> stack = new Stack<Control>();
stack.Push(root);
while (stack.Count > 0)
{
Control control = stack.Pop();
if (control.ID == id)
{
return control;
}
foreach (Control c in control.Controls)
{
stack.Push(c);
}
}
return null;
}
Beide Varianten funktionieren ein bisschen anders, verhalten sich aber gleich: Es wird das gesuchte Control zurückgegeben oder null.
Mithilfe von Generics, Funktionszeigern (delegates, hier einen vom Typ Predicate) können wir ganz interessante Dinge erreichen.
Bei dem Parameter “predicate” kann ein Lambda-Ausdruck oder eine anonyme Methode benutzt werden um eine Bedingung zu stellen. Ist diese erfüllt ist es das korrekte Control und es wird zurückgeliefert.
In der Methode mit dem String-Parameter (id) sieht man ein Beispiel, wie so etwas umgesetzt werden kann.
/// <summary>
/// Findet ein Control rekursiv anhand einer ID.
/// </summary>
/// <typeparam name="T">Typ des zu findenden Controls.</typeparam>
/// <param name="source">Die Quelle für die Suche.</param>
/// <param name="id">Die gesuchte Control ID.</param>
/// <returns>Das gefundene Control oder null.</returns>
public static T FindControlRecursive<T>(this Control source, string id) where T : Control {
return source.FindControlRecursive<T>(c => c.ID == id);
}
/// <summary>
/// Findet ein Control rekursiv anhand eines Predicates.
/// </summary>
/// <typeparam name="T">Typ des zu findenden Controls.</typeparam>
/// <param name="source">Die Quelle für die Suche.</param>
/// <param name="predicate">Die Bedingung für einen Treffer.</param>
/// <returns>Das gefundene Control oder null.</returns>
public static T FindControlRecursive<T>(this Control source, Predicate<T> predicate) where T : Control {
if (source == null) {
throw new ArgumentNullException("source");
}
if (predicate == null) {
throw new ArgumentNullException("predicate");
}
T foundControl = source as T;
if (foundControl != null && predicate(foundControl)) {
return foundControl;
}
foreach (Control control in source.Controls) {
foundControl = control.FindControlRecursive(predicate);
if (foundControl != null) {
return foundControl;
}
}
return null;
}
Anwendung bspw.
Label label = Form.FindControlRecursive<Label>(l => l.CssClass == "test");
label.Text = "found it..!";
Das findet das erste Label mit der CSS-Klasse “test”.
Es können ohne Probleme mehrere Bedingungen gestellt und verknüpft werden.