
GroupTemplate в ASP.NET ListView: сетка из нескольких колонок
Каталог в три колонки, галерея по четыре фото в ряд: такие сетки ListView строит сам через GroupTemplate. Разбираем, как работают плейсхолдеры, GroupItemCount и шаблон пустой ячейки.
Задача: N элементов в строке
Списки в одну колонку легко сделать любым контролом. Сложности начинаются, когда записи нужно разложить по строкам: три товара в ряд, затем следующий ряд. В Repeater приходится считать индекс элемента и вручную вставлять закрывающий и открывающий теги строки, а последнюю неполную строку дополнять пустыми ячейками.
ListView решает эту задачу декларативно. Помимо привычных LayoutTemplate и ItemTemplate у него есть GroupTemplate, шаблон одной группы, то есть ряда. Свойство GroupItemCount задаёт, сколько элементов попадает в группу. Всю арифметику с индексами контрол берёт на себя.
Три уровня плейсхолдеров
Чтобы понять GroupTemplate, удобно представить вложенность как матрёшку. LayoutTemplate описывает внешнюю обёртку и содержит место для групп. GroupTemplate описывает одну группу и содержит место для элементов. ItemTemplate описывает один элемент.
Места обозначаются контролами-плейсхолдерами с определёнными ID. По умолчанию ListView ищет groupPlaceholder в LayoutTemplate и itemPlaceholder в GroupTemplate. Имена можно поменять свойствами GroupPlaceholderID и ItemPlaceholderID. Если плейсхолдер не найден, страница упадёт с исключением, в тексте которого прямо назван отсутствующий ID.
<asp:ListView ID="lvGallery" runat="server" GroupItemCount="3">
<LayoutTemplate>
<table class="gallery">
<asp:PlaceHolder ID="groupPlaceholder" runat="server" />
</table>
</LayoutTemplate>
<GroupTemplate>
<tr>
<asp:PlaceHolder ID="itemPlaceholder" runat="server" />
</tr>
</GroupTemplate>
<ItemTemplate>
<td>
<img src='<%# Eval("ThumbUrl") %>' alt='<%#: Eval("Title") %>' />
<p><%#: Eval("Title") %></p>
</td>
</ItemTemplate>
<EmptyItemTemplate>
<td class="empty"></td>
</EmptyItemTemplate>
<EmptyDataTemplate>
<p>Фотографий пока нет.</p>
</EmptyDataTemplate>
</asp:ListView>Пустые ячейки и разделители
Если записей семь, а в ряду три, последний ряд получится неполным. Без дополнительных мер таблица будет кривой: в последней строке окажется одна ячейка. Шаблон EmptyItemTemplate решает эту проблему: ListView выводит его столько раз, сколько не хватает до полной группы.
Не путайте его с EmptyDataTemplate. Тот показывается вместо всего списка, когда источник данных пуст, и это хорошее место для сообщения «ничего не найдено».
Ещё два шаблона пригодятся для оформления. ItemSeparatorTemplate вставляется между элементами внутри группы, GroupSeparatorTemplate между группами. Например, между рядами можно вывести строку с горизонтальной линией или рекламный блок.
Привязка и смена числа колонок из кода
Данные привязываются как обычно, через DataSource и DataBind. GroupItemCount можно менять и из кода, например давать пользователю выбор между тремя и четырьмя колонками. Менять свойство нужно до DataBind, иначе группы уже сформированы.
Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
If Not IsPostBack Then BindGallery()
End Sub
Protected Sub ddlColumns_SelectedIndexChanged(sender As Object, _
e As EventArgs) Handles ddlColumns.SelectedIndexChanged
BindGallery()
End Sub
Private Sub BindGallery()
' Число колонок из выпадающего списка, по умолчанию 3
Dim cols As Integer
If Not Integer.TryParse(ddlColumns.SelectedValue, cols) Then cols = 3
lvGallery.GroupItemCount = cols
lvGallery.DataSource = PhotoRepository.GetAlbum(AlbumId)
lvGallery.DataBind()
End SubСетка без таблицы
Таблица для галереи исторически самый надёжный способ ровной сетки, но с точки зрения семантики это не таблица данных. Скринридер объявит строки и столбцы, которых по смыслу нет. Поэтому для карточек и фотографий разметку лучше строить на списках или div.
GroupTemplate для этого подходит не хуже: группа становится div с классом row, элементы обычными блоками. А если вёрстка позволяет CSS Grid, от GroupTemplate можно отказаться совсем: выводите элементы одним списком, а число колонок задавайте в CSS. Тогда сетка ещё и перестраивается под ширину экрана, чего таблица с фиксированным GroupItemCount не умеет.
/* Адаптивная сетка вместо GroupItemCount */
.gallery-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
gap: 16px;
list-style: none;
padding: 0;
}
.gallery-grid img { width: 100%; height: auto; display: block; }Когда GroupTemplate всё ещё нужен
Несмотря на CSS Grid, группировка на сервере иногда остаётся правильным выбором:
- вывод для писем и печатных отчётов, где CSS ограничен и таблица единственный надёжный способ;
- ряды должны быть отдельными смысловыми блоками, например с собственным заголовком или рекламной вставкой между ними;
- поддержка старых браузеров, где Grid и Flexbox работают некорректно;
- нужно разбивать данные на равные порции, например по строкам слайдера, где каждый слайд содержит фиксированное число элементов.
Как это делают сегодня
Вопросы и ответы
Чем EmptyItemTemplate отличается от EmptyDataTemplate?
EmptyItemTemplate заполняет недостающие ячейки в последней группе. EmptyDataTemplate выводится вместо всего списка, если данных нет.
Почему ошибка «An item placeholder must be specified»?
В GroupTemplate нет контрола с ID itemPlaceholder или в ListView задано другое значение ItemPlaceholderID. Проверьте имена и атрибут runat=«server».
Можно ли менять число колонок без перезагрузки страницы?
Средствами ListView нет, GroupItemCount применяется на сервере. Для адаптивности используйте плоскую разметку и CSS Grid.