Jak přidat CheckBox do ListView?

Jedním z častých problémů jsou dotazy, jak pracovat s controly v databinding kontajnerech – jako je například klasický GridView a nebo nová součást ASP.NET 3.5, prvek ListView. Typický požadavek je přidat ke každé položce checkbox a pak získat seznam zaškrtnutých položek a nějak s ním pracovat. Dále pak je obvykle žádoucí mít k dispozici obvyklou funkcionalitu ve stylu “vybrat vše”, “invertovat výběr” atd.

Pro následující příklad používám jednoduchý XML soubor jako zdroj dat načítaný pomocí XmlDataSource (ale stejně tak dobře je možné použít databázi nebo cokoliv jiného):

<?xml version="1.0" encoding="utf-8" ?>

<data>

    <row id="JAN" name="leden" days="31" />

    <row id="FEB" name="únor" days="28" />

    <row id="MAR" name="březen" days="31" />

    <row id="APR" name="duben" days="30" />

    <row id="MAY" name="květen" days="31" />

    <row id="JUN" name="červen" days="30" />

    <row id="JUL" name="červenec" days="31" />

    <row id="AUG" name="srpen" days="31" />

    <row id="SEP" name="září" days="30" />

    <row id="OCT" name="říjen" days="31" />

    <row id="NOV" name="listopad" days="30" />

    <row id="DEC" name="prosinec" days="31" />

</data>

Pro deklarativní zobrazení dat používám prvek ListView. Do jeho LayoutTemplate jsem přidal tři tlačítka pro práci s výběrem. Dále na formuláři najdete tlačítko pro odeslání a jeden Literal, ve kterém se zobrazí výsledek – počet vybraných měsíců a celkový součet jejich dnů.

<asp:ListView ID="ListViewMonths" runat="server" DataSourceID="XmlDataSourceMonths" OnItemCommand="ListViewMonths_ItemCommand">

    <LayoutTemplate>

        <table>

            <tr>

                <th align="left">Měsíc</th>

                <th align="left">Dny</th>

            </tr>

            <asp:PlaceHolder ID="itemPlaceHolder" runat="server" />

        </table>

        <p>

            <asp:Button ID="Button1" runat="server" CommandName="check" CommandArgument="all" Text="vše" />

            <asp:Button ID="Button2" runat="server" CommandName="check" CommandArgument="none" Text="nic" />

            <asp:Button ID="Button3" runat="server" CommandName="check" CommandArgument="invert" Text="inv" />

        </p>

    </LayoutTemplate>

    <ItemTemplate>

        <tr>

            <td>

                <asp:CheckBox ID="CheckBoxMonth" runat="server" Text='<%# Eval("name") %>' />

            </td>

            <td align="right">

                <asp:Literal ID="LiteralDays" runat="server" Text='<%# Eval("days") %>' />

            </td>

        </tr>

    </ItemTemplate>

</asp:ListView>

<asp:XmlDataSource ID="XmlDataSourceMonths" runat="server" DataFile="~/App_Data/data.xml" />

<p>

    <asp:Button ID="ButtonSubmit" runat="server" Text="Odeslat" OnClick="ButtonSubmit_Click" />

</p>

<p>

    <asp:Literal ID="LiteralSelected" runat="server" />

</p>

Obecný princip práce s controly v templatovaných prvcích je jednoduchý: rodičovský prvek má nějakou kolekci opakujících se záznamů. V případě prvku GridView se jedná o kolekci Rows, v případě ListView se jmenuje genericky Items. Každý člen v této kolekci reprezentuje jeden nabindovaný záznam – ve výše uvedeném případě tedy obsah ItemTemplate. Má metodu FindControl, pomocí které můžeme najít jakýkoliv prvek, který je součástí šablony.

Tak můžeme snadno implementovat funkci hromadného o(d)značování a podobně. Vytvořil jsem si příkaz “check”, který má jako argument buďto “all” (označit vše), “none” (neoznačit nic) a nebo “invert” (změnit stav všech položek). V ošetření události ItemCommand pak projdu všechny záznamy a nastavím odpovídající hodnotu checkboxu:

protected void ListViewMonths_ItemCommand(object sender, ListViewCommandEventArgs e) {

    if (e.CommandName.Equals("check")) {

        foreach (var row in this.ListViewMonths.Items) {

            CheckBox c = row.FindControl("CheckBoxMonth") as CheckBox;

            switch (e.CommandArgument as string) {

                case "all":    // označit vše

                    c.Checked = true;

                    break;

                case "none":    // zrušit vše

                    c.Checked = false;

                    break;

                case "invert"// invertovat výběr

                    c.Checked = !c.Checked;

                    break;

                default:        // neznámý příkaz

                    break;

            }

        }

    }

}

Ve stejném duchu lze pokračovat ošetřením události Click na tlačítku ButtonSubmit:

protected void ButtonSubmit_Click(object sender, EventArgs e) {

    int totalDays = 0;

    int totalCount = 0;

 

    foreach (var row in this.ListViewMonths.Items) {

        CheckBox c = row.FindControl("CheckBoxMonth") as CheckBox;

        Literal l = row.FindControl("LiteralDays") as Literal;

 

        // Zpracovat zaškrtnuté měsíce

        if (c.Checked) {

            totalDays += int.Parse(l.Text);

            totalCount++;

        }

    }

 

    this.LiteralSelected.Text = string.Format("Počet vybraných měsíců: {0}, celkem dnů: {1}.",

        totalCount,

        totalDays);

}

Toto je obecný postup, jímž jest pracovati s controly, na které se nelze odkázat přímo názvem. Najdeme jejich kontajner a na něm zavoláme metodu FindControl, které jako řetězcový argument předáme identifikátor toho, co hledáme a výsledek odpovídajícím způsobem přetypujeme. V reálné praxi lze navíc doporučit robustní ošetření chyb, protože pokud z nějakého důvodu změníme název nebo typ prvku, aplikace skončí s běhovou chybou NullReferenceException, která se obtížně odlaďuje. Ve shora uvedeném případě by bylo vhodné ověřit si, zda hodnota proměnných c a l není null. Pokud ano, je něco špatně a je třeba se s tím graciézně vyrovnat.

  • Altairis
  • Nemesis
  • Microsoft MVP
  • IIS
  • ASP.NET