Шаблоны GridView

114

До сих пор в примерах элемент управления DataGrid использовался для отображения данных с помощью отдельно привязанных столбцов для каждого поля. Если требуется поместить множественные значения в одну ячейку или иметь неограниченные возможности настройки содержимого ячейки, добавляя HTML-дескрипторы и серверные элементы управления, то для этого нужно применять столбец типа TemplateField.

TemplateField позволяет определять полностью настраиваемый шаблон для столбца. Внутрь шаблона можно добавлять дескрипторы элементов управления, произвольные элементы HTML и выражения привязки данных. Вы получаете полную свободу для отображения чего угодно.

Например, предположим, что необходимо создать столбец, который комбинирует поля имени и фамилии сотрудника из тестовой базы данных Northwind. Чтобы выполнить такой трюк, можно сконструировать примерно такой TemplateField:

<asp:GridView ID="gridEmployees" runat="server" DataSourceID="getEmployeesSDS" ...>

            <Columns>
                <asp:ButtonField DataTextField="EmployeeID" ButtonType="Button" CommandName="Select" />
                <asp:TemplateField HeaderText="Name">
                    <ItemTemplate>
                        <%# Eval("FirstName") %> <%# Eval("LastName") %>
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField DataField="BirthDate" HeaderText="BirthDate" DataFormatString="{0:dd/MM/yyyy}" />
                <asp:BoundField DataField="City" HeaderText="City" />
            </Columns>

            ...
</asp:GridView>
        
<asp:SqlDataSource ID="getEmployeesSDS" runat="server"
            ConnectionString="<%$ ConnectionStrings:Northwind %>"
            SelectCommand="SELECT * FROM Employees" />

Если теперь привязать GridView, он извлечет информацию из источника данных и пройдет по коллекции элементов. Он обработает ItemTemplate для каждого элемента, вычислит выражения привязки данных и добавит сгенерированную HTML-разметку к таблице. Этот шаблон довольно прост — он всего лишь определяет два выражения привязки данных. После вычисления эти выражения преобразуются в обычный текст:

Простой шаблон данных

Как вы заметили, в этих выражениях используется Eval() — статический метод класса DataBinder. Присутствие Eval() здесь обязательно — он автоматически извлекает элемент данных, привязанный к текущей строке, использует рефлексию для нахождения соответствующего поля (для объекта DataRow) или свойства (для пользовательского объекта данных) и извлекает значение. Процесс рефлексии добавляет немного работы. Однако маловероятно, что эти накладные расходы намного увеличат время обработки запроса.

Если вы попытаетесь привязать поле, которое отсутствует в результирующем наборе, то получите ошибку времени выполнения. Если вы извлечете дополнительные поля, которые не будут привязаны ни к одному шаблону, никаких проблем не возникнет.

При выполнении привязки SqlDataSource в режиме DataSet элементом данных является DataRowView. Когда SqlDataSource привязывается в режиме DataReader, элементом данных будет DbDataRecord.

Метод Eval() также добавляет исключительно полезную возможность форматировать поля данных "на лету". Чтобы использовать это средство, понадобится перегрузить версию метода Eval(), которая примет дополнительный параметр — форматную строку.

Шаблонные столбцы можно смешивать со столбцами других типов, как показано в предыдущем примере. Или же можно избавиться от всех прочих столбцов и поместить информацию из таблицы Employees в форматированный шаблон:

<asp:GridView ID="gridEmployees" runat="server" DataSourceID="getEmployeesSDS" ...>

            <Columns>
                <asp:TemplateField HeaderText="Сотрудники компании Northwind">
                    <ItemTemplate>
                        <b>
                            <%# Eval("EmployeeID") %> - 
                            <%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %>
                            <%# Eval("LastName") %>
                        </b>
                        <hr />
                        <small><i>
                            <%# Eval("Address") %><br />
                            <%# Eval("City") %>, <%# Eval("Country") %>,
                            <%# Eval("PostalCode") %><br />
                            <%# Eval("HomePhone") %></i>
                            <br />
                            <br />
                            <%# Eval("Notes") %>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>

            ...
</asp:GridView>

Результат показан на рисунке:

Создание шаблонного столбца

Использование множественных шаблонов

В предыдущем примере применялся отдельный шаблон для конфигурирования внешнего вида элемента данных. Однако ItemTemplate — не единственный шаблон, который предлагает GridView. Фактически GridView позволяет конфигурировать различные аспекты своего внешнего вида с помощью множества шаблонов. Внутри каждой шаблонного столбца можно использовать шаблоны, описанные в таблице ниже:

Шаблоны GridView
Шаблон Описание
HeaderTemplate

Определяет внешний вид и содержимое ячейки заголовка

FooterTemplate

Определяет внешний вид и содержимое ячейки нижнего колонтитула

ItemTemplate

