Нумерация строк GridView: RowIndex, DataItemIndex и пейджинг

РазделGridViewУровеньНовичок Чтение6 минОпубликовано ТегиGridViewVB.NETTemplateField

Колонка «№ п/п» нужна почти в каждом отчёте, но хранить её в базе бессмысленно. Номер строки можно вычислить прямо в GridView, и сделать это так, чтобы он не сбивался на второй странице.

Главное. Порядковый номер в GridView проще всего вывести выражением Container.DataItemIndex + 1. RowIndex даёт номер на текущей странице, DataItemIndex сквозной, а при пользовательском пейджинге номер собирают из PageIndex, PageSize и RowIndex.

Зачем нумеровать строки на лету

Пользователи привыкли к колонке «№ п/п» по бумажным реестрам и Excel: по ней удобно ссылаться в разговоре («посмотри двадцать третью строку») и сверять распечатку с экраном. Брать для этого первичный ключ таблицы нельзя: идентификаторы идут с пропусками после удалений и меняют порядок при сортировке.

Номер должен отражать позицию строки в текущем представлении: после фильтра, после сортировки, с учётом страницы. Значит, вычислять его нужно при выводе. У GridView для этого есть два индекса, и важно понимать разницу между ними.

RowIndex и DataItemIndex: в чём разница

Свойство RowIndex у GridViewRow показывает номер строки внутри коллекции Rows, то есть на текущей странице. На каждой странице он начинается с нуля. Свойство DataItemIndex показывает индекс записи во всём источнике данных. Без пейджинга они совпадают, с пейджингом расходятся: на третьей странице по 20 строк первая строка имеет RowIndex = 0 и DataItemIndex = 40.

Для сквозной нумерации нужен DataItemIndex. Для нумерации, которая начинается заново на каждой странице, подходит RowIndex. В обоих случаях прибавляем единицу, потому что индексы считаются с нуля, а пользователи с единицы.

  • RowIndex: позиция на странице, используется для DataKeys(row.RowIndex) и FindControl;
  • DataItemIndex: позиция в источнике данных с учётом пейджинга;
  • DisplayIndex: позиция среди отображаемых строк, полезна в ListView, в GridView обычно совпадает с RowIndex.

Самый короткий вариант: выражение в разметке

Достаточно добавить TemplateField и вывести в нём Container.DataItemIndex + 1. Container здесь это сама строка GridViewRow, поэтому в выражении доступны все её свойства. Никакого кода в code-behind не нужно, и номер будет сквозным даже при встроенном пейджинге.

Report.aspxASPX
<asp:GridView ID="gvReport" runat="server" AutoGenerateColumns="false"
    AllowPaging="true" PageSize="20" DataSourceID="sdsReport">
  <Columns>
    <asp:TemplateField HeaderText="№" ItemStyle-CssClass="num">
      <ItemTemplate>
        <%# Container.DataItemIndex + 1 %>
      </ItemTemplate>
    </asp:TemplateField>
    <asp:BoundField DataField="Customer" HeaderText="Клиент" />
    <asp:BoundField DataField="Total" HeaderText="Сумма" DataFormatString="{0:N2}" />
  </Columns>
</asp:GridView>
Выражение с <%# %> вычисляется только при привязке данных. Если строки восстанавливаются из ViewState без DataBind, значение тоже придёт из ViewState, и это нормально.

Вариант через RowDataBound

Если номер нужно оформить сложнее, например добавить буквенный префикс раздела или выделить каждую десятую строку, удобнее заполнить ячейку в обработчике RowDataBound. Там доступны и индексы, и данные строки через e.Row.DataItem.

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

Report.aspx.vbVB.NET
Protected Sub gvReport_RowDataBound(ByVal sender As Object, ByVal e As GridViewRowEventArgs) _
    Handles gvReport.RowDataBound
    If e.Row.RowType <> DataControlRowType.DataRow Then Return

    ' Сквозной номер с учётом страницы
    Dim number As Integer = e.Row.DataItemIndex + 1
    e.Row.Cells(0).Text = number.ToString()

    ' Подсветка каждой десятой строки для сверки с распечаткой
    If number Mod 10 = 0 Then e.Row.CssClass = "row-tenth"
End Sub

Пользовательский пейджинг и сортировка

При пользовательском пейджинге через ObjectDataSource источник возвращает только одну страницу, и DataItemIndex внутри неё снова начинается с нуля. Сквозной номер тогда считают сами: PageIndex * PageSize + RowIndex + 1. Результат тот же, но не зависит от того, сколько строк вернул источник.

При сортировке номер вычисляется заново и всегда идёт от единицы по порядку, потому что отражает позицию, а не запись. Если пользователям нужен постоянный номер, привязанный к записи (например номер документа в реестре), его нужно хранить в данных или считать в SQL через ROW_NUMBER() OVER (ORDER BY ...) по фиксированному полю.

Report.aspx.vbVB.NET
' Номер для пользовательского пейджинга (источник отдаёт одну страницу)
Dim number As Integer = gvReport.PageIndex * gvReport.PageSize + e.Row.RowIndex + 1

Оформление колонки номера

Колонку номера делают узкой, с выравниванием по правому краю и моноширинными цифрами, чтобы двузначные и трёхзначные номера стояли ровно. В CSS для этого достаточно свойства font-variant-numeric: tabular-nums. Цвет номера можно сделать приглушённым: это служебная информация, она не должна спорить с данными.

Для экспорта в Excel номер иногда мешает: в выгрузке он превращается в лишнюю колонку. Если вы экспортируете GridView через RenderControl, скройте колонку номера на время экспорта свойством Visible = False и верните после.

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

В Blazor QuickGrid номер строки берут из позиции элемента в текущем срезе: при использовании ItemsProvider известен StartIndex, к нему прибавляют индекс в странице. В SQL нумерацию для отчётов удобно отдавать сразу из запроса через ROW_NUMBER, тогда номер одинаков в таблице, экспорте и печатной форме.

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

Почему на второй странице нумерация начинается снова с единицы?

Используется RowIndex, он считается в пределах страницы. Замените его на DataItemIndex или на формулу PageIndex * PageSize + RowIndex + 1.

Можно ли вывести номер в BoundField без шаблона?

Нет, у BoundField нет доступа к индексу строки. Нужен TemplateField с выражением или заполнение ячейки в RowDataBound.

Номер меняется при сортировке. Это ошибка?

Нет, номер показывает позицию в текущем порядке. Если нужен постоянный номер записи, храните его в данных или вычисляйте в SQL по фиксированному полю.

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