Dar formato a los controles DataList y Repeater en función de los datos (VB)

por Scott Mitchell

Descargar PDF

En este tutorial se describen ejemplos de cómo se da formato a la apariencia de los controles DataList y Repeater, ya sea mediante funciones de formato dentro de plantillas o controlando el evento DataBound.

Introducción

Como vimos en el tutorial anterior, DataList ofrece una serie de propiedades relacionadas con el estilo que afectan a su apariencia. En concreto, vimos cómo asignar clases CSS predeterminadas a las propiedades HeaderStyle, ItemStyle, AlternatingItemStyle y SelectedItemStyle de DataList. Además de estas cuatro propiedades, DataList incluye otras propiedades relacionadas con el estilo, como Font, ForeColor, BackColor y BorderWidth, por nombrar algunas. El control Repeater no contiene ninguna propiedad relacionada con el estilo. Cualquier configuración de estilo de este tipo debe realizarse directamente dentro del marcado en las plantillas de Repeater.

Pero a menudo, el formato de los datos depende de los propios datos. Por ejemplo, al enumerar productos, es posible que queramos mostrar la información del producto en un color de fuente gris claro si se interrumpe, o es posible que queramos resaltar el valor UnitsInStock si es cero. Como vimos en los tutoriales anteriores, GridView, DetailsView y FormView ofrecen dos formas distintas de dar formato a su apariencia en función de sus datos:

  • El evento DataBound crea un controlador de eventos para el evento DataBound adecuado, que se desencadena después de que los datos se hayan enlazado a cada elemento (para GridView era el evento RowDataBound; para DataList y Repeater es el evento ItemDataBound). En ese controlador de eventos, se pueden examinar los datos enlazados y adoptar decisiones de formato. Hemos examinado esta técnica en el tutorial Formato personalizado basado en datos.
  • Funciones de formato en plantillas. Cuando se usan TemplateFields en los controles DetailsView o GridView, o una plantilla en el control FormView, podemos agregar una función de formato a la clase de código subyacente de la página ASP.NET, la capa lógica de negocios o cualquier otra biblioteca de clases accesible desde la aplicación web. Esta función de formato puede aceptar un número arbitrario de parámetros de entrada, pero debe devolver el HTML para representarlo en la plantilla. Las funciones de formato se examinaron por primera vez en el tutorial Uso de objetos TemplateField en el control GridView.

Ambas técnicas de formato están disponibles con los controles DataList y Repeater. En este tutorial se describen ejemplos que usan las dos técnicas para ambos controles.

Uso del controlador de eventos ItemDataBound

Cuando los datos se enlazan a un objeto DataList, ya sea desde un control de origen de datos o asignando datos mediante programación a la propiedad DataSource del control y la llamada a su método DataBind(), se desencadena el evento DataBinding de DataList, se muestra el origen de datos y cada registro de datos se enlaza a DataList. Para cada registro del origen de datos, DataList crea un objeto DataListItem que se enlaza al registro actual. Durante este proceso, DataList genera dos eventos:

  • ItemCreated se desencadena después de que se haya creado DataListItem.
  • ItemDataBound se desencadena después de que el registro actual se haya enlazado a DataListItem.

En los pasos siguientes se describe el proceso de enlace de datos para el control DataList.

  1. Se desencadena el evento DataBinding de DataList.

  2. Los datos se enlazan a DataList.

    Para cada registro del origen de datos:

    1. Cree un objeto DataListItem.
    2. Desencadenar el evento ItemCreated.
    3. Enlace el registro a DataListItem.
    4. Desencadene el evento ItemDataBound.
    5. Agregue DataListItem a la colección Items.

Al enlazar datos al control Repeater, progresa mediante la misma secuencia exacta de pasos. La única diferencia es que, en lugar de crear instancias de DataListItem, Repeater usa RepeaterItem.

Nota:

Es posible que el lector astuto haya observado una ligera anomalía entre la secuencia de pasos cuando DataList y Repeater están enlazados a los datos frente a cuando GridView está enlazado a los datos. Al final del proceso de enlace de datos, GridView genera el evento DataBound, pero ni el control DataList ni Repeater tienen este evento. Esto se debe a que los controles DataList y Repeater se crearon en el periodo en el que existía ASP.NET 1.x, antes de que el patrón de controlador de eventos de nivel anterior y posterior se hubiera vuelto común.

