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

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 mediante el control del evento DataBound.

Introducción

Como ha visto en el tutorial anterior, el control DataList ofrece una serie de propiedades relacionadas con el estilo que afectan a su apariencia. En concreto, ha visto 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 valor de estilo de este tipo se debe plasmar 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 quiera mostrar la información del producto en un color de fuente gris claro si está descatalogado o resaltar el valor UnitsInStock si es cero. Como ha visto 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. Esta técnica se ha examinado en el tutorial Formato personalizado basado en datos.
  • Funciones de formato en plantillas. Cuando se usan elementos TemplateField en los controles DetailsView o GridView, o una plantilla en el control FormView, se puede agregar una función de formato a la clase de código subyacente de la página ASP.NET, la capa de 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. Desencadene 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ía 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 la versión estándar de Font, ForeColor, BackColor, CssClass, etc. Para afectar al formato de determinados controles web dentro de la plantilla de DataList, es necesario acceder mediante programación y modificar el estilo de esos controles web. Ha 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 tanto, todos los cambios relacionados con el estilo realizados en un elemento RepeaterItem en el controlador de eventos ItemDataBound se deben realizar mediante programación para acceder a los controles web y actualizarlos dentro de la plantilla.

Como las técnicas de formato ItemDataBound de DataList y Repeater son prácticamente idénticas, este ejemplo se centrará en el uso de DataList.

Paso 1: Representación de información del producto en DataList

Antes de preocuparse por el formato, creará una página en la que se use DataList para mostrar la información del producto. En el tutorial anterior ha creado un control DataList cuyo objeto ItemTemplate mostraba el nombre, categoría, proveedor, cantidad por unidad y precio de cada producto. En este tutorial se repetirá esta funcionalidad. Para ello, puede volver a crear el control DataList y su objeto 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 la funcionalidad de 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 se 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, se dará formato a DataList de modo que, cualquier producto con un precio inferior a 20,00 USD, tendrá su nombre y precio unitario resaltados en amarillo.

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

Como solo los productos con un precio inferior a 20,00 USD tendrán aplicado el formato personalizado, es necesario poder determinar el precio de cada producto. Al enlazar datos a un control DataList, este enumera los registros de su origen de datos y, para cada registro, crea una instancia de DataListItem, lo que enlaza el registro del origen de datos a DataListItem. Una vez que se enlazan los datos del registro concreto al objeto DataListItem actual, se desencadena el evento ItemDataBound de DataList. Puede 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 void ItemDataBoundFormattingExample_ItemDataBound
    (object sender, DataListItemEventArgs e)
{
    if (e.Item.ItemType == ListItemType.Item ||
        e.Item.ItemType == ListItemType.AlternatingItem)
    {
        // Programmatically reference the ProductsRow instance bound
        // to this DataListItem
        Northwind.ProductsRow product =
            (Northwind.ProductsRow)((System.Data.DataRowView)e.Item.DataItem).Row;
        // See if the UnitPrice is not NULL and less than $20.00
        if (!product.IsUnitPriceNull() && product.UnitPrice < 20)
        {
            // TODO: Highlight the product's name and price
        }
    }
}

Aunque el concepto y la semántica 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 tanto, al usar el evento ItemDataBound para inspeccionar los datos enlazados a DataList, primero se debe asegurar de que trabaja con un elemento de datos. Esto se puede lograr si comprueba 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. Si trabaja con un objeto Item o AlternatingItem, acceda a la instancia de 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, se comprueba la propiedad UnitPrice de la instancia ProductsRow. Como el campo UnitPrice de la tabla Products permite valores NULL, antes de intentar acceder a la propiedad UnitPrice, primero debe 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: Resaltado del nombre y el precio del producto

Una vez que se sabe 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 se debe 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, es necesario que muestren un fondo amarillo. Esta información de formato se puede aplicar sin se modifican directamente las propiedades BackColor de Label (LabelID.BackColor = Color.Yellow); lo ideal es que todos los aspectos relacionados con la presentación se expresen mediante hojas de estilos en cascada. De hecho, ya hay una hoja de estilos que proporciona el formato deseado definido en Styles.css - AffordablePriceEmphasis, que se ha creado y explicado en el tutorial Formato personalizado basado en datos.

