Поделиться через


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

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

Скачивание PDF

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

Введение

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

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

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

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

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

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

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

Затем настройте GridView таким образом, чтобы он содержал только поля ProductName, CategoryName, SupplierName и UnitPrice BoundFields и поле CheckBoxField для "Discontinued". Наконец, настройте GridView для поддержки сортировки, установив флажок "Включить сортировку" в смарт-теге GridView (или установив для свойства значение AllowSortingtrue). После добавления этих дополнений 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 SortExpressionSortDirection
  3. Просмотрите ProductsRows в ProductsDataTable, ищите различия в отсортированном столбце
  4. На границе каждой группы введите экземпляр записи ProductsRow разделителя в DataTable, который имеет CategoryID установленное значение -1 (или любое обозначение было принято, чтобы отметить запись как запись разделителя)
  5. После внедрения строк разделителя программным способом привязать данные к GridView

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

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

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

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

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

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

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

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

Замечание

Код, представленный в этом руководстве, основан на примере, приведенном в записи блога Teemu Keiski, Игра с группировкой и сортировкой в GridView.

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Add code to manipulate the GridView control hierarchy
    base.Render(writer);
}

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

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

Замечание

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // Determine the index and HeaderText of the column that
        //the data is sorted by
        int sortColumnIndex = -1;
        string sortColumnHeaderText = string.Empty;
        for (int i = 0; i < ProductList.Columns.Count; i++)
        {
            if (ProductList.Columns[i].SortExpression.CompareTo(ProductList.SortExpression)
                == 0)
            {
                sortColumnIndex = i;
                sortColumnHeaderText = ProductList.Columns[i].HeaderText;
                break;
            }
        }
        // TODO: Scan the rows for differences in the sorted column�s values
}

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

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

protected override void Render(HtmlTextWriter writer)
{
    // Only add the sorting UI if the GridView is sorted
    if (!string.IsNullOrEmpty(ProductList.SortExpression))
    {
        // ... Code for finding the sorted column index removed for brevity ...
        // Reference the Table the GridView has been rendered into
        Table gridTable = (Table)ProductList.Controls[0];
        // Enumerate each TableRow, adding a sorting UI header if
        // the sorted value has changed
        string lastValue = string.Empty;
        foreach (GridViewRow gvr in ProductList.Rows)
        {
            string currentValue = gvr.Cells[sortColumnIndex].Text;
            if (lastValue.CompareTo(currentValue) != 0)
            {
                // there's been a change in value in the sorted column
                int rowIndex = gridTable.Rows.GetRowIndex(gvr);
                // Add a new sort header row
                GridViewRow sortRow = new GridViewRow(rowIndex, rowIndex,
                    DataControlRowType.DataRow, DataControlRowState.Normal);
                TableCell sortCell = 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;
            }
        }
    }
    base.Render(writer);
}

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

Замечание

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

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

Обратите внимание, что одиночная строка TableCell разделителя отформатирована так, что она охватывает всю ширину GridView, форматирована с использованием CSS-класса SortHeaderRowStyle, и её свойство Text настроено так, чтобы отображать как имя группы сортировки (например, Категория), так и значение группы (например, Напитки). Наконец, 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, так как код в текущей версии использует только свойство TableCellText для определения значения отсортированного столбца для каждой строки. Для CheckBoxFields поле TableCells Text является пустой строкой; вместо этого значение доступно через веб-элемент управления CheckBox, который находится в коллекции TableCells Controls.

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

string currentValue = string.Empty;
if (gvr.Cells[sortColumnIndex].Controls.Count > 0)
{
    if (gvr.Cells[sortColumnIndex].Controls[0] is CheckBox)
    {
        if (((CheckBox)gvr.Cells[sortColumnIndex].Controls[0]).Checked)
            currentValue = "Yes";
        else
            currentValue = "No";
    }
    // ... Add other checks here if using columns with other
    //      Web controls in them (Calendars, DropDownLists, etc.) ...
}
else
    currentValue = gvr.Cells[sortColumnIndex].Text;

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

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

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

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

Замечание

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

Сводка

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

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

Сведения о авторе

Скотт Митчелл, автор семи книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с технологиями Microsoft Web с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга — Научитесь сами ASP.NET 2.0 за 24 часа. С ним можно связаться по адресу mitchell@4GuysFromRolla.com.