Al igual que con GridView, una opción a fin de dar formato en función de los datos es crear un controlador de eventos para el evento ItemDataBound. Este controlador de eventos inspeccionaría los datos que se habían enlazado a DataListItem o RepeaterItem, y afectarían al formato del control según sea necesario.

Para el control DataList, los cambios de formato para todo el elemento se pueden implementar mediante las propiedades relacionadas con el estilo DataListItem, que incluyen el estándar Font, ForeColor, BackColor, CssClass, etc. Para afectar al formato de determinados controles web dentro de la plantilla DataList, es necesario acceder mediante programación y modificar el estilo de esos controles web. Hemos visto cómo hacer esto en el tutorial Formato personalizado basado en datos. Al igual que el control Repeater, la clase RepeaterItem no tiene propiedades relacionadas con el estilo; por lo tanto, todos los cambios relacionados con el estilo realizados en un elemento RepeaterItem en el controlador de eventos ItemDataBound deben realizarse mediante programación para acceder a los controles web y actualizarlos dentro de la plantilla.

Dado que las técnica de formato ItemDataBound de DataList y Repeater son prácticamente idénticas, nuestro ejemplo se centrará en el uso de DataList.

Paso 1: Mostrar información del producto en DataList

Antes de preocuparnos por el formato, primero vamos a crear una página que use DataList para mostrar la información del producto. En el tutorial anterior creamos un objeto DataList cuyo objeto ItemTemplate mostraba el nombre, categoría, proveedor, cantidad por unidad y precio de cada producto. Vamos a repetir esta función en este tutorial. Para ello, puede volver a crear DataList y su ObjectDataSource desde cero, o bien copiar esos controles desde la página creada en el tutorial anterior (Basics.aspx) y pegarlos en la página de este tutorial (Formatting.aspx).

Una vez que haya replicado las funciones DataList y ObjectDataSource de Basics.aspx en Formatting.aspx, dedique un momento a cambiar la propiedad ID de DataList de DataList1 a un elemento ItemDataBoundFormattingExample, más descriptivo. A continuación, vea DataList en un explorador. Como se muestra en la Figura 1, la única diferencia de formato entre cada producto es que el color de fondo alterna.

The Products are Listed in the DataList Control

Figura 1: los productos aparecen en el control DataList (haga clic para ver la imagen a tamaño completo)

Para este tutorial, vamos a dar formato a DataList de modo que, cualquier producto con un precio inferior a 20,00 USD, tendrá su nombre y precio unitario resaltado en amarillo.

Paso 2: Determinar mediante programación el valor de los datos en el controlador de eventos ItemDataBound

Puesto que solo esos productos con un precio inferior a 20,00 USD tendrán aplicado el formato personalizado, debemos poder determinar el precio de cada producto. Al enlazar datos a un objeto DataList, este enumera los registros de su origen de datos y, para cada registro, crea una instancia DataListItem, lo que enlaza el registro del origen de datos a DataListItem. Una vez enlazados los datos del registro concreto al objeto DataListItem actual, se desencadena el evento ItemDataBound de DataList. Podemos crear un controlador de eventos para este evento a fin de inspeccionar los valores de datos del objeto DataListItem actual y, en función de esos valores, realizar los cambios de formato necesarios.

Cree un evento ItemDataBound para DataList y agregue el código siguiente:

Protected Sub ItemDataBoundFormattingExample_ItemDataBound _
    (sender As Object, e As DataListItemEventArgs) _
    Handles ItemDataBoundFormattingExample.ItemDataBound
    If e.Item.ItemType = ListItemType.Item OrElse _
       e.Item.ItemType = ListItemType.AlternatingItem Then
        ' Programmatically reference the ProductsRow instance
        ' bound to this DataListItem
        Dim product As Northwind.ProductsRow = _
            CType(CType(e.Item.DataItem, System.Data.DataRowView).Row, _
                Northwind.ProductsRow)
        ' See if the UnitPrice is not NULL and less than $20.00
        If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 20 Then
            ' TODO: Highlight the product's name and price
        End If
    End If
End Sub