Определяет внешний вид и содержимое каждой ячейки данных (если не используется AlternatingItemTemplate) или каждой нечетной ячейки (если используется)

AlternatingItemTemplate

Используется в сочетании с ItemTemplate для различного форматирования четных и нечетных строк

EditItemTemplate

Определяет внешний вид и элементы управления, используемые в режиме редактирования

InsertItemTemplate

Определяет внешний вид и элементы управления, используемые при вставке новой записи

Из всех шаблонов, перечисленных в таблице, EditItemTemplate является одним из наиболее полезных, поскольку он предоставляет возможность управлять поведением поля при редактировании. Если вы не используете шаблонные столбцы, то ограничены простыми текстовыми полями, без какой-либо проверки достоверности. GridView также определяет два шаблона, которые можно применять вне столбцов. Это шаблон PagerTemplate, позволяющий настроить внешний вид элементов управления страницами, и EmptyDataTemplate, позволяющий видеть содержимое, которое будет отображено, если GridView будет привязан к пустому объекту данных.

Привязка метода

Одно из преимуществ шаблонов заключается в том, что они позволяют использовать выражения привязки данных, которые расширяют возможности форматирования и представления привязанных данных. Один из ключевых приемов, который возникает во многих сценариях - это применение метода класса страницы для обработки значения поля. Это отменяет ограничения простой привязки данных и позволяет включить динамическую информацию и условную логику.

Например, предположим, что необходимо создать столбец, в котором рядом с каждой строкой будет отображаться значок. Однако вы не хотите использовать статический значок. Вместо этого изображение должно выбираться на основе данных строки. На рисунке ниже показан пример, в котором отметки ставятся возле продуктов, которые есть на складе:

Вот как можно определить столбец состояния и источник данных:

<asp:GridView ID="gridEmployees" runat="server" DataSourceID="getProductsSDS" ...>

            <Columns>
                <asp:TemplateField HeaderText="Наличие">
                    <ItemTemplate>
                        <img src="<%# GetStatusPicture(Container.DataItem) %>" />
                    </ItemTemplate>
                </asp:TemplateField>
                <asp:BoundField HeaderText="ID" DataField="ProductID" />
                <asp:BoundField HeaderText="Название" DataField="ProductName" />
                <asp:BoundField HeaderText="Цена" DataField="UnitPrice" DataFormatString="{0:c}" />
                <asp:BoundField HeaderText="Кол-во на складе" DataField="UnitsInStock"
                    ItemStyle-HorizontalAlign="Right" />
            </Columns>
</asp:GridView>

<asp:SqlDataSource ID="getProductsSDS" runat="server"
            ConnectionString="<%$ ConnectionStrings:Northwind %>"
            SelectCommand="SELECT * FROM Products" />

Ниже показан метод GetStatusPicture(), который проверяет элемент данных и выбирает правильный URL-адрес изображения:

protected string GetStatusPicture(object dataItem)
{
        // Получить количество товара на складе
        int count = Int32.Parse(
            DataBinder.Eval(dataItem, "UnitsInStock").ToString());

        return count == 0 ? "Cancel.gif" : "OK.gif"; // Эти рисунки должны быть добавлены в проект
}

Такой прием применяется во многих сценариях. Например, его можно использовать для корректировки цен в соответствии с текущим курсом валют или же для перевода числового кода в более осмысленный фрагмент текста. Можно даже создавать полностью вычисляемые столбцы — например, применять поле EmployeeDateOfBirth (дата рождения сотрудника) для вычисления значения столбца EmployeeAge (возраст сотрудника).

С помощью такого подхода в рассматриваемом примере можно установить атрибут alt дескриптора <img>. Это позволит указать альтернативный текст, который даст более осмысленное описание (такое как ОК или Cancel), отражающее состояние соответствующего объекта.

Обработка событий в шаблоне

В некоторых случаях понадобится обрабатывать события, инициированные элементами управления, которые добавляются к шаблонному столбцу. Например, предположим, что предыдущий пример изменен таким образом, что вместо показа статичного значка состояния он создает ссылку в виде активного изображения с помощью элемента управления ImageButton. Достичь этого довольно просто:

<asp:TemplateField HeaderText="Наличие">
	<ItemTemplate>
		<asp:ImageButton ID="ImageButton1" runat="server"
			ImageUrl="<%# GetStatusPicture(Container.DataItem) %>" />
	</ItemTemplate>
</asp:TemplateField>

Проблема в том, что при добавлении элемента управления к шаблону, GridView создает множество копий этого элемента управления — по одной для каждого элемента данных. Когда выполняется щелчок на ImageButton, необходим способ определения, на каком именно экземпляре изображения он был совершен, и к какой строке он относится.

