La methode FindControl
La méthode FindControl permet de trouver un contrôle à partir de son ID. Sans que l'on s'en rende compte, on l'utilise de plus en plus souvent. En effet ASP.net Ajax l'utilise très souvent en interne, ainsi lorsque l'on définit un trigger sur un UpdatePanel ou lorsque l'on renseigne la propriété AssociatedUpdatePanelID d'un UpdateProgress voir encore la propriété TargetControlID d'un extender, alors ASP.net utilise la méthode FindControl.
<asp:Button ID="btn1" runat="server" Text="go" OnClick="btn1_Click" />
<asp:UpdatePanel ID="up1" runat="server" >
<ContentTemplate>
<%=DateTime.Now.ToLongTimeString()%>
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn1" />
</Triggers>
</asp:UpdatePanel>
<asp:updateprogress id="upg1" runat="server" AssociatedUpdatePanelID="up1">
<ProgressTemplate>
En cours ...
</ProgressTemplate>
</asp:updateprogress>
Il est donc intéressant de savoir comment fonctionne cette méthode en détail. La description de la méthode FindControl dans msdn nous donne très peu d'information :
Searches the current naming container for a server control with the specified id parameter.
En utilisant Reflector on comprend mieux comment fonctionne cette méthode. Pour vous l'expliquer, voici une version simplifiée de son code :
protected virtual Control FindControl(string id)
{
// vérification que les contrôles enfants sont bien créer
this.EnsureChildControls();
/// this.flags[0x80] est vrai seulement si l'instance en cours implémente INamingContainer
/// ce bloc nous informe que la méthode FindControl appelle la méthode FindControl
/// de son NamingContainer ou de lui même s'il implémente INamingContainer
if (!this.flags[0x80])
{
Control namingContainer = this.NamingContainer;
if (namingContainer != null)
{
return namingContainer.FindControl(id, pathOffset);
}
return null;
}
// this._occasionalFields.NamedControls est un dictionnaire d'id/control qui contient
// tous les contrôles du namingContainer en cours
if (this.HasControls() && (this._occasionalFields.NamedControls == null))
{
/// Cette méthode va au final appeler la méthode FillNamedControlsTable qui est
/// une fonction récursive ajoutant dans le dictionnaire _occasionalFields.NamedControls
/// tous les contrôles enfants à l'exception des enfants des contrôle implémentant
/// l'interface INamingContainer ou des contrôles dont l'id n'a pas été spécifié.
this.EnsureNamedControlsTable();
}
int num = id.IndexOfAny(new char[] { '$', ':' });
/// si l'id ne contient pas le caractère $ ou : alors on retourne directement le contrôle
/// présent dans le namingcontainer en cours
if (num == -1)
{
// retourne l'élément demandé du dictionnaire
return (this._occasionalFields.NamedControls[id] as Control);
}
/// sinon on cherche le contrôle "parent" puis on recherche via la méthode FindControl
/// l'id sur le contrôle "parent". En vrai c'est implémenté différemment mais
/// c'est l'idée qu'il faut retenir
else
{
return ((Control)this._occasionalFields.NamedControls[id]).FindControl(
id.Substring(num + 1,id.Length - num));
}
}
La première chose intéressante est que la méthode FindControl recherche les contrôles contenus par les NamingContainer c'est à dire les contrôles qui implémente INamingContainer. La seconde chose, est que si l'on veut faire une recherche d'un namingcontrol à un autre on peut avoir une notion de hiérarchie en utilisant les symboles $ ou : pour séparer les contrôles.
Par exemple le contrôle Login implémente INamingContainer puisqu'il hérite de CompositeControl. Ce contrôle est constitué de différentes textbox. Si l'on veut récupérer l'instance d'une textbox on peut alors faire :
// contient l'instance de la textbox du password contenu dans le controle login
TextBox tbPassword = (TextBox)Page.FindControl("login1:Password");
Lorsque l'on définit un trigger sur un UpdatePanel :
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btn1" />
</Triggers>
On utilise au final la méthode ControlUtil.FindTargetControl :
internal static Control FindTargetControl(string controlID, Control control,
bool searchNamingContainers)
{
if (searchNamingContainers)
{
Control namingContainer = control;
Control control2 = null;
while ((control2 == null) && (namingContainer != control.Page))
{
namingContainer = namingContainer.NamingContainer;
if (namingContainer == null)
{
return control2;
}
control2 = namingContainer.FindControl(controlID);
}
return control2;
}
return control.FindControl(controlID);
}
Le paramètre controlID correspond à la valeur de la propriété ControlID, control est l'UpdatePanel sur lequel on a ajouté le trigger et searchNamingContainers est vrai dans le cas d'un AsyncPostBackTrigger et faux dans le cas d'un PostBackTrigger. Lorsqu'on utilise un AsyncPostBackTrigger on se rend alors compte que la recherche se fait en remontant les NamingContainer.