.NET Tip #35: Jak je to se šablonami v ASP.NET aneb jak přistupovat ke komponentě, když to nejde přes její ID?

Tomáš Herceg       3. 11. 2009       C#, ASP.NET/IIS, .NET Tips       6991 zobrazení

Na fórech se velmi často setkávám s dotazy, jak přistupovat příkladně ke komponentě definované v HeaderTemplate u komponenty Repeater, případně jak změnit stav zaškrtnutí políčka v řádku GridView, v němž se právě kliknulo na tlačítko?

V prvním případě to některým začátečníkům připadá skoro jako chyba v ASP.NET – proč když udělám tlačítko s ID Button1, není sakra možné v kódu napsat Button1.Text = “blabla”? U druhého případu je to trochu jasnější, když je v GridView řádků víc, asi nemůžeme přistupovat ke komponentě podle ID, když je v každém řádku. Jak z toho ven?

Co je naming container?

Pro začátek si vysvětlíme, co to je tzv. naming container. Jak již bylo řečeno v předchozím odstavci, někdy máme zdánlivě ve stránce více komponent se stejným ID. Když se ale podíváme do výsledného HTML, které jde do prohlížeče, uvidíme, že komponenty do svého ID dostávají speciální prefixy, např. GridView1_ctl00_CheckBox1 atd.

O tohle se právě stará koncept naming containerů. Naming container je jakási část stránky, v rámci níž jsou ID komponent unikátní. Pokud si třeba uděláte vlastní User Control (komponenta s příponou ASCX), je tato komponenta naming containerem, protože uvnitř ní musí být všechny ID komponent unikátní. Ale komponentu jako takovou můžete použít ve stránce, kde jiná komponenta má ID, které se používá uvnitř oné ASCX komponenty.

Obdobně každá položka Repeateru nebo každý řádek v GridView je naming container, protože v rámci řádku musí být všechna ID unikátní, ale v každém řádku může být TextBox1.

Je zřejmé, že naming containery do sebe můžeme libovolně vnořovat. Komponenty z vnitřního naming containeru dostanout takový prefix, aby nekolidovaly s ostatními naming containery na stejné nebo vyšší úrovni.

Jak s komponentou pracovat z kódu?

Pomocí ID komponenty můžeme pracovat pouze s komponentami v top-level (nejvyšším) naming containeru, kterýmžto je celá stránka. Pokud je komponenta třeba uvnitř komponenty Panel, přistupovat k ní přes ID můžeme, protože Panel není naming container (není k tomu důvod, Panel je jen .NETí obálka HTML elementu div).

Pokud ale komponenta v nějakém vnořeném kontejneru, není možné k ní přistupovat pomocí ID. Jde to pomocí funkce FindControl daného naming containeru. Této funkci předáme ID hledané komponenty a na oplátku dostaneme objekt typu Control (ze kterého dědí všechny komponenty), anebo null, pokud taková komponenta neexistuje. Je nutné si uvědomit, že pokud zadáme metodě FindControl ID komponenty, tato metoda nehledá rekurzivně do hloubky, ale pouze mezi svými přímými potomky.

Protože metoda FindControl vrací typ Control, ale my většinou chceme pracovat s vlastnostmi specifickými pro konkrétní komponentu (třeba s vlastností Text u TextBoxu nebo s vlastností Checked u CheckBoxu), musíme si výsledek přetypovat (ve VB.NET třeba pomocí DirectCast, v C# standardním operátorem přetypování).

Příklad

Mějme třeba stránku s jednoduchou tabulkou:

         <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" Width="600px">
<Columns>
<asp:BoundField HeaderText="Název služby" ItemStyle-Width="80%" DataField="Title" />
<asp:TemplateField HeaderText="Aktivní">
<ItemTemplate>
<asp:CheckBox ID="CheckBox1" runat="server" Enabled="false" Checked='<%# Eval("Active") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField>
<ItemTemplate>
<asp:Button ID="Button1" runat="server" Text="Změnit stav" />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>

Tuto tabulku plníme testovacími daty v události Page_Load, jen při prvním načtení (už ne při PostBacku, o zapamatování si hodnot se stará ViewState).

     protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var data = new[]
{
new { Title = "Web Server", Active = true },
new { Title = "Mail Server", Active = true },
new { Title = "Source Control", Active = false },
new { Title = "Streaming Server", Active = true },
new { Title = "VPN Server", Active = true },
};

GridView1.DataSource = data;
GridView1.DataBind();
}
}

