Создание интерфейса настраиваемого упорядочения (VB)

Скотт Митчелл

Загрузить PDF-файл

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

Введение

При отображении длинного списка отсортированных данных с несколькими различными значениями в отсортированных столбцах конечным пользователям может быть трудно определить, где именно возникают границы различий. Например, в базе данных есть 81 продукт, но только девять различных категорий (восемь уникальных категорий плюс NULL параметр). Рассмотрим случай пользователя, который заинтересован в изучении продуктов, которые относятся к категории Морепродукты. На странице со списком всех продуктов в одном GridView пользователь может решить, что ей лучше всего отсортировать результаты по категориям, чтобы сгруппировать все продукты морепродуктов вместе. После сортировки по категориям пользователь должен искать список, искать, где продукты, сгруппированные в морепродукты, начинаются и заканчиваются. Так как результаты упорядочены в алфавитном порядке по названию категории найти морепродукты продуктов не сложно, но это все еще требует тщательного сканирования списка элементов в сетке.

Для выделения границ между отсортированных групп многие веб-сайты используют пользовательский интерфейс, который добавляет разделитель между такими группами. Разделители, как показано на рис. 1, позволяют пользователю быстрее найти определенную группу и определить ее границы, а также определить, какие отдельные группы существуют в данных.

Каждая группа категорий четко определена

Рис. 1. Каждая группа категорий четко определена (щелкните, чтобы просмотреть полноразмерное изображение)

В этом руководстве мы посмотрим, как создать такой пользовательский интерфейс сортировки.

Шаг 1. Создание стандартного сортируемого GridView

Прежде чем мы рассмотрим, как расширить GridView для предоставления расширенного интерфейса сортировки, давайте сначала создадим стандартный, сортируемый Элемент GridView со списком продуктов. Начните с открытия страницы CustomSortingUI.aspx в папке PagingAndSorting . Добавьте gridView на страницу, задайте для его ID свойства ProductListзначение и привяжите его к новому объекту ObjectDataSource. Настройте ObjectDataSource для использования ProductsBLL метода класса GetProducts() для выбора записей.

Затем настройте GridView таким образом, чтобы он содержал ProductNameтолько , CategoryNameSupplierName, и UnitPrice BoundFields и Неподдерживаемый CheckBoxField. Наконец, настройте GridView для поддержки сортировки, установив флажок Включить сортировку в смарт-теге GridView (или задав для его AllowSorting свойства значение true). После внесения этих дополнений на страницу CustomSortingUI.aspx декларативная разметка должна выглядеть примерно так:

<asp:GridView ID="ProductList" runat="server" AllowSorting="True"
    AutoGenerateColumns="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="ProductName" HeaderText="Product"
            SortExpression="ProductName" />
        <asp:BoundField DataField="CategoryName" HeaderText="Category"
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:BoundField DataField="SupplierName" HeaderText="Supplier"
            ReadOnly="True" SortExpression="SupplierName" />
        <asp:BoundField DataField="UnitPrice" DataFormatString="{0:C}"
            HeaderText="Price" HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued"
            SortExpression="Discontinued" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server"
    OldValuesParameterFormatString="original_{0}" SelectMethod="GetProducts"
    TypeName="ProductsBLL"></asp:ObjectDataSource>

На минутку просмотрите наш прогресс до сих пор в браузере. На рисунке 2 показан сортируемый элемент GridView, если его данные сортируются по категориям в алфавитном порядке.

Сортируемые данные GridView упорядочены по категории

Рис. 2. Сортируемые данные GridView упорядочены по категориям (щелкните, чтобы просмотреть полноразмерное изображение)

Шаг 2. Изучение методов добавления строк-разделителей

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

Добавить дополнительные строки в GridView можно тремя способами:

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

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

Два других варианта добавления строк-разделителей к фактическим данным, привязанным к GridView, и управления коллекцией элементов управления GridView после привязки — атакуют проблему иначе и заслуживают обсуждения.

Добавление строк к привязанным данным в GridView

Когда GridView привязан к источнику данных, он создает для каждой GridViewRow записи, возвращаемой источником данных. Таким образом, мы можем внедрить необходимые строки разделителя, добавив записи разделителя в источник данных, прежде чем привязать их к GridView. На рисунке 3 показана эта концепция.

Один из способов включает добавление строк-разделителей в источник данных

Рис. 3. Один из способов включает добавление строк-разделителей в источник данных