Способ решения этой проблемы заключается в использовании события GridView, а не события кнопки, которую он содержит. Для этой цели служит событие GridView.RowCommand, потому что оно инициируется при щелчке на любой кнопке в любом шаблоне. Процесс, когда событие элемента управления в шаблоне превращается в событие содержащего его элемента управления, называется пузырьковым распространением событий.

Разумеется, вам все еще нужен способ передачи информации событию RowCommand для идентификации строки, в которой действие имело место. Секрет — в двух строковых свойствах всех кнопочных элементов управления: CommandName и CommandArguments. В CommandName устанавливается описательное имя, которое можно использовать для того, чтобы отличить щелчки на ImageButton от щелчков на других кнопочных элементах управления в GridView. В CommandArguments указывается фрагмент специфичных для строки данных, которые можно использовать для идентификации строки, где был выполнен щелчок. Передать эту информацию можно с использованием выражения привязки данных.

Ниже показано шаблонное поле, содержащее измененный дескриптор ImageButton:

<asp:TemplateField HeaderText="Наличие">
	<ItemTemplate>
		<asp:ImageButton ID="ImageButton1" runat="server"
			ImageUrl="<%# GetStatusPicture(Container.DataItem) %>"
            CommandName="StatusClick"
            CommandArgument='<%# Eval("ProductName") %>' />
	</ItemTemplate>
</asp:TemplateField>

А вот код, необходимый для реагирования на щелчок на ImageButton:

protected void grid_RowCommand(object sender, GridViewCommandEventArgs e)
{
        if (e.CommandName == "StatusClick")
            Label1.Text = "Вы выбрали <b>" + e.CommandArgument + "</b>";
}

Приведенный пример просто отображает ProductID внутри метки Label1.

Помните, что за счет использования встроенной в GridView поддержки выбора строк вы можете облегчить себе жизнь. Просто установите CommandName в Select и обрабатывайте событие SelectIndexChanged, как было описано в статье "Выбор строк". Хотя этот подход обеспечивает простой доступ к строке, на которой выполнен щелчок, он не поможет, если вы хотите предоставить множество кнопок, решающих разные задачи.

Редактирование с помощью шаблона

Одним из главных аргументов в пользу применения шаблонов является обеспечение расширенных возможностей редактирования. Ранее было показано, как GridView поддерживает автоматические возможности редактирования. Все, что для этого понадобится — переключить строку в режим редактирования, установив свойство GridView.EditItemIndex.

Простейший способ сделать это возможным — добавить столбец CommandFieId со свойством ShowEditButton, установленным в true (или установить свойство GridView.AutoGenerateEditButton в true). В любом случае вы придете к выделенному столбцу, используемому для отображения команд редактирования. Изначально этот столбец будет отображать ссылку по имени Edit (Правка) рядом с каждой записью. Когда пользователь щелкает на этой ссылке, все метки во всех столбцах строки заменяются текстовыми полями (если только они не являются доступными лишь для чтения).

Стандартной поддержке редактирования присущи некоторые ограничения:

В шаблонных столбцах всего этого нет. Взамен вы явно определяете редактирующие элементы управления и их компоновку с использованием шаблона EditItemTemplate. Этот процесс может оказаться довольно трудоемким.

Ниже показан шаблон редактирования, который позволяет редактирование отдельного поля — поля Notes (видоизмененный первый пример):

<EditItemTemplate>
    <b>
        <%# Eval("EmployeeID") %> - 
        <%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %>
        <%# Eval("LastName") %>
    </b>
    <hr />
    <small><i>
        <%# Eval("Address") %><br />
        <%# Eval("City") %>, <%# Eval("Country") %>,
        <%# Eval("PostalCode") %><br />
        <%# Eval("HomePhone") %></i>
        <br />
        <br />
        <asp:TextBox Text='<%# Bind("Notes") %>' runat="server" ID="textBox" 
            Font-Names="Verdana" Font-Size="XX-Small" Height="40px" 
            TextMode="MultiLine" Width="413px" />
</EditItemTemplate>

Привязывая редактируемое значение к элементу управления, вместо обычного метода Eval() в выражении привязки данных следует применять метод Bind(). Только метод Bind() создает двунаправленную ссылку, гарантируя, что обновленные значения будут отправлены серверу.

Другой важный факт, который необходимо иметь в виду — когда GridView фиксирует обновление, то делает это только с привязанными, редактируемыми параметрами. В предыдущем примере это значит, что GridView отправит обратно единственный параметр @Notes для поля Notes. Это важно, потому что когда вы пишете параметризованные команды обновления (в случае применения SqlDataSource), то должны использовать лишь один параметр, как показано здесь:

<asp:SqlDataSource ID="getEmployeesSDS" runat="server"
            ConnectionString="<%$ ConnectionStrings:Northwind %>"
            SelectCommand="SELECT * FROM Employees"
            UpdateCommand="UPDATE Employees SET Notes=@Notes WHERE EmployeeID=@EmployeeID" />