Aunque el concepto y la semántica detrás del controlador de eventos ItemDataBound de DataList son los mismos que los que usa el controlador de eventos RowDataBound de GridView en el tutorial Formato personalizado basado en datos, la sintaxis difiere ligeramente. Cuando se desencadena el evento ItemDataBound, el objeto DataListItem que se acaba de enlazar a los datos se pasa al controlador de eventos correspondiente mediante e.Item (en lugar de e.Row, como con el controlador de eventos RowDataBound de GridView). El controlador de eventos ItemDataBound de DataList se desencadena para cada fila agregada a DataList, incluidas las filas de encabezado, las de pie de página y las de separadores. Pero la información del producto solo está enlazada a las filas de datos. Por lo tanto, al usar el evento ItemDataBound para inspeccionar los datos enlazados a DataList, primero debemos asegurarnos de que estamos trabajando con un elemento de datos. Esto se puede lograr comprobando la propiedad ItemType de DataListItem, que puede tener uno de los ocho valores siguientes:

  • AlternatingItem
  • EditItem
  • Footer
  • Header
  • Item
  • Pager
  • SelectedItem
  • Separator

Item y AlternatingItem``DataListItem conforman los elementos de datos de DataList. Suponiendo que estamos trabajando con un objeto Item o AlternatingItem, accedemos a la instancia ProductsRow real enlazada al objeto DataListItem actual. La propiedad DataItem de DataListItem contiene una referencia al objeto DataRowView, cuya propiedad Row proporciona una referencia al objeto ProductsRow.

A continuación, comprobamos la propiedad UnitPrice de la instancia ProductsRow. Dado que el campo UnitPrice de la tabla Products permite valores NULL, antes de intentar acceder a la propiedad UnitPrice, primero debemos comprobar si tiene un valor NULL mediante el método IsUnitPriceNull(). Si el valor UnitPrice no es NULL, se comprueba si es inferior a 20,00 USD. Si realmente es inferior a 20,00 USD, será necesario aplicar el formato personalizado.

Paso 3: Resaltar el nombre y el precio del producto

Una vez que sabemos que el precio de un producto es inferior a 20,00 USD, todo lo que queda es resaltar su nombre y precio. Para ello, primero debemos hacer referencia mediante programación a los controles Label en el objeto ItemTemplate en el que se muestran el nombre y el precio del producto. A continuación, necesitamos que muestren un fondo amarillo. Esta información de formato se puede aplicar modificando directamente las propiedades BackColor de Label (LabelID.BackColor = Color.Yellow); lo ideal es que todos los asuntos relacionados con la presentación se expresen mediante hojas de estilos en cascada. De hecho, ya tenemos una hoja de estilos que proporciona el formato deseado definido en Styles.css - AffordablePriceEmphasis, que se creó y se explicó en el tutorial Formato personalizado basado en datos.

Para aplicar el formato, basta con establecer las dos propiedades CssClass de controles web Label en AffordablePriceEmphasis, como se muestra en el código siguiente:

' Highlight the product name and unit price Labels
' First, get a reference to the two Label Web controls
Dim ProductNameLabel As Label = CType(e.Item.FindControl("ProductNameLabel"), Label)
Dim UnitPriceLabel As Label = CType(e.Item.FindControl("UnitPriceLabel"), Label)
' Next, set their CssClass properties
If ProductNameLabel IsNot Nothing Then
    ProductNameLabel.CssClass = "AffordablePriceEmphasis"
End If
If UnitPriceLabel IsNot Nothing Then
    UnitPriceLabel.CssClass = "AffordablePriceEmphasis"
End If

Una vez completado el controlador de eventos ItemDataBound, vuelva a visitar la página Formatting.aspx en un explorador. Como se muestra en la Figura 2, esos productos con un precio inferior a 20,00 USD tienen resaltado su nombre y precio.

Those Products Less than $20.00 are Highlighted

Figura 2: esos productos inferiores a 20,00 USD están resaltados (haga clic para ver la imagen a tamaño completo).

Nota:

Dado que DataList se representa como <table> HTML, sus instancias de DataListItem tienen propiedades relacionadas con el estilo que se pueden establecer para aplicar un estilo específico a todo el elemento. Por ejemplo, si queríamos resaltar todo el elemento en amarillo cuando su precio era inferior a 20,00 USD, podríamos haber reemplazado el código que hacía referencia a las etiquetas y establecer sus propiedades CssClass con la línea de código siguiente: e.Item.CssClass = "AffordablePriceEmphasis" (vea la Figura 3).

Pero los elementos RepeaterItem que componen el control Repeater no ofrecen estas propiedades de nivel de estilo. Por lo tanto, aplicar formato personalizado a Repeater requiere la aplicación de propiedades de estilo a los controles web dentro de las plantillas de Repeater, al igual que hicimos en la Figura 2.

The Entire Product Item is Highlighted for Products Under $20.00

Figura 3: todo el elemento de producto está resaltado para productos menores de 20,00 USD (haga clic para ver la imagen a tamaño completo).

