Создание интерфейса настраиваемого упорядочения (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. Наконец, настройте 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, определить, где происходят различия между значениями отсортированного столбца, а затем добавить соответствующую строку разделителя. При рассмотрении этой проблемы кажется естественным, что решение находится где-то в обработчике RowDataBound событий GridView. Как мы уже говорили в руководстве по настраиваемого форматированию на основе данных , этот обработчик событий обычно используется при применении форматирования на уровне строк на основе данных строк. Однако 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. Сортировка данных на основе свойств и SortDirection GridView SortExpression
  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 override void Render(HtmlTextWriter writer)
{
    // Add code to manipulate the GridView control hierarchy
    base.Render(writer);
}

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

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

Примечание

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

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 . Эту логику можно реплицировать для обработки сортировки для всех полей 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.