Аналогично, если вы используете ObjectDataSource, то должны убедиться в том, что метод обновления принимает только один параметр по имени Notes. На рисунке показана строка в режиме редактирования:

Условная пометка строк

Редактирование с помощью расширенных элементов управления

Преимущество редактирования на основе шаблонов особенно проявляется, когда нужно привязать более сложные элементы управления, такие как списки. Например, предыдущий пример можно изменить, сделав поле TitleOfCourtesy редактируемым через раскрывающийся список. Вот шаблон, который понадобится для этого:

<EditItemTemplate>
    <b>
        <%# Eval("EmployeeID") %> - 
        <asp:DropDownList runat="server" ID="EditTitle"
            SelectedIndex='<%# GetSelectedTitle(Eval("TitleOfCourtesy")) %>'
            Font-Names="Verdana" Font-Size="XX-Small"
            DataSource='<%# TitlesOfCourtesy %>' />
        <%# Eval("TitleOfCourtesy") %> <%# Eval("FirstName") %>
        <%# Eval("LastName") %>
    </b>
    <hr />
    <small><i>
        <%# Eval("Address") %><br />
        <%# Eval("City") %>, <%# Eval("Country") %>,
        <%# Eval("PostalCode") %><br />
        <%# Eval("HomePhone") %></i>
        <br />
        <br />
        <asp:TextBox Text='<%# Bind("Notes") %>' runat="server" ID="textBox" 
            Font-Names="Verdana" Font-Size="XX-Small" Height="40px" 
            TextMode="MultiLine" Width="413px" />
</EditItemTemplate>

Этот шаблон дает возможность пользователю выбирать титул для вежливого обращения из ограниченного набора допустимых. Чтобы создать этот список, придется прибегнуть к небольшому трюку — установить в DropDownList.DataSource выражение привязки данных, указывающее на пользовательское свойство. Это свойство может затем возвращать подходящий источник данных возможных титулов. Ниже приведено определение свойства TitleOfCourtesy в классе веб-страницы:

protected string[] TitlesOfCourtesy
{
        get
        {
            return new string[] { "Mr.", "Dr.", "Ms.", "Mrs." };
        }
}

Этот шаг обеспечивает наполнение раскрывающегося списка, но не решает связанную проблему с исходным выбором правильного титула в списке для существующего значения поля. Лучший подход состоит в привязке SelectedIndex к пользовательскому методу, принимающему текущий титул и возвращающему индекс его значения в списке. В данном примере эту задачу выполняет метод GetSelectedTitle(). Он принимает титул на входе и возвращает индекс соответствующего значения в массиве, возвращенном TitlesOfCourtesy:

 protected int GetSelectedTitle(object title)
{
        return Array.IndexOf(TitlesOfCourtesy, title.ToString());
}

Этот код производит поиск в массиве с использованием статического метода Array.IndexOf(). Обратите внимание на необходимость явного приведения титула к строковому типу. Это объясняется тем, что метод DataBinder.Eval() возвращает объект, а не строку, и это значение передается методу GetSelectedTitle(). На рисунке ниже показан раскрывающийся список в действии:

Редактирование с использованием раскрывающегося списка значений

К сожалению, на этом пример все еще не завершен. Теперь вы имеете окно списка, наполненное в режиме редактирования с автоматически выбранным правильным элементом. Однако если вы измените выбор, значение не будет отправлено обратно источнику данных. В этом примере можно попробовать решить проблему, используя метод Bind() со свойством SelectedValue, поскольку текст в элементе управления в точности соответствует тексту, который нужно внести в запись.

Однако иногда дела обстоят не так просто, и требуется транслировать значение в другое представление базы данных. В такой ситуации единственный выбор — обработать событие RowUpdating, найти списковый элемент управления в текущей строке и извлечь текст. Затем можно динамически добавить дополнительные параметры, как показано ниже:

protected void gridEmployees_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
        // Получить ссылку на списковый элемент управления
        DropDownList title = (DropDownList)(gridEmployees.Rows[e.RowIndex].FindControl("EditTitle"));

        // Добавить его к параметру
        e.NewValues.Add("TitleOfCourtesy", title.Text);
}

UpdateCommand в SqlDataSource также следует обновить для использования параметра @TitleOfCourtesy:

<asp:SqlDataSource ID="getEmployeesSDS" runat="server"
            ...
	UpdateCommand="UPDATE Employees SET Notes=@Notes, TitleOfCourtesy=@TitleOfCourtesy
		WHERE EmployeeID=@EmployeeID" />

Теперь успешно обновятся оба поля — Notes и TitleOfCourtesy. Как видите, редактируемые шаблоны предоставляют значительные возможности, но иногда их долго кодировать.

Пройди тесты
Лучший чат для C# программистов