Uso de funciones de formato desde dentro de la plantilla

En el tutorial Uso de objetos TemplateField en el control GridView vimos cómo usar una función de formato dentro de un objeto TemplateField de GridView para aplicar formato personalizado basado en los datos enlazados a las filas de GridView. Una función de formato es un método que se puede invocar desde una plantilla y devuelve el código HTML que se va a emitir en su lugar. Las funciones de formato pueden residir en la clase de código subyacente de la página ASP.NET o pueden centralizarse en archivos de clase de la carpeta App_Code o en un proyecto de biblioteca de clases independiente. Mover la función de formato fuera de la clase de código subyacente de la página ASP.NET es ideal si planea usar la misma función de formato en varias páginas ASP.NET o en otras aplicaciones web ASP.NET.

Para mostrar las funciones de formato, vamos a tener la información del producto que incluya el texto [DISCONTINUED] junto al nombre del producto si se interrumpe. Además, vamos a tener el precio resaltado en amarillo si es menor que 20,00 USD (como hicimos en el ejemplo del controlador de eventos ItemDataBound); si el precio es 20,00 USD o más, en vez de mostrar el precio real, aparecerá el texto: Llame para obtener un presupuesto. En la Figura 4 se muestra una captura de pantalla de la lista de productos con estas reglas de formato aplicadas.

Screenshot showing products listed in the DataList control, with the price of products costing more than $20.00 replaced with the text, 'Please call for a price quote.'

Figura 4: para productos caros, el texto Llame para obtener un presupuesto reemplaza al precio (haga clic para ver la imagen a tamaño completo).

Paso 1: Crear las funciones de formato

En este ejemplo necesitamos dos funciones de formato, una que muestra el nombre del producto junto con el texto [DISCONTINUED], si es necesario, y otro que muestra un precio resaltado si es inferior a 20,00 USD o, de lo contrario, el texto: Llame para obtener un presupuesto. Vamos a crear estas funciones en la clase de código subyacente de la página ASP.NET y a asignarles el nombre DisplayProductNameAndDiscontinuedStatus y DisplayPrice. Ambos métodos deben devolver el HTML para representarse como una cadena y ambos deben marcarse Protected (o Public) para poder invocarse desde la parte de sintaxis declarativa de la página ASP.NET. El código de estos dos métodos es el siguiente:

Protected Function DisplayProductNameAndDiscontinuedStatus _
    (productName As String, discontinued As Boolean) As String
    ' Return just the productName if discontinued is false
    If Not discontinued Then
        Return productName
    Else
        ' otherwise, return the productName appended with the text "[DISCONTINUED]"
        Return String.Concat(productName, " [DISCONTINUED]")
    End If
End Function
Protected Function DisplayPrice(product As Northwind.ProductsRow) As String
    ' If price is less than $20.00, return the price, highlighted
    If Not product.IsUnitPriceNull() AndAlso product.UnitPrice < 20 Then
        Return String.Concat("<span class="AffordablePriceEmphasis">", _
                             product.UnitPrice.ToString("C"), "</span>")
    Else
        ' Otherwise return the text, "Please call for a price quote"
        Return "<span>Please call for a price quote</span>"
    End If
End Function

Tenga en cuenta que el método DisplayProductNameAndDiscontinuedStatus acepta los valores de los campos de datos productName y discontinued como valores escalares, mientras que el método DisplayPrice acepta una instancia de ProductsRow (en lugar de un valor escalar unitPrice). Cualquier enfoque funcionará, pero si la función de formato está trabajando con valores escalares que pueden contener valores NULL de base de datos (como UnitPrice; ni ProductName ni Discontinued permiten valores NULL), se debe tener especial cuidado en el control de estas entradas escalares.

En concreto, el parámetro de entrada debe ser de tipo Object, ya que el valor entrante podría ser una instancia de DBNull en lugar del tipo de datos esperado. Además, se debe realizar una comprobación para determinar si el valor entrante es o no un valor NULL de base de datos. Es decir, si queremos que el método DisplayPrice acepte el precio como un valor escalar, tendríamos que usar el código siguiente:

Protected Function DisplayPrice(ByVal unitPrice As Object) As String
    ' If price is less than $20.00, return the price, highlighted
    If Not Convert.IsDBNull(unitPrice) AndAlso CType(unitPrice, Decimal) < 20 Then
        Return String.Concat("<span class="AffordablePriceEmphasis">", _
            CType(unitPrice, Decimal).ToString("C"), "</span>")
    Else
        ' Otherwise return the text, "Please call for a price quote"
        Return "<span>Please call for a price quote</span>"
    End If
