Создание интерфейса настраиваемого упорядочения (C#)
При отображении длинного списка отсортированных данных очень полезно сгруппировать связанные данные, введя строки разделителей. В этом руководстве мы посмотрим, как создать такой пользовательский интерфейс сортировки.
Введение
При отображении длинного списка отсортированных данных, где в отсортированных столбцах есть только несколько разных значений, пользователю может быть трудно понять, где именно возникают границы различий. Например, в базе данных имеется 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, если его данные сортируются по категориям в алфавитном порядке.
Рис. 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
(так как такое значение не может существовать обычно).
Чтобы использовать этот метод, необходимо выполнить следующие действия:
- Программное извлечение данных для привязки к GridView (экземпляру
ProductsDataTable
) - Сортировка данных на основе свойств и
SortDirection
GridViewSortExpression
- Выполните итерацию
ProductsRows
вProductsDataTable
, в которой находятся различия в отсортированных столбцах. - На каждой границе группы вставьте экземпляр записи-разделителя
ProductsRow
в таблицу DataTable, дляCategoryID
которой задано значение-1
(или любое другое обозначение, принятое для пометки записи как записи-разделителя). - После внедрения строк разделителя программно привяжите данные к 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.
Рис. 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)
исходного Render
Page
метода класса все элементы управления на странице будут отрисованы, создавая разметку на основе их иерархии элементов управления. Поэтому крайне важно, чтобы мы оба вызывали 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).
Рис. 5. Интерфейс сортировки включает заголовки группы сортировки при сортировке по BoundFields (щелкните для просмотра полноразмерного изображения)
Рис. 6. Заголовки группы сортировки отсутствуют при сортировке CheckBoxField (щелкните для просмотра полноразмерного изображения)
Причина отсутствия заголовков группы сортировки при сортировке по CheckBoxField заключается в том, что код в настоящее время использует только TableCell
свойство s Text
для определения значения отсортированного столбца для каждой строки. Для CheckBoxFields TableCell
свойство s Text
является пустой строкой; вместо этого значение доступно через веб-элемент управления CheckBox, который находится в TableCell
коллекции s Controls
.
Для обработки типов полей, отличных от BoundFields, необходимо дополнить код, в котором currentValue
переменная назначается проверка для существования CheckBox в TableCell
Controls
коллекции 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).
Рис. 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.