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

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

Скачивание примера приложения или Загрузка PDF-файла

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

Введение

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

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

каждая группа категорий четко идентифицирована

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

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

Шаг 1. Создание стандартного, допускающего сортировку GridView

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

Затем настройте GridView таким, чтобы оно содержало только ProductName, CategoryName, SupplierNameи UnitPrice BoundFields и снятую CheckBoxField. Наконец, настройте GridView для поддержки сортировки, установив флажок Включить сортировку в смарт-теге GridView s (или задав для свойства 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 s упорядочиваются по категориям

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

Шаг 2. изучение методов добавления разделяющих строк

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

Чтобы добавить дополнительные строки в GridView, у нас есть три варианта:

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

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

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

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

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

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

Рис. 3. один прием включает добавление разделяющих строк в источник данных

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

Для использования этой методики нам нужно выполнить следующие действия:

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

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

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

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

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

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

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

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

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

Note

Код m, представленный в этом учебнике, основан на примере, приведенном в записи блога Теему кейски s, с группированием сортировки GridView.

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

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

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

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

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

Note

Если необходимо, чтобы GridView было упорядочено по определенному столбцу при первой загрузке страницы, вызовите метод GridView s Sort на первой странице посещения (но не при последующих обратных передачах). Чтобы сделать это, добавьте этот вызов в обработчик событий 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 еще не был отсортирован, свойство SortExpression GridView s не будет установлено. Поэтому мы хотим добавить разделяющие строки только в том случае, если это свойство имеет какое бы то ни было значение. Если это так, необходимо определить индекс столбца, по которому были отсортированы данные. Это достигается путем прохода по коллекции Columns GridView, в которой выполняется поиск столбца, свойство SortExpression которого равно свойству GridView s SortExpression. Помимо индекса столбцов, мы также получаем свойство HeaderText, которое используется при отображении разделяющих строк.

С индексом столбца, по которому сортируются данные, последним шагом является перечисление строк GridView. Для каждой строки необходимо определить, отличается ли значение отсортированного столбца от предыдущей строки отсортированного столбца s. Если это так, необходимо внедрить новый экземпляр 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 s, и создании строковой переменной с именем lastValue. lastValue используется для сравнения значения столбца текущей строки с сортировкой по сравнению с предыдущим значением строки s. Затем Коллекция Rows GridView s перечисляется и для каждой строки значение отсортированного столбца сохраняется в переменной currentValue.

Note

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

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

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

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

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

теперь заголовки групп сортировки представлены при сортировке CheckBoxField

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

Note

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

Сводка

GridView не включает множество встроенных параметров для настройки интерфейса сортировки. Однако с поразрядностью низкоуровневого кода можно настроить иерархию элементов управления GridView s, чтобы создать более специализированный интерфейс. В этом учебнике мы увидели, как добавить строку разделителя групп сортировки для элемента управления GridView, для которого можно выполнить сортировку, что упрощает определение отдельных групп и границ этих групп. Дополнительные примеры настраиваемых интерфейсов сортировки см. в статье Скотт Гатри ( s ASP.NET 2,0. советы и приемы в блоге по сортировке в GridView .

Поздравляем с программированием!

Об авторе

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