Celá stránka pak vypadá v prohlížeči nějak takto.

Tabulka v prohlížeči

Jak taková tabulka vlastně vznikne aneb jak fungují šablony?

V deklarativním popisu komponenty GridView máme definovány tří sloupce – první je BoundField, další dva jsou TemplateFieldy. Tyto mají uvnitř definovanou šablonu ItemTemplate.

Komponenty uvnitř šablon nejsou samy o sobě jako takové součástí stránky, ItemTemplate a jiné elementy končící Template jsou pouze šablony. Jejich obsah může být ve stránce mnohokrát, jednou anebo vůbec, to záleží na logice komponenty.

Při kompilaci stránky se z našeho elementu ItemTemplate vygeneruje na pozadí třída implementující rozhraní ITemplate. To definuje jedinou metodu InstantiateIn. Při jejím zavoláním se do předaného kontejneru (komponenta, která podporuje umístění komponent dovnitř sebe samotné) vygeneruje obsah šablony.

Komponenta GridView tedy pro každý řádek zavolá na šablonách sloupců metodu InstantiateIn, kterážto pak vytvoří komponenty v každém řádku.

Důvod, proč ke komponentám z šablon nemůžeme přistupovat přímo, je zjevný. Lépe je to ještě vidět u FormView. Ta má třeba definované šablony ItemTemplate a EditItemTemplate, ale zároveň není v obou režimech – buď edituje, nebo zobrazuje položku. Jedna ze šablon tedy vůbec ve stránce instanciovaná není, přístup k vnitřním komponentám přes IDčko by tedy hned vyhodil chybu.

Jak získat tlačítko ze čtvrtého řádku?

Každý řádek GridView je naming container a zároveň komponenta GridViewRow. Tyto komponenty jsou přístupné přes vlastnost Rows komponenty GridView. Jednoduše si tedy sáhneme na čtvrtou položku z kolekce Rows a máme řádek, v němž se tlačítko nachází. Pak již jen stačí přes FindControl vytáhnout samotné tlačítko.

         var btn = (Button)GridView1.Rows[3].FindControl("Button1");
btn.Text =
"Damn foxes!";

Přepnutí stavu pomocí tlačítka v řádku tabulky

Poslední věc, kterou si ukážeme, je změna zaškrtnutí CheckBoxu v řádku, na jehož tlačítko se kliknulo. Do události tlačítka Button1 stačí vložit tento kód. Aktuální naming container si najdeme pomocí vlastnosti NamingContainer parametru sender, což je komponenta, která událost vyvolala (v tomto případě ono tlačítko). V něm si už najdeme komponentu CheckBox1 a změníme její stav.

     protected void Button1_Click(object sender, EventArgs e)
{
var container = ((Control)sender).NamingContainer;
var chkBox = (CheckBox)container.FindControl("CheckBox1");
chkBox.Checked = !chkBox.Checked;
}

Jak je vidět, pokud je komponenta zahrabaná hlouběji ve stránce, nelze k ní přistupovat přímo přes její ID, je nutné najít ji přes FindControl z nejbližšího místa, k němuž se dostaneme.

Závěrem

Abych zodpověděl dotaz, kvůli kterému článek vznikl, ke komponentám ve FooterTemplate se dostanete přes DataList1.Controls[DataList1.Controls.Count – 1].FindControl(“TextBox1”). Docela blbé řešení, autoři DataListu evidentně byli líní přidat vlastnost Header a Footer, která by vrátila přímo řádek se záhlavím a zápatím. Změna se bohužel nechystá zatím ani do 4. verze ASP.NET, zkusím to navrhnout jako vylepšení.

Možná ale bude lepší přejít na ListView, osobně jsem DataList nikdy nepoužíval, přišel mi takový nadbytečný a nedodělaný.

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback