Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Al mostrar una larga lista de datos ordenados, puede ser muy útil agrupar los datos relacionados mediante la introducción de filas separadoras. En este tutorial veremos cómo crear una interfaz de usuario de clasificación de este tipo.
Introducción
Al mostrar una larga lista de datos ordenados en la que solo hay un puñado de valores diferentes en la columna ordenada, es posible que a un usuario final le resulte difícil discernir dónde se producen exactamente los límites de diferencia. Por ejemplo, hay 81 productos en la base de datos, pero solo nueve opciones de categoría diferentes (ocho categorías únicas más la NULL
opción). Considere el caso de un usuario que está interesado en examinar los productos que se incluyen en la categoría Mariscos. Desde una página que enumera todos los productos en un solo GridView, el usuario puede decidir que su mejor opción es ordenar los resultados por categoría, lo que agrupará todos los productos de mariscos. Después de ordenar por categoría, el usuario debe buscar en la lista dónde comienzan y terminan los productos agrupados en mariscos. Dado que los resultados están ordenados alfabéticamente por el nombre de la categoría, encontrar los productos del mar no es difícil, pero aún requiere escanear de cerca la lista de artículos en la cuadrícula.
Para ayudar a resaltar los límites entre los grupos ordenados, muchos sitios web emplean una interfaz de usuario que agrega un separador entre dichos grupos. Los separadores como los que se muestran en la Figura 1 permiten al usuario encontrar más rápidamente un grupo en particular e identificar sus límites, así como determinar qué grupos distintos existen en los datos.
Figura 1: Cada grupo de categorías está claramente identificado (haga clic para ver la imagen a tamaño completo)
En este tutorial veremos cómo crear una interfaz de usuario de clasificación de este tipo.
Paso 1: Crear un GridView estándar y ordenable
Antes de explorar cómo aumentar GridView para proporcionar la interfaz de ordenación mejorada, primero vamos a crear un GridView estándar y ordenable que enumere los productos. Para empezar, abra la página CustomSortingUI.aspx
en la carpeta PagingAndSorting
. Agregue un GridView a la página, establezca su ID
propiedad en ProductList
, y vincúlelo a un nuevo ObjectDataSource. Configure ObjectDataSource para que utilice el método s ProductsBLL
de la GetProducts()
clase para seleccionar registros.
A continuación, configure GridView de modo que solo contenga los ProductName
campos , , CategoryName
SupplierName
, y UnitPrice
BoundFields y el CheckBoxField discontinuado. Por último, configure GridView para que admita la ordenación activando la casilla Habilitar ordenación en la etiqueta inteligente de GridView (o estableciendo su AllowSorting
propiedad en true
). Después de realizar estas adiciones a la CustomSortingUI.aspx
página, el marcado declarativo debe tener un aspecto similar al siguiente:
<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>
Tómese un momento para ver nuestro progreso hasta ahora en un navegador. En la figura 2 se muestra el GridView que se puede ordenar cuando sus datos se ordenan por categoría en orden alfabético.
Figura 2: Los datos de GridView ordenables por categoría (haga clic para ver la imagen a tamaño completo)
Paso 2: Explorar técnicas para agregar las filas separadoras
Una vez completado el GridView genérico y ordenable, todo lo que queda es poder agregar las filas de separación en el GridView antes de cada grupo ordenado único. Pero, ¿cómo se pueden insertar estas filas en GridView? Básicamente, necesitamos iterar a través de las filas de GridView, determinar dónde se producen las diferencias entre los valores de la columna ordenada y, a continuación, agregar la fila de separación adecuada. Al pensar en este problema, parece natural que la solución se encuentre en algún lugar del controlador de eventos de RowDataBound
GridView. Como se explicó en el tutorial Formato personalizado basado en datos , este controlador de eventos se usa normalmente al aplicar formato de nivel de fila basado en los datos de la fila. Sin embargo, el RowDataBound
controlador de eventos no es la solución aquí, ya que las filas no se pueden agregar a GridView mediante programación desde este controlador de eventos. De hecho, la colección GridView s Rows
es de solo lectura.
Para agregar filas adicionales a GridView, tenemos tres opciones:
- Agregue estas filas separadoras de metadatos a los datos reales que están enlazados a GridView
- Una vez que GridView se haya enlazado a los datos, agregue instancias adicionales
TableRow
a la colección de controles de GridView - Cree un control de servidor personalizado que amplíe el control GridView e invalide los métodos responsables de construir la estructura de GridView
La creación de un control de servidor personalizado sería el mejor enfoque si esta funcionalidad fuera necesaria en muchas páginas web o en varios sitios web. Sin embargo, implicaría un poco de código y una exploración exhaustiva de las profundidades del funcionamiento interno de GridView. Por lo tanto, no consideraremos esa opción para este tutorial.
Las otras dos opciones, agregar filas de separadores a los datos reales que se enlazan a GridView y manipular la colección de controles de GridView después de que se haya enlazado, atacan el problema de manera diferente y merecen una discusión.
Adición de filas a los datos enlazados a GridView
Cuando GridView está enlazado a un origen de datos, crea un GridViewRow
para cada registro devuelto por el origen de datos. Por lo tanto, podemos insertar las filas de separador necesarias agregando registros de separador al origen de datos antes de vincularlo a GridView. La figura 3 ilustra este concepto.
Figura 3: Una técnica consiste en agregar filas separadoras a la fuente de datos
Utilizo el término registros separadores entre comillas porque no hay un registro separador especial; Más bien, debemos marcar de alguna manera que un registro determinado en la fuente de datos sirve como separador en lugar de una fila de datos normal. En nuestros ejemplos, volvemos a enlazar una ProductsDataTable
instancia a GridView, que se compone de ProductRows
. Podríamos marcar un registro como una fila separadora estableciendo su CategoryID
propiedad en -1
(ya que tal valor no podría existir normalmente).
Para utilizar esta técnica necesitamos realizar los siguientes pasos:
- Recupere mediante programación los datos que se van a enlazar a GridView (una
ProductsDataTable
instancia) - Ordene los datos en función de las propiedades y
SortExpression
SortDirection
GridView - Recorra en iteración el
ProductsRows
en elProductsDataTable
, buscando dónde se encuentran las diferencias en la columna ordenada - En cada límite de grupo, inserte una instancia de registro
ProductsRow
separador en DataTable, una que tenga su sCategoryID
establecido en-1
(o cualquier designación que se haya decidido para marcar un registro como un registro separador ) - Después de insertar las filas del separador, enlazar los datos mediante programación a GridView
Además de estos cinco pasos, también necesitaríamos proporcionar un controlador de eventos para el evento GridView RowDataBound
. Aquí, verificaríamos cada uno DataRow
y determinaríamos si era una fila separadora, una cuya CategoryID
configuración era -1
. Si es así, probablemente querríamos ajustar su formato o el texto que se muestra en la(s) celda(s).
El uso de esta técnica para insertar los límites del grupo de ordenación requiere un poco más de trabajo que el descrito anteriormente, ya que también debe proporcionar un controlador de eventos para el evento GridView Sorting
s y realizar un seguimiento de los SortExpression
valores y SortDirection
.
Manipulación de la colección de controles GridView después de que se haya enlazado a datos
En lugar de enviar mensajes a los datos antes de enlazarlos a GridView, podemos agregar las filas separadoras después de que los datos se hayan enlazado a GridView. El proceso de enlace de datos crea la jerarquía de controles de GridView, que en realidad es simplemente una Table
instancia compuesta por una colección de filas, cada una de las cuales está compuesta por una colección de celdas. En concreto, la colección de controles GridView contiene un Table
objeto en su raíz, un GridViewRow
(que se deriva de la TableRow
clase) para cada registro del DataSource
enlace a GridView y un TableCell
objeto en cada GridViewRow
instancia para cada campo de datos del DataSource
archivo .
Para agregar filas separadoras entre cada grupo de clasificación, podemos manipular directamente esta jerarquía de control una vez que se ha creado. Podemos estar seguros de que la jerarquía de controles de GridView se ha creado por última vez en el momento en que se representa la página. Por lo tanto, este enfoque invalida el método s Page
de la Render
clase, momento en el que la jerarquía de control final de GridView se actualiza para incluir las filas de separación necesarias. La figura 4 ilustra este proceso.
Figura 4: Una técnica alternativa manipula la jerarquía de controles de GridView (haga clic para ver la imagen a tamaño completo)
En este tutorial, usaremos este último enfoque para personalizar la experiencia del usuario de ordenación.
Nota:
El código que presento en este tutorial se basa en el ejemplo proporcionado en la entrada del blog de Teemu Keiski , Playing a Bit with GridView Sort Grouping.
Paso 3: Agregar las filas del separador a la jerarquía de controles de GridView s
Dado que solo queremos agregar las filas separadoras a la jerarquía de controles de GridView después de que se haya creado su jerarquía de controles y se haya creado por última vez en esa visita a la página, queremos realizar esta adición al final del ciclo de vida de la página, pero antes de que la jerarquía de controles GridView real se haya representado en HTML. El último punto posible en el que podemos lograr esto es el evento s Page
de la Render
clase, que podemos invalidar en nuestra clase de código subyacente usando la siguiente firma de método:
Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
' Add code to manipulate the GridView control hierarchy
MyBase.Render(writer)
End Sub
Cuando se invoca Page
el método original Render
de la base.Render(writer)
clase, se representará cada uno de los controles de la página, generando el marcado en función de su jerarquía de controles. Por lo tanto, es imperativo que ambos llamemos base.Render(writer)
a , para que la página se represente y que manipulemos la jerarquía de control de GridView antes de llamar base.Render(writer)
a , de modo que las filas de separación se hayan agregado a la jerarquía de control de GridView antes de que se represente.
Para inyectar los encabezados del grupo de ordenación, primero debemos asegurarnos de que el usuario haya solicitado que se ordenen los datos. De forma predeterminada, el contenido de GridView no se ordena y, por lo tanto, no es necesario introducir ningún encabezado de ordenación de grupo.
Nota:
Si desea que GridView se ordene por una columna determinada cuando la página se cargue por primera vez, llame al método GridView s Sort
en la primera visita a la página (pero no en las devoluciones posteriores). Para ello, agregue esta llamada en el controlador de eventos dentro de Page_Load
un if (!Page.IsPostBack)
condicional. Consulte la información del tutorial Datos de informe de paginación y ordenación para obtener más información sobre el Sort
método.
Suponiendo que los datos se han ordenado, nuestra siguiente tarea es determinar en qué columna se ordenaron los datos y, a continuación, examinar las filas en busca de diferencias en los valores de esa columna. El código siguiente garantiza que los datos se han ordenado y busca la columna por la que se han ordenado los datos:
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
Si GridView aún no se ha ordenado, la propiedad s de SortExpression
GridView no se habrá establecido. Por lo tanto, solo queremos agregar las filas separadoras si esta propiedad tiene algún valor. Si es así, lo siguiente que tenemos que hacer es determinar el índice de la columna por la que se ordenaron los datos. Esto se logra recorriendo en bucle la colección s Columns
, buscando la columna cuya SortExpression
propiedad es igual a la propiedad s de SortExpression
GridView. Además del índice de la columna, también tomamos la HeaderText
propiedad, que se usa cuando se muestran las filas del separador.
Con el índice de la columna por la que se ordenan los datos, el último paso es enumerar las filas de GridView. Para cada fila tenemos que determinar si el valor de la columna ordenada difiere del valor de la columna ordenada de la fila anterior. Si es así, necesitamos inyectar una nueva GridViewRow
instancia en la jerarquía de control. Esto se logra con el código siguiente:
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
Este código comienza haciendo referencia mediante programación al Table
objeto que se encuentra en la raíz de la jerarquía de controles de GridView y creando una variable de cadena denominada lastValue
.
lastValue
se utiliza para comparar el valor de la columna ordenada de la fila actual con el valor de la fila anterior. A continuación, se enumera la colección GridView Rows
y para cada fila se almacena en la currentValue
variable el valor de la columna ordenada.
Nota:
Para determinar el valor de la columna ordenada de la fila en particular, utilizo la propiedad s Text
de la celda. Esto funciona bien para BoundFields, pero no funcionará como se desea para TemplateFields, CheckBoxFields, etc. En breve veremos cómo tener en cuenta los campos GridView alternativos.
A continuación, se comparan las currentValue
variables y lastValue
. Si difieren, debemos agregar una nueva fila separadora a la jerarquía de control. Esto se logra determinando el índice de la GridViewRow
en la colección del Table
objeto, creando nuevas Rows
instancias GridViewRow
y TableCell
y, a continuación, agregando y a TableCell
GridViewRow
la jerarquía de controles.
Tenga en cuenta que la fila del separador s lone TableCell
tiene un formato que abarca todo el ancho de GridView, tiene formato mediante la SortHeaderRowStyle
clase CSS y tiene su Text
propiedad tal que muestra el nombre del grupo de ordenación (como Category ) y el valor del grupo s (como Beverages ). Por último, lastValue
se actualiza al valor de currentValue
.
La clase CSS utilizada para dar formato a la fila SortHeaderRowStyle
del encabezado del grupo de ordenación debe especificarse en el Styles.css
archivo. Siéntase libre de usar cualquier configuración de estilo que le atraiga; Utilicé lo siguiente:
.SortHeaderRowStyle
{
background-color: #c00;
text-align: left;
font-weight: bold;
color: White;
}
Con el código actual, la interfaz de ordenación agrega encabezados de grupo de ordenación al ordenar por cualquier BoundField (consulte la figura 5, que muestra una captura de pantalla al ordenar por proveedor). Sin embargo, al ordenar por cualquier otro tipo de campo (como CheckBoxField o TemplateField), los encabezados de los grupos de ordenación no se encuentran en ninguna parte (consulte la figura 6).
Figura 5: La interfaz de ordenación incluye encabezados de grupo de ordenación al ordenar por BoundFields (haga clic para ver la imagen a tamaño completo)
Figura 6: Faltan los encabezados del grupo de clasificación al ordenar un campo CheckBoxField (haga clic para ver la imagen a tamaño completo)
La razón por la que faltan los encabezados de grupo de ordenación al ordenar por CheckBoxField es porque el código actualmente usa solo la TableCell
propiedad s Text
para determinar el valor de la columna ordenada para cada fila. En el caso de CheckBoxFields, la TableCell
propiedad s Text
es una cadena vacía; en su lugar, el valor está disponible a través de un control web CheckBox que reside en la TableCell
colección s Controls
.
Para manejar tipos de campo que no sean BoundFields, necesitamos aumentar el código donde se asigna la currentValue
variable para verificar la existencia de una CheckBox en la TableCell
colección s Controls
. En lugar de usar currentValue = gvr.Cells(sortColumnIndex).Text
, reemplace este código por lo siguiente:
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
Este código examina la columna TableCell
ordenada de la fila actual para determinar si hay controles en la Controls
colección. Si los hay, y el primer control es un CheckBox, la currentValue
variable se establece en Yes o No, dependiendo de la propiedad de Checked
CheckBox. De lo contrario, el valor se toma de la TableCell
propiedad s Text
. Esta lógica se puede replicar para controlar la ordenación de los TemplateFields que puedan existir en GridView.
Con la adición de código anterior, los encabezados del grupo de ordenación ahora están presentes al ordenar por el CheckBoxField descontinuado (consulte la figura 7).
Figura 7: Los encabezados del grupo de ordenación ahora están presentes al ordenar un CheckBoxField (haga clic para ver la imagen a tamaño completo)
Nota:
Si tiene productos con NULL
valores de base de datos para los CategoryID
campos , SupplierID
, o , esos UnitPrice
valores aparecerán como cadenas vacías en GridView de forma predeterminada, lo que significa que el texto de la fila del separador para esos productos con NULL
valores será como Categoría: (es decir, no hay ningún nombre después de Categoría: como con Categoría: Bebidas ). Si desea que se muestre un valor aquí, puede establecer la propiedad BoundFields NullDisplayText
en el texto que desea mostrar o puede agregar una instrucción condicional en el método Render al asignar la currentValue
propiedad s de la fila del Text
separador.
Resumen
GridView no incluye muchas opciones integradas para personalizar la interfaz de ordenación. Sin embargo, con un poco de código de bajo nivel, es posible ajustar la jerarquía de controles de GridView para crear una interfaz más personalizada. En este tutorial vimos cómo agregar una fila separadora de grupo de ordenación para un GridView ordenable, que identifica más fácilmente los distintos grupos y los límites de esos grupos. Para ver ejemplos adicionales de interfaces de clasificación personalizadas, consulte la entrada del blog A Few ASP.NET 2.0 GridView Sorting Tips and Tricks deScott Guthrie.
¡Feliz programación!
Acerca del autor
Scott Mitchell, autor de siete libros de ASP/ASP.NET y fundador de 4GuysFromRolla.com, ha estado trabajando con tecnologías web de Microsoft desde 1998. Scott trabaja como consultor independiente, entrenador y escritor. Su último libro es Sams Teach Yourself ASP.NET 2.0 en 24 horas. Se puede contactar con él en mitchell@4GuysFromRolla.com.