Я использую термин разделители записей в кавычках, так как нет специальной записи разделителя; вместо этого мы должны каким-то образом пометить, что определенная запись в источнике данных служит разделителем, а не обычной строкой данных. В наших примерах мы повторно привязываем ProductsDataTable экземпляр к GridView, который состоит из ProductRows. Мы можем пометить запись как строку разделителя, присвоив ее CategoryID свойству значение -1 (так как такое значение не может существовать в обычном режиме).

Чтобы использовать этот метод, необходимо выполнить следующие действия:

  1. Программное извлечение данных для привязки к GridView (экземпляр)ProductsDataTable
  2. Сортировка данных на основе свойств GridView SortExpression и SortDirection
  3. Выполните итерацию по ProductsRows в ProductsDataTable, в котором находятся различия в отсортированных столбцах.
  4. На каждой границе группы вставьте экземпляр записи-разделителя ProductsRow в таблицу DataTable, которая имеет CategoryID значение -1 (или любое другое обозначение было принято решение о том, чтобы пометить запись как запись разделителя).
  5. После внедрения строк разделителя привяжите данные к GridView программным способом.

В дополнение к этим пяти шагам необходимо также предоставить обработчик событий для события GridView RowDataBound . Здесь мы проверка каждой из них DataRow и определим, была ли это строка разделителя, для которой CategoryID задано -1значение . В этом случае мы, вероятно, захотите изменить его форматирование или текст, отображаемый в ячейках.

Использование этого метода для внедрения границ группы сортировки требует немного больше работы, чем описано выше, так как необходимо также предоставить обработчик событий для события GridView Sorting и отслеживать SortExpression значения и SortDirection .

Управление коллекцией элементов управления GridView после подключения к данным

Вместо обмена сообщениями о данных перед их привязкой к GridView можно добавить строки разделителя после привязки данных к GridView. Процесс привязки данных создает иерархию элементов управления GridView, которая на самом деле представляет собой Table просто экземпляр, состоящий из коллекции строк, каждая из которых состоит из коллекции ячеек. В частности, коллекция элементов управления GridView содержит Table объект в корне, (производный GridViewRow от TableRow класса) для каждой записи в DataSource связанном TableCell с GridView, и объект в каждом GridViewRow экземпляре для каждого поля данных в DataSource.

Чтобы добавить строки-разделители между каждой группой сортировки, можно напрямую управлять этой иерархией элементов управления после ее создания. Мы можем быть уверены, что иерархия элементов управления GridView была создана в последний раз к моменту отрисовки страницы. Таким образом, этот подход переопределяет Page метод класса Render , после чего окончательная иерархия элементов управления GridView обновляется для включения необходимых строк разделителя. Этот процесс показан на рисунке 4.

Альтернативный метод управляет иерархией элементов управления GridView

Рис. 4. Альтернативный метод управляет иерархией элементов управления GridView (щелкните для просмотра полноразмерного изображения)

В этом руководстве мы будем использовать последний подход для настройки пользовательского интерфейса сортировки.

Примечание

Код, который я представляю в этом руководстве, основан на примере, приведенном в записи блога Teemu Keiski , Playing a Bit with GridView Sort Grouping.

Шаг 3. Добавление строк-разделителей в иерархию элементов управления GridView

Так как мы хотим добавить строки разделителя в иерархию элементов управления GridView только после того, как иерархия элементов управления была создана и создана в последний раз при этом посещении страницы, мы хотим выполнить это добавление в конце жизненного цикла страницы, но до того, как фактическая иерархия элементов управления GridView будет преобразована в HTML. Последней возможной точкой, в которой мы можем выполнить это, является Page событие класса Render , которое можно переопределить в нашем классе кода программной части с помощью следующей сигнатуры метода:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
   ' Add code to manipulate the GridView control hierarchy
   MyBase.Render(writer)
End Sub

При вызове исходного PageRender метода класса каждый элемент управления на странице будет отрисован base.Render(writer) , создавая разметку на основе их иерархии элементов управления. Поэтому крайне важно, чтобы мы оба вызывали base.Render(writer), чтобы страница отображалась, и мы манипулировали иерархией элементов управления GridView перед вызовом base.Render(writer), чтобы строки разделителей были добавлены в иерархию элементов управления GridView до ее отрисовки.

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

Примечание