Para aplicar el formato, basta con establecer las propiedades CssClass de los dos 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
Label ProductNameLabel = (Label)e.Item.FindControl("ProductNameLabel");
Label UnitPriceLabel = (Label)e.Item.FindControl("UnitPriceLabel");
// Next, set their CssClass properties
if (ProductNameLabel != null)
    ProductNameLabel.CssClass = "AffordablePriceEmphasis";
if (UnitPriceLabel != null)
    UnitPriceLabel.CssClass = "AffordablePriceEmphasis";

Una vez que se complete el controlador de eventos ItemDataBound, vuelva a visitar la página Formatting.aspx en un explorador. Como se muestra en la figura 2, los 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: Los productos inferiores a 20,00 USD se resaltan (Haga clic para ver la imagen a tamaño completo)

Nota:

Como DataList se representa como un elemento <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 quisiera resaltar todo el elemento en amarillo cuando su precio sea inferior a 20,00 USD, podríamos reemplazar 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 tanto, para aplicar formato personalizado a Repeater es necesario aplicar propiedades de estilo a los controles web dentro de las plantillas de Repeater, como 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 ha visto 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, la información del producto incluirá el texto [DISCONTINUED] junto al nombre del producto si está descatalogado. Además, el precio se resaltará en amarillo si es menor que 20,00 USD (como 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 precio se reemplaza por el texto Llame para obtener un presupuesto (Haga clic para ver la imagen a tamaño completo)

Paso 1: Creación de las funciones de formato

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

protected string DisplayProductNameAndDiscontinuedStatus
    (string productName, bool discontinued)
{
    // Return just the productName if discontinued is false
    if (!discontinued)
        return productName;
    else
        // otherwise, return the productName appended with the text "[DISCONTINUED]"
        return string.Concat(productName, " [DISCONTINUED]");
}
protected string DisplayPrice(Northwind.ProductsRow product)
{
    // If price is less than $20.00, return the price, highlighted
    if (!product.IsUnitPriceNull() && product.UnitPrice < 20)
        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>";
}

Observe 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 trabaja 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 quiere que el método DisplayPrice acepte el precio como un valor escalar, tendría que usar el código siguiente:

protected string DisplayPrice(object unitPrice)
{
    // If price is less than $20.00, return the price, highlighted
    if (!Convert.IsDBNull(unitPrice) && ((decimal) unitPrice) < 20)
        return string.Concat("<span class=\"AffordablePriceEmphasis\">",
                              ((decimal) unitPrice).ToString("C"), "</span>");
    else
        // Otherwise return the text, "Please call for a price quote"
        return "<span>Please call for a price quote</span>";
}

Observe 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, como el parámetro de entrada unitPrice se pasa como un objeto Object, se debe convertir a un valor decimal.

Paso 2: Llamada 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 invocarlas 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 mediante la asignación a su propiedad Text del 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, debe pasar los valores de nombre del producto y si está descatalogado 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 tanto, debe 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 puede establecer la propiedad Text de Label UnitPriceLabel en el valor que devuelve el método DisplayPrice, como hizo para mostrar el nombre del producto y el texto [DISCONTINUED]. Pero en lugar de pasar UnitPrice como un parámetro de entrada escalar, se pasa 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 el progreso en un explorador. La pantalla debe ser similar a la figura 5, incluyendo en los productos descatalogados 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 precio se reemplaza por el texto Llame para obtener un presupuesto (Haga clic para ver la imagen a tamaño completo)

Resumen

El formato del contenido de un control DataList o Repeater basado en 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 se enlaza 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 código 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 bien pasar todo el objeto enlazado al elemento (como la instancia de ProductsRow).

¡Feliz programación!

Acerca del autor

Scott Mitchell, autor de siete libros de ASP y ASP.NET, y fundador de 4GuysFromRolla.com, trabaja 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 a través de mitchell@4GuysFromRolla.com. o de su blog, que se puede encontrar en http://ScottOnWriting.NET.

Agradecimientos especiales a

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