Filtrado de maestro y detalles con dos DropDownLists (C#)

por Scott Mitchell

Descargar PDF

En este tutorial se expande la relación maestro/detalle para agregar una tercera capa, mediante dos controles DropDownList para seleccionar los registros primario y primario principal deseados.

Introducción

En el tutorial anterior se ha examinado cómo mostrar un informe de maestro y detalles sencillo mediante un único control DropDownList rellenado con las categorías y un control GridView en el que se muestran los productos que pertenecen a la categoría seleccionada. Este patrón de informe funciona bien al mostrar registros que tienen una relación de uno a varios y se puede ampliar fácilmente para escenarios que incluyen varias relaciones de uno a varios. Por ejemplo, un sistema de entrada de pedidos tendría tablas que se corresponden a clientes, pedidos y elementos de línea de pedidos. Un cliente determinado puede tener varios pedidos con cada pedido que consta de varios elementos. Estos datos se pueden presentar al usuario con dos controles DropDownList y un control GridView. El primer control DropDownList tendría un elemento de lista para cada cliente de la base de datos, y el contenido del segundo son los pedidos realizado por el cliente seleccionado. En un control GridView se enumerarían los elementos de línea del pedido seleccionado.

Aunque la base de datos Northwind incluye la información canónica de detalles de cliente, pedido y detalles del pedido en sus tablas Customers, Ordersy Order Details, estas tablas no se capturan en esta arquitectura. Pero todavía se puede ilustrar el uso de dos controles DropDownList dependientes. El primer control DropDownList enumerará las categorías y el segundo los productos que pertenecen a la categoría seleccionada. Después, en un control DetailsView se enumerarán los detalles del producto seleccionado.

Paso 1: Creación y relleno del control DropDownList Categories

El primer objetivo es agregar el control DropDownList que enumera las categorías. Estos pasos se han examinado en detalle en el tutorial anterior, pero se resumen aquí para su integridad.

Abra la página MasterDetailsDetails.aspx de la carpeta Filtering, agregue un control DropDownList a la página, establezca su propiedad ID en Categories y, después, haga clic en el vínculo Configurar origen de datos en su etiqueta inteligente. En el Asistente para configuración de orígenes de datos, elija agregar un nuevo origen de datos.

Add a New Data Source for the DropDownList

Figura 1: Adición de un nuevo origen de datos para el control DropDownList (Haga clic para ver la imagen a tamaño completo)

El nuevo origen de datos debe ser, naturalmente, una instancia de ObjectDataSource. Asigne el nombre CategoriesDataSource a esta instancia de ObjectDataSource y haga que invoque el GetCategories() método del objeto CategoriesBLL.

Choose to Use the CategoriesBLL Class

Figura 2: Elección del uso de la clase CategoriesBLL (Haga clic para ver la imagen a tamaño completo)

Configure the ObjectDataSource to Use the GetCategories() Method

Figura 3: Configuración de ObjectDataSource para usar el método GetCategories() (Haga clic para ver la imagen a tamaño completo)

Después de configurar ObjectDataSource, todavía es necesario especificar qué campo de origen de datos se debe mostrar en el control DropDownList Categories y cuál se debe configurar como el valor del elemento de lista. Establezca el campo CategoryName como pantalla y CategoryID como valor para cada elemento de lista.

Have the DropDownList Display the CategoryName Field and Use CategoryID as the Value

Figura 4: Representación en DropDownList del campo CategoryName y uso de CategoryID como valor (Haga clic para ver la imagen a tamaño completo)

En este punto, tiene un control DropDownList (Categories) que se rellena con los registros de la tabla Categories. Cuando el usuario elige una nueva categoría en DropDownList, querrá que se produzca un postback para actualizar el control DropDownList de productos que se va a crear en el paso 2. Por tanto, active la opción Habilitar AutoPostBack desde la etiqueta inteligente del control DropDownList categories.

Enable AutoPostBack for the Categories DropDownList

Figura 5: Habilitación de AutoPostBack para el control DropDownList Categories (Haga clic para ver la imagen a tamaño completo)

Paso 2: Representación de los productos de la categoría seleccionada en un segundo control DropDownList

Una vez que se ha completado el control DropDownList Categories, el siguiente paso consiste en mostrar un control DropDownList de productos pertenecientes a la categoría seleccionada. Para ello, agregue otro control DropDownList a la página con el nombre ProductsByCategory. Al igual que con el control DropDownList Categories, cree un objeto ObjectDataSource para el control DropDownList ProductsByCategory con el nombre ProductsByCategoryDataSource.

Add a New Data Source for the ProductsByCategory DropDownList

Figura 6: Adición de un nuevo origen de datos para el control DropDownList ProductsByCategory (Haga clic para ver la imagen a tamaño completo)

Create a New ObjectDataSource Named ProductsByCategoryDataSource

Figura 7: Creación de una instancia de ObjectDataSource con el nombre ProductsByCategoryDataSource (Haga clic para ver la imagen a tamaño completo)

Como el control DropDownList ProductsByCategory solo debe mostrar los productos que pertenecen a la categoría seleccionada, haga que ObjectDataSource invoque el método GetProductsByCategoryID(categoryID) desde el objeto ProductsBLL.

Screenshot of the Configure Data Source - productsByCategoryDataSource window with ProductsBLL selected and the Next button highlighted.

Figura 8: Elección del uso de la clase ProductsBLL (Haga clic para ver la imagen a tamaño completo)

Configure the ObjectDataSource to Use the GetProductsByCategoryID(categoryID) Method

Figura 9: Configuración de ObjectDataSource para usar el método GetProductsByCategoryID(categoryID) (Haga clic para ver la imagen a tamaño completo)

En el paso final del asistente, es necesario especificar el valor del parámetro categoryID. Asigne este parámetro al elemento seleccionado en el control DropDownList Categories.

Pull the categoryID Parameter Value from the Categories DropDownList

Figura 10: Extracción del valor de parámetro categoryID del control DropDownList Categories (Haga clic para ver la imagen a tamaño completo)

Con la instancia de ObjectDataSource configurada, solo queda especificar qué campos del origen de datos se usan para la presentación y el valor de los elementos de DropDownList. Muestre el campo ProductName y use el campo ProductID como valor.

Specify the Data Source Fields Used for the DropDownList's ListItems' Text and Value Properties

Figura 11: Especificación de los campos del origen de datos usados para las propiedades Text y Value del control DropDownList ListItem (Haga clic para ver la imagen a tamaño completo)

Con ObjectDataSource y el control DropDownList ProductsByCategory configurados, en la página se mostrarán dos controles DropDownList: el primero enumerará todas las categorías, mientras que el segundo enumerará los productos que pertenecen a la categoría seleccionada. Cuando el usuario selecciona una nueva categoría en el primer control DropDownList, se producirá un postback y se volverá a enlazar el segundo control DropDownList, en el que se mostrarán los productos que pertenecen a la categoría recién seleccionada. En las figuras 12 y 13 se muestran MasterDetailsDetails.aspx en acción cuando se ven desde un explorador.

When First Visiting the Page, the Beverages Category is Selected

Figura 12: Cuando se visita la página por primera vez, la categoría Beverages está seleccionada (Haga clic para ver la imagen a tamaño completo)

Choosing a Different Category Displays the New Category's Products

Figura 13: Al elegir otra categoría se muestran los productos de la nueva categoría (Haga clic para ver la imagen a tamaño completo)

Actualmente, cuando cambia el control DropDownList productsByCategory, noprovoca un postback. Pero querrá que se produzca un postback una vez que agregue un control DetailsView para mostrar los detalles del producto seleccionado (paso 3). Por tanto, active la casilla Habilitar AutoPostBack desde la etiqueta inteligente del control DropDownList productsByCategory.

Enable the AutoPostBack Feature for the productsByCategory DropDownList

Figura 14: Habilitación de AutoPostBack para el control DropDownList productsByCategory (Haga clic para ver la imagen a tamaño completo)

Paso 3: Uso de un control DetailsView para mostrar los detalles del producto seleccionado

El último paso consiste en mostrar los detalles del producto seleccionado en un control DetailsView. Para ello, agregue un control DetailsView a la página, establezca su propiedad ID en ProductDetails y cree un objeto ObjectDataSource para ella. Configure esta instancia de ObjectDataSource para extraer sus datos del método GetProductByProductID(productID) de la clase ProductsBLL mediante el valor seleccionado del control DropDownList ProductsByCategory para el valor del parámetro productID.

Screenshot of the Configure Data Source - productsByCategoryDataSource window where ProductsBLL is selected and the Next button is highlighted.

Figura 15: Elección del uso de la clase ProductsBLL (Haga clic para ver la imagen a tamaño completo)

Configure the ObjectDataSource to Use the GetProductByProductID(productID) Method

Figura 16: Configuración de ObjectDataSource para usar el método GetProductByProductID(productID) (Haga clic para ver la imagen a tamaño completo)

Pull the productID Parameter Value from the ProductsByCategory DropDownList

Figura 17: Extracción del valor de parámetro productID del control DropDownList ProductsByCategory (Haga clic para ver la imagen a tamaño completo)

Puede elegir mostrar cualquiera de los campos disponibles en el control DetailsView. Aquí se ha optado por quitar los campos ProductID, SupplierID y CategoryID, y por reordenar y dar formato a los campos restantes. Además, se han borrado las propiedades Height y Width de DetailsView, lo que permite que se expanda al ancho necesario para mostrar mejor sus datos en lugar de restringirse a un tamaño especificado. A continuación se muestra el marcado completo:

<asp:DetailsView ID="ProductDetails" runat="server"
    AutoGenerateRows="False" DataKeyNames="ProductID"
    DataSourceID="ObjectDataSource1" EnableViewState="False">
    <Fields>
        <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="QuantityPerUnit"
          HeaderText="Qty/Unit" SortExpression="QuantityPerUnit" />
        <asp:BoundField DataField="UnitPrice"
          DataFormatString="{0:c}" HeaderText="Price"
          HtmlEncode="False" SortExpression="UnitPrice" />
        <asp:BoundField DataField="UnitsInStock"
          HeaderText="UnitsInStock" SortExpression="Units In Stock" />
        <asp:BoundField DataField="UnitsOnOrder"
          HeaderText="UnitsOnOrder" SortExpression="Units On Order" />
        <asp:BoundField DataField="ReorderLevel"
          HeaderText="ReorderLevel" SortExpression="Reorder Level" />
        <asp:CheckBoxField DataField="Discontinued"
          HeaderText="Discontinued" SortExpression="Discontinued" />
    </Fields>
</asp:DetailsView>

Dedique un momento a probar la página MasterDetailsDetails.aspx en un explorador. A primera vista puede parecer que todo funciona según lo deseado, pero hay un problema sutil. Al elegir una nueva categoría, el control DropDownList ProductsByCategory se actualiza para incluir esos productos para la categoría seleccionada, pero en el control DetailsView ProductDetails se sigue mostrando la información del producto anterior. El control DetailsView se actualiza al elegir otro producto para la categoría seleccionada. Además, si realiza pruebas lo suficientemente exhaustivas, verá que si elige continuamente nuevas categorías (como Beverages en el control DropDownList Categories, luego Condiments y después Confections) cada selección de categoría hace que se actualice el control DetailsView ProductDetails.

Para concretar este problema, se examinará un ejemplo específico. Cuando visita la página por primera vez, se selecciona la categoría Beverages y los productos relacionados se cargan en el control DropDownList ProductsByCategory. Chai es el producto seleccionado y sus detalles se muestran en el control DetailsView ProductDetails, como se muestra en la figura 18.

The Selected Product's Details are Displayed in a DetailsView

Figura 18: Los detalles del producto seleccionado se muestran en un control DetailsView (Haga clic para ver la imagen a tamaño completo)

Si cambia la selección de la categoría de Beverages a Condiments, se produce un postback y se actualiza el control DropDownList ProductsByCategory en consecuencia, pero en DetailsView se siguen mostrando los detalles de Chai.

The Previously Selected Product's Details are Still Displayed

Figura 19: Los detalles del producto seleccionado previamente se siguen mostrando (Haga clic para ver la imagen a tamaño completo)

La selección de un nuevo producto en la lista actualiza DetailsView según lo previsto. Si elige una nueva categoría después de cambiar el producto, no se actualizará DetailsView. Pero si en lugar de elegir un nuevo producto selecciona una nueva categoría, DetailsView se actualizará. ¿Qué sucede aquí?

Es un problema de tiempo en el ciclo de vida de la página. Cada vez que se solicita una página, pasa por varios pasos a fin de representarse. En uno de estos pasos, los controles ObjectDataSource comprueban si alguno de sus valores SelectParameters ha cambiado. Si es así, el control web de datos enlazado a ObjectDataSource sabe que necesita actualizar su presentación. Por ejemplo, cuando se selecciona una categoría nueva, la instancia de ObjectDataSource ProductsByCategoryDataSource detecta que sus valores de parámetro han cambiado y el control DropDownList ProductsByCategory se vuelve a enlazar y obtiene los productos de la categoría seleccionada.

El problema que surge en esta situación es que el punto del ciclo de vida de la página en el que ObjectDataSources comprueba los parámetros modificados se produce antes de que se vuelvan a enlazar los controles web de datos asociados. Por tanto, al seleccionar una nueva categoría, la instancia de ObjectDataSource ProductsByCategoryDataSource detecta un cambio en el valor de su parámetro. Pero la instancia de ObjectDataSource que usa el control DetailsView ProductDetails no observa ningún cambio de este tipo porque el control DropDownList ProductsByCategory aún no se ha vuelto a enlazar. Más adelante en el ciclo de vida, el control DropDownList ProductsByCategory se vuelve a enlazar a su ObjectDataSource, y captura los productos de la categoría recién seleccionada. Aunque el valor del control DropDownList ProductsByCategory ha cambiado, el objeto ObjectDataSource ProductDetails de DetailsView ya ha realizado su comprobación de valores de parámetro; por tanto, DetailsView muestra sus resultados anteriores. Esta interacción se muestra en la figura 20.

The ProductsByCategory DropDownList Value Changes After the ProductDetails DetailsView's ObjectDataSource Checks for Changes

Figura 20: El valor del control DropDownList ProductsByCategory cambia después de que el objeto ObjectDataSource del control DetailsView ProductDetails comprueba los cambios (Haga clic para ver la imagen A tamaño completo)

Para solucionar esto, es necesario volver a enlazar explícitamente el control DetailsView ProductDetails después de que se haya enlazado el control DropDownList ProductsByCategory. Para ello, se llama al método DataBind() del control DetailsView ProductDetails cuando se desencadena el evento DataBound del control DropDownList ProductsByCategory. Agregue el siguiente código de controlador de eventos a la clase de código subyacente de la página MasterDetailsDetails.aspx (consulte "Establecimiento mediante programación de los valores de parámetro de ObjectDataSource" para obtener una explicación sobre cómo agregar un controlador de eventos):

protected void ProductsByCategory_DataBound(object sender, EventArgs e)
{
    ProductDetails.DataBind();
}

Una vez que se agrega esta llamada explícita al método DataBind()del control DetailsView ProductDetails, el tutorial funciona según lo previsto. En la figura 21 se resalta cómo este cambio ha corregido el problema anterior.

The ProductDetails DetailsView is Explicitly Refreshed When the ProductsByCategory DropDownList's DataBound Event Fires

Figura 21: El control DetailsView ProductDetails se actualiza explícitamente cuando se desencadena el evento DataBound del control DropDownList ProductsByCategory (Haga clic para ver la imagen a tamaño completo)

Resumen

DropDownList actúa como un elemento de interfaz de usuario idóneo para informes de maestro y detalles en los que hay una relación de uno a varios entre los registros maestro y de detalles. En el tutorial anterior ha visto cómo usar un único control DropDownList para filtrar los productos mostrados por la categoría seleccionada. En este tutorial, ha reemplazado el control GridView de productos por un control DropDownList y se ha usado un control DetailsView para mostrar los detalles del producto seleccionado. Los conceptos descritos en este tutorial se pueden extender fácilmente a los modelos de datos que implican varias relaciones de uno a varios, como clientes, pedidos y elementos de pedido. En general, siempre puede agregar un control DropDownList para cada una de las entidades "uno" en las relaciones de uno a varios.

¡Feliz programación!

Acerca del autor

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

Agradecimientos especiales a

Muchos revisores han evaluado esta serie de tutoriales. El revisor principal de este tutorial ha sido Hilton Giesenow. ¿Le interesa revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.