End Function

Tenga en cuenta que el parámetro de entrada unitPrice es de tipo Object y que la instrucción condicional se ha modificado para determinar si unitPrice es DBNull o no. Además, dado que el parámetro de entrada unitPrice se pasa como un objeto Object, debe convertirse a un valor decimal.

Paso 2: Llamar a la función de formato desde el objeto ItemTemplate de DataList

Con las funciones de formato agregadas a la clase de código subyacente de la página ASP.NET, todo lo que queda es invocar estas funciones de formato desde el objeto ItemTemplate de DataList. Para llamar a una función de formato desde una plantilla, coloque la llamada de función dentro de la sintaxis de enlace de datos:

<%# MethodName(inputParameter1, inputParameter2, ...) %>

En el objeto ItemTemplate de DataList, el control web Label ProductNameLabel muestra actualmente el nombre del producto asignando a su propiedad Text el resultado de <%# Eval("ProductName") %>. Para que muestre el nombre más el texto [DISCONTINUED], si es necesario, actualice la sintaxis declarativa a fin de que, en su lugar, asigne a la propiedad Text el valor del método DisplayProductNameAndDiscontinuedStatus. Al hacerlo, debemos pasar el nombre del producto y los valores interrumpidos mediante la sintaxis Eval("columnName"). Eval devuelve un valor de tipo Object, pero el método DisplayProductNameAndDiscontinuedStatus espera parámetros de entrada de tipo String y Boolean; por lo tanto, debemos convertir los valores que devuelve el método Eval a los tipos de parámetros de entrada esperados, de la siguiente manera:

<h4>
    <asp:Label ID="ProductNameLabel" runat="server"
        Text='<%# DisplayProductNameAndDiscontinuedStatus((string) Eval("ProductName"),
              (bool) Eval("Discontinued")) %>'>
    </asp:Label>
</h4>

Para mostrar el precio, simplemente podemos establecer la propiedad Text de Label UnitPriceLabel en el valor que devuelve el método DisplayPrice, al igual que hicimos para mostrar el nombre del producto y el texto [DISCONTINUED]. Pero en lugar de pasar UnitPrice como un parámetro de entrada escalar, en su lugar pasamos toda la instancia de ProductsRow:

<asp:Label ID="UnitPriceLabel" runat="server"
    Text='<%# DisplayPrice((Northwind.ProductsRow)
          ((System.Data.DataRowView) Container.DataItem).Row) %>'>
</asp:Label>

Con las llamadas a las funciones de formato realizadas, dedique un momento a ver nuestro progreso en un explorador. La pantalla debe ser similar a la Figura 5, incluyendo en los productos interrumpidos el texto [DISCONTINUED] y con el precio de esos productos que cuestan más de 20,00 USD reemplazado por el texto Llame para obtener un presupuesto.

Screenshot showing products listed in the DataList control, with the price of products costing more than $20.00 replaced with the text, 'Please call for a price quote', and the text '[DISCONTINUED]' appended to the name of discontinued products.

Figura 5: para productos caros, el texto Llame para obtener un presupuesto reemplaza al precio (haga clic para ver la imagen a tamaño completo).

Resumen

El formato del contenido de un control DataList o Repeater basado en los datos se puede realizar mediante dos técnicas. La primera técnica consiste en crear un controlador de eventos para el evento ItemDataBound, que se desencadena a medida que cada registro del origen de datos está enlazado a un objeto DataListItem o RepeaterItem nuevo. En el controlador de eventos ItemDataBound, los datos del elemento actual se pueden examinar y, después, se puede aplicar formato al contenido de la plantilla o, para objetos DataListItem, a todo el elemento.

Como alternativa, el formato personalizado se puede realizar mediante funciones de formato. Una función de formato es un método que se puede invocar desde las plantillas de DataList o Repeater que devuelve el HTML que se va a emitir en su lugar. A menudo, el HTML que devuelve una función de formato lo determinan los valores que se enlazan al elemento actual. Estos valores se pueden pasar a la función de formato, ya sea como valores escalares o pasando todo el objeto enlazado al elemento (tal como la instancia de ProductsRow).

¡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 in 24 Hours. Puede ponerse en contacto con él via mitchell@4GuysFromRolla.com. o a través de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. Los revisores principales de este tutorial han sido Yaakov Ellis, Randy Schmidt y Liz Shulok. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.