GroupTemplate в ASP.NET ListView: сетка из нескольких колонок

РазделДанные и привязкаУровеньСредний Чтение7 минОпубликовано ТегиASP.NETListViewVB.NETCSS

Каталог в три колонки, галерея по четыре фото в ряд: такие сетки ListView строит сам через GroupTemplate. Разбираем, как работают плейсхолдеры, GroupItemCount и шаблон пустой ячейки.

Главное. GroupTemplate превращает ListView в генератор сетки: GroupItemCount задаёт размер ряда, плейсхолдеры определяют вложенность, а EmptyItemTemplate выравнивает последний ряд. Для адаптивной вёрстки проще вывести плоский список и разложить его CSS Grid.

Задача: N элементов в строке

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

ListView решает эту задачу декларативно. Помимо привычных LayoutTemplate и ItemTemplate у него есть GroupTemplate, шаблон одной группы, то есть ряда. Свойство GroupItemCount задаёт, сколько элементов попадает в группу. Всю арифметику с индексами контрол берёт на себя.

Три уровня плейсхолдеров

Чтобы понять GroupTemplate, удобно представить вложенность как матрёшку. LayoutTemplate описывает внешнюю обёртку и содержит место для групп. GroupTemplate описывает одну группу и содержит место для элементов. ItemTemplate описывает один элемент.

Места обозначаются контролами-плейсхолдерами с определёнными ID. По умолчанию ListView ищет groupPlaceholder в LayoutTemplate и itemPlaceholder в GroupTemplate. Имена можно поменять свойствами GroupPlaceholderID и ItemPlaceholderID. Если плейсхолдер не найден, страница упадёт с исключением, в тексте которого прямо назван отсутствующий ID.

Gallery.aspxASPX
<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, иначе группы уже сформированы.

Gallery.aspx.vbVB.NET
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
Если вместе с GroupTemplate используется DataPager, делайте PageSize кратным GroupItemCount. Иначе последняя строка каждой страницы будет неполной.

Сетка без таблицы

Таблица для галереи исторически самый надёжный способ ровной сетки, но с точки зрения семантики это не таблица данных. Скринридер объявит строки и столбцы, которых по смыслу нет. Поэтому для карточек и фотографий разметку лучше строить на списках или div.

GroupTemplate для этого подходит не хуже: группа становится div с классом row, элементы обычными блоками. А если вёрстка позволяет CSS Grid, от GroupTemplate можно отказаться совсем: выводите элементы одним списком, а число колонок задавайте в CSS. Тогда сетка ещё и перестраивается под ширину экрана, чего таблица с фиксированным GroupItemCount не умеет.

gallery.cssCSS
/* Адаптивная сетка вместо 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 работают некорректно;
  • нужно разбивать данные на равные порции, например по строкам слайдера, где каждый слайд содержит фиксированное число элементов.

Как это делают сегодня

Сегодня сетки карточек почти всегда строят на CSS Grid с auto-fill и minmax: число колонок подстраивается под экран без участия сервера. В Razor и Blazor элементы выводятся простым @foreach, а если ряды всё же нужны как отдельные блоки, коллекцию режут на порции методом LINQ Chunk, доступным с .NET 6.

Вопросы и ответы

Чем EmptyItemTemplate отличается от EmptyDataTemplate?

EmptyItemTemplate заполняет недостающие ячейки в последней группе. EmptyDataTemplate выводится вместо всего списка, если данных нет.

Почему ошибка «An item placeholder must be specified»?

В GroupTemplate нет контрола с ID itemPlaceholder или в ListView задано другое значение ItemPlaceholderID. Проверьте имена и атрибут runat=«server».

Можно ли менять число колонок без перезагрузки страницы?

Средствами ListView нет, GroupItemCount применяется на сервере. Для адаптивности используйте плоскую разметку и CSS Grid.

Читайте также