
Нумерация строк GridView: RowIndex, DataItemIndex и пейджинг
Колонка «№ п/п» нужна почти в каждом отчёте, но хранить её в базе бессмысленно. Номер строки можно вычислить прямо в GridView, и сделать это так, чтобы он не сбивался на второй странице.
Зачем нумеровать строки на лету
Пользователи привыкли к колонке «№ п/п» по бумажным реестрам и 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 не нужно, и номер будет сквозным даже при встроенном пейджинге.
<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>Вариант через RowDataBound
Если номер нужно оформить сложнее, например добавить буквенный префикс раздела или выделить каждую десятую строку, удобнее заполнить ячейку в обработчике RowDataBound. Там доступны и индексы, и данные строки через e.Row.DataItem.
В обработчике обязательно проверяем тип строки: событие вызывается и для шапки, и для подвала, а у них индексов данных нет.
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 ...) по фиксированному полю.
' Номер для пользовательского пейджинга (источник отдаёт одну страницу)
Dim number As Integer = gvReport.PageIndex * gvReport.PageSize + e.Row.RowIndex + 1Оформление колонки номера
Колонку номера делают узкой, с выравниванием по правому краю и моноширинными цифрами, чтобы двузначные и трёхзначные номера стояли ровно. В CSS для этого достаточно свойства font-variant-numeric: tabular-nums. Цвет номера можно сделать приглушённым: это служебная информация, она не должна спорить с данными.
Для экспорта в Excel номер иногда мешает: в выгрузке он превращается в лишнюю колонку. Если вы экспортируете GridView через RenderControl, скройте колонку номера на время экспорта свойством Visible = False и верните после.
Как это делают сегодня
Вопросы и ответы
Почему на второй странице нумерация начинается снова с единицы?
Используется RowIndex, он считается в пределах страницы. Замените его на DataItemIndex или на формулу PageIndex * PageSize + RowIndex + 1.
Можно ли вывести номер в BoundField без шаблона?
Нет, у BoundField нет доступа к индексу строки. Нужен TemplateField с выражением или заполнение ячейки в RowDataBound.
Номер меняется при сортировке. Это ошибка?
Нет, номер показывает позицию в текущем порядке. Если нужен постоянный номер записи, храните его в данных или вычисляйте в SQL по фиксированному полю.