Если вы хотите отсортировать GridView по определенному столбцу при первой загрузке страницы, вызовите метод GridView Sort при первом посещении страницы (но не при последующих обратных операциях). Для этого добавьте этот вызов в Page_Load обработчик событий в условном объекте if (!Page.IsPostBack) . Дополнительные сведения о методе см. в руководстве По страницам и сортировкеSort данных отчета.

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

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' Determine the index and HeaderText of the column that
        'the data is sorted by
        Dim sortColumnIndex As Integer = -1
        Dim sortColumnHeaderText As String = String.Empty
        For i As Integer = 0 To ProductList.Columns.Count - 1
            If ProductList.Columns(i).SortExpression.CompareTo( _
                ProductList.SortExpression) = 0 Then
                sortColumnIndex = i
                sortColumnHeaderText = ProductList.Columns(i).HeaderText
                Exit For
            End If
        Next
        ' TODO: Scan the rows for differences in the sorted column�s values
End Sub

Если Элемент GridView еще не отсортирован, свойство GridView SortExpression не задано. Поэтому мы хотим добавить строки разделителя, только если это свойство имеет некоторое значение. В этом случае необходимо определить индекс столбца, по которому были отсортированы данные. Для этого выполняется цикл по коллекции GridView Columns и выполняется поиск столбца, свойство которого SortExpression равно свойству GridView SortExpression . В дополнение к индексу столбцов мы также захватываем HeaderText свойство , которое используется при отображении строк разделителя.

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

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    ' Only add the sorting UI if the GridView is sorted
    If Not String.IsNullOrEmpty(ProductList.SortExpression) Then
        ' ... Code for finding the sorted column index removed for brevity ...
        ' Reference the Table the GridView has been rendered into
        Dim gridTable As Table = CType(ProductList.Controls(0), Table)
        ' Enumerate each TableRow, adding a sorting UI header if
        ' the sorted value has changed
        Dim lastValue As String = String.Empty
        For Each gvr As GridViewRow In ProductList.Rows
            Dim currentValue As String = gvr.Cells(sortColumnIndex).Text
            If lastValue.CompareTo(currentValue) <> 0 Then
                ' there's been a change in value in the sorted column
                Dim rowIndex As Integer = gridTable.Rows.GetRowIndex(gvr)
                ' Add a new sort header row
                Dim sortRow As New GridViewRow(rowIndex, rowIndex, _
                    DataControlRowType.DataRow, DataControlRowState.Normal)
                Dim sortCell As New TableCell()
                sortCell.ColumnSpan = ProductList.Columns.Count
                sortCell.Text = String.Format("{0}: {1}", _
                    sortColumnHeaderText, currentValue)
                sortCell.CssClass = "SortHeaderRowStyle"
                ' Add sortCell to sortRow, and sortRow to gridTable
                sortRow.Cells.Add(sortCell)
                gridTable.Controls.AddAt(rowIndex, sortRow)
                ' Update lastValue
                lastValue = currentValue
            End If
        Next
    End If
    MyBase.Render(writer)
End Sub

Этот код начинается с программной ссылки на объект, Table найденный в корне иерархии элементов управления GridView, и создания строковой переменной с именем lastValue. lastValue используется для сравнения текущего значения отсортированного столбца строки со значением предыдущей строки. Затем перечисляется коллекция GridView Rows , и для каждой строки значение отсортированного столбца сохраняется в переменной currentValue .

Примечание

Чтобы определить значение отсортированного столбца конкретной строки, используется свойство ячейки Text . Это хорошо подходит для BoundFields, но не будет работать так, как нужно для TemplateFields, CheckBoxFields и т. д. Вскоре мы рассмотрим, как учесть альтернативные поля GridView.

Затем currentValue сравниваются переменные и lastValue . Если они отличаются, необходимо добавить новую строку разделителя в иерархию элементов управления. Это достигается путем определения индекса GridViewRow объекта в Table коллекции объектов Rows , создания экземпляров GridViewRow и TableCell , а затем добавления TableCell и GridViewRow в иерархию элементов управления.

Обратите внимание, что одинокая TableCell строка разделителя отформатирована таким образом, что она охватывает всю ширину GridView, отформатирована с помощью SortHeaderRowStyle класса CSS и имеет свое Text свойство таким образом, что отображается как имя группы сортировки (например, Категория ), так и значение группы (например, Beverages ). Наконец, lastValue обновляется до значения currentValue.

В файле необходимо указать класс CSS, используемый для форматирования строки SortHeaderRowStyle заголовка группы сортировки Styles.css . Вы можете использовать любые параметры стиля, которые вам понравится; Я использовал следующее:

.SortHeaderRowStyle
{
    background-color: #c00;
    text-align: left;
    font-weight: bold;
    color: White;
}

В текущем коде интерфейс сортировки добавляет заголовки группы сортировки при сортировке по любому BoundField (см. рис. 5, на котором показан снимок экрана при сортировке по поставщику). Однако при сортировке по любому другому типу поля (например, CheckBoxField или TemplateField) заголовки группы сортировки нигде не находятся (см. рис. 6).

Интерфейс сортировки включает заголовки групп сортировки при сортировке по boundfields

Рис. 5. Интерфейс сортировки включает заголовки группы сортировки при сортировке по BoundFields (щелкните для просмотра полноразмерного изображения)

Заголовки группы сортировки отсутствуют при сортировке CheckBoxField

Рис. 6. Заголовки группы сортировки отсутствуют при сортировке CheckBoxField (щелкните для просмотра полноразмерного изображения)

Причина отсутствия заголовков группы сортировки при сортировке по CheckBoxField заключается в том, что код в настоящее время использует только TableCell свойство s Text для определения значения отсортированного столбца для каждой строки. Для CheckBoxFields TableCell свойство s Text является пустой строкой; вместо этого значение доступно через веб-элемент управления CheckBox, который находится в TableCell коллекции s Controls .

Для обработки типов полей, отличных от BoundFields, необходимо дополнить код, в котором currentValue переменная назначается проверка для существования CheckBox в TableCellControls коллекции s. Вместо использования currentValue = gvr.Cells(sortColumnIndex).Textзамените этот код следующим кодом:

Dim currentValue As String = String.Empty
If gvr.Cells(sortColumnIndex).Controls.Count > 0 Then
    If TypeOf gvr.Cells(sortColumnIndex).Controls(0) Is CheckBox Then
        If CType(gvr.Cells(sortColumnIndex).Controls(0), CheckBox).Checked Then
            currentValue = "Yes"
        Else
            currentValue = "No"
        End If
        ' ... Add other checks here if using columns with other
        '      Web controls in them (Calendars, DropDownLists, etc.) ...
    End If
Else
    currentValue = gvr.Cells(sortColumnIndex).Text
End If

Этот код проверяет отсортированный столбец TableCell для текущей строки, чтобы определить, есть ли какие-либо элементы управления в Controls коллекции. Если они имеются и первым элементом управления является CheckBox, currentValue переменная имеет значение Да или Нет в зависимости от свойства CheckBox.Checked В противном случае значение берется из TableCell свойства s Text . Эту логику можно реплицировать для обработки сортировки для всех полей TemplateField, которые могут существовать в GridView.

С добавлением приведенного выше кода заголовки группы сортировки теперь присутствуют при сортировке по снятой функции CheckBoxField (см. рис. 7).

Заголовки группы сортировки теперь присутствуют при сортировке CheckBoxField

Рис. 7. Заголовки группы сортировки теперь присутствуют при сортировке CheckBoxField (щелкните для просмотра полноразмерного изображения)

Примечание

Если у вас есть продукты со значениями NULL базы данных для CategoryIDполей , SupplierIDили UnitPrice , эти значения будут по умолчанию отображаться в GridView в виде пустых строк. Это означает, что текст строки разделителя для этих продуктов со значениями NULL будет выглядеть как Категория: (то есть нет имени после Category: like with Category: Beverages). Если вы хотите, чтобы здесь отображалось значение, можно задать для свойства BoundFields NullDisplayText нужный текст или добавить условный оператор в метод Render при назначении currentValue свойству строки Text разделителя.

Сводка

GridView не включает много встроенных параметров для настройки интерфейса сортировки. Однако с помощью небольшого кода низкого уровня можно настроить иерархию элементов управления GridView, чтобы создать более настраиваемый интерфейс. В этом руководстве мы узнали, как добавить строку разделителя групп сортировки для сортируемого элемента GridView, который проще определить отдельные группы и границы этих групп. Дополнительные примеры настраиваемых интерфейсов сортировки проверка запись блога Скотта Гатриa Few ASP.NET 2.0 GridView Sorting Tips and Tricks.

Счастливого программирования!

Об авторе

Скотт Митчелл( Scott Mitchell), автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Он может быть доступен в mitchell@4GuysFromRolla.com. или через его блог, который можно найти по адресу http://ScottOnWriting.NET.