ControlCollection, ID e padroni

di Marco Leoncini, in asp.net,
 Era un po? di tempo che mi assillava, il problema si riassume nel seguente post del forum :

link

in pratica l?obbiettivo è caricare differenti UserControl in base alle esigenze, gli attori sono questi:

la pagina (classe Page)
un placeholder (classe PlaceHolder)
un usercontrol (classe UserControl)
un bottone (classe Button) per caricare un UserControl differente nel PlaceHolder

il codice :

namespace TestUserConttrol
{ public class WebForm1 : System.Web.UI.Page
{
protected System.Web.UI.WebControls.Button carica;
protected System.Web.UI.WebControls.PlaceHolder inserisci;

private void Page_Load(object sender, System.EventArgs e)
{

string contenuto = "UserControl1.ascx";
if(this.ViewState["contenuto"] != null)
{
contenuto = (string)this.ViewState["contenuto"];
}
inserisci.Controls.Add(Page.LoadControl(contenuto));
}
<p />private void carica_Click(object sender, System.EventArgs e)
{
inserisci.Controls.Clear();
this.ViewState["contenuto"] = "UserControl2.ascx"; 
inserisci.Controls.Add(Page.LoadControl("UserControl2.ascx")); 
}
}
}

il problema è semplice gli eventi dei controlli caricati dinamicamente sembrano scatenarsi con un PostBack di ritardo, questo comportamento può essere corretto semplicemente assegnando un ID hai vari controlli caricati dinamicamente.

Ma nel nostro caso questo non viene fatto, cosa succede dietro le quinte?

Partiamo dalla proprietà Controls del tipo ControlCollection che tutte le sotto classi di Control ereditano, di conseguenza anche il PlaceHolder.

Il metodo Add della classe ControlCollection richiama il metodo AddedControl del controllo che contiene la collezione:

protected internal virtual void AddedControl(Control control, int index)
{
?<OMISSIS>?
control._parent = this;
control._page = this._page;
Control control1 = this.flags[0x80] ? this : this._namingContainer;
if (control1 != null)
{
control._namingContainer = control1;
if ((control._id == null) && !control.flags[0x40])
{
control.GenerateAutomaticID();
}
?<OMISSIS>?
}
?<OMISSIS>?
}

il metodo riceve come argomento il controllo aggiunto alla collezione, tra le varie operazione viene controllato che l?ID non sia nullo.
In caso affermativo ne viene generato uno richiamando il metodo GenerateAutomaticID

private void GenerateAutomaticID()
{
int num1 = this._namingContainer._namedControlsID++;
if (num1 < 0x80)
{
this._id = Control.automaticIDs[num1];
}
else
{
this._id = "_ctl" + num1.ToString(NumberFormatInfo.InvariantInfo);
}
this._namingContainer.DirtyNameTable();
}

l?id viene generato incrementando il campo _namedControlsID di tipo int del NamingContainer,
tale campo viene utilizzato per recuperare da un Array in nome da assegnare al controllo appena aggiunto.
Nel nostro caso il controllo aggiunto è un UserControl, e il suo NamingConteiner, impostato nel metodo AddedControl, con questa riga di codice Control control1 = this.flags[0x80] ? this : this._namingContainer; è la pagina.

Queste operazioni sono eseguite ogni volta che si aggiunge un controllo alla collezione.
come sappiamo i controlli creati dinamicamente devono essere ricreati ad ogni PostBack.

Questo è lo scenario:
al primo caricamento della pagina viene caricato UserControl1.ascx,
premiamo il pulsante per caricare il secondo controllo, nel PageLoad UserControl1 viene ricaricato, successivamente viene eseguito il metodo gestore dell?evento carica_Click, che pulisce la collezione dei controlli del PlaceHolder e aggiunge UserControl2.ascx.

nel nostro caso UserControl2 ha un LinkButton lo premiamo aspettandoci che venga eseguito il rispettivo gestore dell?evento. Ma nulla succede, caparbi lo premiamo ancora, stavolta il gestore viene richiamato?ma perche?

Abilitando il Trace a livello di pagina notiamo tra i vari post back cambi nell?id generato automaticamente per gli UserControl.

Il responsabile è il campo _namedControlsID che non viene azzerato anche eseguendo il metodo Clear() della collezione dei controlli.

Ecco il codice del metodo Clear:

public virtual void Clear()
{
?<OMISSIS>?
if (this._owner is INamingContainer)
{
this._owner.ClearNamingContainer();
}
}
}

il metodo celar dovrebbe effetivamte azzerare il campo _namedControlsID ma lo fa solo se il proprietario della collezione implementi l?interfaccia INamingContainer.
La collezione a cui vengono aggiunti i vari UserControl è di ?proprietà? del PlaceHolder che non implementa tale interfaccia, ma anche se lo facesse non servirebbe a niente in quando il campo _namedControlsID incrementato è quello della pagina.

Come azzerare allora _namedControlsID? Una rapita soluzione arriva usando la reflection.
Ecco il metodo carica_Click modificato

private void carica_Click(object sender, System.EventArgs e)
{
inserisci.Controls.Clear();
Type type = typeof(Control);
FieldInfo info = type.GetField("_namedControlsID", BindingFlags.NonPublic |BindingFlags.Instance);
info.SetValue(this,0);
this.ViewState["contenuto"] = "UserControl2.ascx"; 
inserisci.Controls.Add(Page.LoadControl("UserControl2.ascx"));

}

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Nella stessa categoria
I più letti del mese