Agregar una columna GridView de botones de radio (C#)

por Scott Mitchell

Descargar PDF

En este tutorial se explica cómo agregar una columna de botones de radio a un control GridView para proporcionar al usuario una manera más intuitiva de seleccionar una sola fila de GridView.

Introducción

El control GridView ofrece una amplia funcionalidad integrada. Incluye varios campos diferentes para mostrar texto, imágenes, hipervínculos y botones. Admite plantillas para una mayor personalización. Con unos pocos clics del mouse, es posible crear un control GridView donde cada fila se puede seleccionar mediante un botón, o bien habilitar las funcionalidades de edición o eliminación. A pesar de la gran cantidad de características proporcionadas, a menudo habrá situaciones en las que se tendrán que agregar características adicionales no admitidas. En este tutorial y en los dos siguientes se verá cómo mejorar la funcionalidad de GridView para incluir características adicionales.

Este tutorial y el siguiente se centran en mejorar el proceso de selección de filas. Como se ha explicado en el tutorial Maestro y detalles mediante un control GridView maestro seleccionable con un control DetailView de detalles, se puede agregar un campo CommandField a GridView que incluya un botón Seleccionar. Al hacer clic en él, se produce un postback y la propiedad SelectedIndex de GridView se actualiza al índice de la fila en cuyo botón Seleccionar se ha hecho clic. En el tutorial Maestro y detalles mediante un control GridView maestro seleccionable con un control DetailView de detalles, ha visto cómo usar esta característica para mostrar los detalles de la fila de GridView seleccionada.

Aunque el botón Seleccionar funciona en muchas situaciones, puede que no vaya bien en otras. En lugar de usar un botón, se suelen usar otros dos elementos de interfaz de usuario para la selección: el botón de radio y la casilla. Se puede aumentar GridView para que, en lugar de un botón Seleccionar, cada fila contenga un botón de radio o una casilla. En escenarios en los que el usuario solo puede seleccionar uno de los registros de GridView, es posible que se prefiera el botón de radio al botón Seleccionar. En situaciones en las que el usuario puede seleccionar potencialmente varios registros, como en una aplicación de correo electrónico basada en web, donde es posible que un usuario quiera seleccionar varios mensajes para eliminar, la casilla ofrece una funcionalidad que no está disponible en las interfaces de usuario del botón Seleccionar o del botón de radio.

En este tutorial se explica cómo agregar una columna de botones de radio al control GridView. En el siguiente tutorial se explorará el uso de casillas.

Paso 1: Creación de las páginas web con GridView

Antes de empezar a mejorar el control GridView para incluir una columna de botones de radio, dedique un momento a crear las páginas ASP.NET en el proyecto de sitio web que necesitará para este tutorial y los dos siguientes. Para empezar, agregue una nueva carpeta denominada EnhancedGridView. Después, agregue las siguientes páginas ASP.NET a esa carpeta y asegúrese de asociar cada página a la página maestra Site.master:

  • Default.aspx
  • RadioButtonField.aspx
  • CheckBoxField.aspx
  • InsertThroughFooter.aspx

Add the ASP.NET Pages for the SqlDataSource-Related Tutorials

Figura 1: Adición de las páginas ASP.NET para los tutoriales relacionados con el control SqlDataSource

Igual que en las otras carpetas, Default.aspx en la carpeta EnhancedGridView enumerará los tutoriales en su sección. Recuerde que el control de usuario SectionLevelTutorialListing.ascx proporciona esta funcionalidad. Por tanto, para agregar este control de usuario a Default.aspx arrástrelo desde el Explorador de soluciones a la vista Diseño de la página.

Add the SectionLevelTutorialListing.ascx User Control to Default.aspx

Figura 2: Adición del control de usuario SectionLevelTutorialListing.ascx a Default.aspx (Haga clic para ver la imagen a tamaño completo)

Por último, agregue estas cuatro páginas como entradas al archivo Web.sitemap. En concreto, agregue el marcado siguiente después del elemento <siteMapNode> del control Uso de SqlDataSource:

<siteMapNode 
    title="Enhancing the GridView" 
    url="~/EnhancedGridView/Default.aspx" 
    description="Augment the user experience of the GridView control.">
    <siteMapNode 
        url="~/EnhancedGridView/RadioButtonField.aspx" 
        title="Selection via a Radio Button Column" 
        description="Explore how to add a column of radio buttons in the GridView." />
    <siteMapNode 
        url="~/EnhancedGridView/CheckBoxField.aspx" 
        title="Selection via a Checkbox Column" 
        description="Select multiple records in the GridView by using a column of 
            checkboxes." />
    <siteMapNode 
        url="~/EnhancedGridView/InsertThroughFooter.aspx" 
        title="Add New Records through the Footer" 
        description="Learn how to allow users to add new records through the 
            GridView's footer." />
</siteMapNode>

Después de actualizar Web.sitemap, dedique un momento a ver el sitio web de los tutoriales desde un explorador. Ahora el menú de la izquierda incluye elementos para los tutoriales sobre edición, inserción y eliminación.

The Site Map Now Includes Entries for the Enhancing the GridView Tutorials

Figura 3: El mapa del sitio ahora incluye entradas para mejorar los tutoriales de GridView

Paso 2: Representación de los proveedores en un control GridView

Para este tutorial, creará un control GridView que enumera los proveedores de EE. UU., y cada fila del control GridView proporciona un botón de radio. Después de seleccionar un proveedor mediante el botón de radio, el usuario puede ver los productos del proveedor si hace clic en un botón. Aunque esta tarea puede parecer trivial, hay una serie de sutilezas que la hacen particularmente complicada. Antes de profundizar en estas sutilezas, primero se configurará un control GridView que enumere los proveedores.

Para empezar, abra la página RadioButtonField.aspx en la carpeta EnhancedGridView y arrastre un control GridView desde el Cuadro de herramientas al Diseñador. Establezca ID de GridView en Suppliers y, desde su etiqueta inteligente, elija crear un origen de datos. En concreto, cree un elemento ObjectDataSource denominado SuppliersDataSource que extraiga sus datos del objeto SuppliersBLL.

Create a New ObjectDataSource Named SuppliersDataSource

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

Screenshot of the Configure Data Source - SuppliersDataSource window with the business object SuppliersBLL selected and the Next button highlighted.

Figura 5: Configuración de ObjectDataSource para usar la clase SuppliersBLL (Haga clic para ver la imagen a tamaño completo)

Como solo quiere enumerar los proveedores de Estados Unidos, elija el método GetSuppliersByCountry(country) en la lista desplegable de la pestaña SELECT.

Screenshot of the Configure Data Source - SuppliersDataSource window with the SELECT tab open. The method option GetSupplierByCountry is selected and the Next button is highlighted.

Figura 6: Configuración de ObjectDataSource para usar la clase SuppliersBLL (Haga clic para ver la imagen a tamaño completo)

En la pestaña UPDATE, seleccione la opción (None) y haga clic en Siguiente.

Screenshot of the Configure Data Source - SuppliersDataSource window with the UPDATE tab open. The method option (None) is selected and the Next button is highlighted.

Figura 7: Configuración de ObjectDataSource para usar la clase SuppliersBLL (Haga clic para ver la imagen a tamaño completo)

Como el método GetSuppliersByCountry(country) acepta un parámetro, el Asistente para configuración del origen de datos le pide el origen de ese parámetro. Para especificar un valor codificado de forma rígida (USA, en este ejemplo), deje la lista desplegable Origen del parámetro establecida en Ninguno y escriba el valor predeterminado en el cuadro de texto. Haga clic en Finalizar para completar el asistente.

Use USA as the Default Value for the country Parameter

Figura 8: Uso de USA como valor predeterminado para el parámetro country (Haga clic para ver la imagen a tamaño completo)

Después de completar el asistente, el control GridView incluirá un objeto BoundField para cada uno de los campos de datos de proveedor. Quite todas las instancias de BoundField excepto CompanyName, City y Country, y cambie el nombre de la propiedad HeaderText de BoundField CompanyName a Supplier. Después de hacerlo, la sintaxis declarativa de GridView y ObjectDataSource debería tener un aspecto similar al siguiente.

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
    DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:BoundField DataField="CompanyName" HeaderText="Supplier" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="SuppliersDataSource" runat="server" 
    OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetSuppliersByCountry" TypeName="SuppliersBLL">
    <SelectParameters>
        <asp:Parameter DefaultValue="USA" Name="country" Type="String" />
    </SelectParameters>
</asp:ObjectDataSource>

En este tutorial, se permitirá que el usuario vea los productos del proveedor seleccionado en la misma página que la lista de proveedores, o bien en otra página. Para ello, agregue dos controles web Button a la página. Se han establecido los valores ID de estos dos botones en ListProducts y SendToProducts, con la idea de que, cuando se haga clic en ListProducts, se produzca un postback y se muestren los productos del proveedor seleccionado en la misma página, pero que cuando se haga clic en SendToProducts, se redirija al usuario a otra página donde se enumeran los productos.

En la figura 9 se muestran el control GridView Suppliers y los dos controles web Button cuando se ven en un explorador.

Those Suppliers from the USA Have Their Name, City, and Country Information Listed

Figura 9: Se muestra el nombre, la ciudad y el país de esos proveedores de Estados Unidos (Haga clic para ver la imagen a tamaño completo)

Paso 3: Adición de una columna de botones de radio

En este momento, el control GridView Suppliers tiene tres instancias de BoundField que muestran el nombre de empresa, la ciudad y el país de cada proveedor de Estados Unidos. Pero todavía falta una columna de botones de radio. Desafortunadamente, GridView no incluye un objeto RadioButtonField integrado; de lo contrario, podría agregarlo a la cuadrícula y listo. En su lugar, puede agregar un objeto TemplateField y configurar su elemento ItemTemplate para representar un botón de radio, lo que da como resultado un botón de radio para cada fila de GridView.

Inicialmente, podría suponer que la interfaz de usuario deseada se puede implementar mediante la adición de un control web RadioButton al elemento ItemTemplate de un objeto TemplateField. Aunque esto agregará realmente un solo botón de radio a cada fila de GridView, los botones de radio no se pueden agrupar y, por tanto, no son mutuamente excluyentes. Es decir, un usuario final puede seleccionar varios botones de radio simultáneamente desde el control GridView.

Aunque el uso de un objeto TemplateField con controles web RadioButton no ofrece la funcionalidad que necesita, se implementará este enfoque, ya que vale la pena examinar por qué no se agrupan los botones de radio resultantes. Para empezar, agregue un objeto TemplateField al control GridView Suppliers, lo que lo convierte en el campo situado más a la izquierda. Después, desde la etiqueta inteligente del control GridView, haga clic en el vínculo Editar plantillas y arrastre un control web RadioButton desde el Cuadro de herramientas al elemento ItemTemplate de TemplateField (vea la figura 10). Establezca la propiedad ID de RadioButton en RowSelector y la propiedad GroupName en SuppliersGroup.

Add a RadioButton Web Control to the ItemTemplate

Figura 10: Adición de un control web RadioButton a ItemTemplate (Haga clic para ver la imagen a tamaño completo)

Después de realizar estas adiciones mediante el Diseñador, el marcado del control GridView debería ser similar al siguiente:

<asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
    DataKeyNames="SupplierID" DataSourceID="SuppliersDataSource" 
    EnableViewState="False">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:RadioButton ID="RowSelector" runat="server" 
                    GroupName="SuppliersGroup" />
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CompanyName" HeaderText="Supplier" 
            SortExpression="CompanyName" />
        <asp:BoundField DataField="City" HeaderText="City" 
            SortExpression="City" />
        <asp:BoundField DataField="Country" HeaderText="Country" 
            SortExpression="Country" />
    </Columns>
</asp:GridView>

La propiedad GroupName de RadioButton es lo que se usa para agrupar una serie de botones de radio. Todos los controles RadioButton con el mismo valor GroupName se consideran agrupados; solo se puede seleccionar un botón de radio de un grupo a la vez. La propiedad GroupName especifica el valor del atributo name del botón de radio representado. El explorador examina los atributos name de los botones de radio para determinar las agrupaciones de botones de radio.

Con el control web RadioButton agregado a ItemTemplate, visite esta página desde un explorador y haga clic en los botones de radio de las filas de la cuadrícula. Observe que los botones de radio no están agrupados, lo que permite seleccionar todas las filas, como se muestra en la figura 11.

The GridView s Radio Buttons are Not Grouped

Figura 11: Los botones de radio del control GridView no están agrupados (Haga clic para ver la imagen a tamaño completo)

La razón por la que los botones de radio no están agrupados es porque sus atributos name representados son diferentes, a pesar de tener la misma configuración para la propiedad GroupName. Para ver estas diferencias, visualice el código fuente desde el explorador y examine el marcado del botón de radio:

<input id="ctl00_MainContent_Suppliers_ctl02_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl02$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl03_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl03$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl04_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl04$SuppliersGroup" 
    type="radio" value="RowSelector" />
<input id="ctl00_MainContent_Suppliers_ctl05_RowSelector" 
    name="ctl00$MainContent$Suppliers$ctl05$SuppliersGroup" 
    type="radio" value="RowSelector" />

Observe cómo los atributos name y id no son los valores exactos tal como se especifican en la ventana Propiedades, sino que llevan antepuestos otros valores ID. Los valores ID adicionales agregados delante de los atributos id y name representados son los valores ID de los controles primarios de los botones de radio, el valor ID de GridViewRow, el valor ID de GridView, el valor IDdel control de contenido y el valor ID de Web Forms. Estos valores ID se agregan para que cada control web representado en el control GridView tenga valores id y name únicos.

Cada control representado necesita un valor name y id diferente, para que el explorador identifique de forma única cada control en el lado cliente y el servidor web identifique qué acción o cambio se ha producido en el postback. Por ejemplo, imagine que quiere ejecutar código del lado servidor cada vez que se cambie el estado de selección de un objeto RadioButton. Podría hacerlo se establece la propiedad AutoPostBack de RadioButton en true y crea un controlador de eventos para el evento CheckChanged. Pero si los valores name y id representados para todos los botones de radio fueran los mismos, con un postback no se podría determinar en qué objeto RadioButton específico se ha hecho clic.

En resumen, no se puede crear una columna de botones de radio en un control GridView mediante el control web RadioButton. En su lugar, debe usar técnicas bastante arcaicas para garantizar que se inserte el marcado adecuado en cada fila del control GridView.

Nota:

Igual que el control web RadioButton, al agregar el control HTML del botón de radio a una plantilla, incluirá un atributo name único, lo que hace que los botones de radio de la cuadrícula no se agrupen. Si no tiene experiencia con los controles HTML, puede ignorar esta nota, ya que los controles HTML rara vez se usan, especialmente en ASP.NET 2.0. Pero si le interesa aprender más, vea la entrada de blog de K. Scott Allen sobre controles web y controles HTML.

Uso de un control literal para insertar marcado de botón de radio

Para agrupar correctamente todos los botones de radio dentro del control GridView, es necesario insertar manualmente el marcado de los botones de radio en ItemTemplate. Cada botón de radio necesita el mismo atributo name, pero debe tener un atributo id único (en caso de que se quiera acceder a un botón de radio desde el script del lado cliente). Cuando un usuario seleccione un botón de radio y aplique postback a la página, el explorador devolverá el valor del atributo value del botón de radio seleccionado. Por tanto, cada botón de radio necesitará un atributo value único. Por último, en el postback debe asegurarse de agregar el atributo checked al botón de radio seleccionado; de lo contrario, después de que el usuario realice una selección y aplique postback, los botones de radio volverán a su estado predeterminado (ninguno seleccionado).

Hay dos enfoques que se pueden adoptar para insertar marcado de bajo nivel en una plantilla. Uno consiste en realizar una combinación de marcado y llamadas a métodos de formato definidos en la clase de código subyacente. Esta técnica se ha explicado por primera vez en el tutorial Uso de objetos TemplateField en el control GridView. En este caso, podría tener un aspecto similar al siguiente:

<input type="radio" id='<%# GetUniqueRadioButtonID(...) %>' 
    name='SuppliersGroup' value='<%# GetRadioButtonValue(...) %>' ... />

Aquí, GetUniqueRadioButton y GetRadioButtonValue serían métodos definidos en la clase de código subyacente que devuelven los valores de atributo id y value adecuados para cada botón de radio. Este enfoque funciona bien para asignar los atributos id y value, pero se queda corto cuando se necesita especificar el valor del atributo checked, porque la sintaxis de enlace de datos solo se ejecuta cuando los datos se enlazan por primera vez al control GridView. Por tanto, si el control GridView tiene el estado de visualización habilitado, los métodos de formato solo se activarán cuando la página se cargue por primera vez (o cuando el control GridView vuelva explícitamente al origen de datos) y, por tanto, no se llamará a la función que establece el atributo checked en el postback. Es un problema bastante sutil y va un poco más allá del ámbito de este artículo, así que no se profundizará más. Pero le animamos a intentar usar el enfoque anterior y trabajarlo hasta el punto en el que se quede atascado. Aunque este ejercicio no le acercará a una versión funcional, le ayudará a obtener una comprensión más profunda del control GridView y el ciclo de vida del enlace de datos.

El otro enfoque para insertar marcado personalizado y de bajo nivel en una plantilla, es que se usará para este tutorial, la adición de un control Literal a la plantilla. Después, en el controlador de eventos RowCreated o RowDataBound del control GridView, se puede acceder al control Literal mediante programación y se puede establecer su propiedad Text en el marcado que se va a emitir.

Para empezar, quite el objeto RadioButton de la instancia ItemTemplate de TemplateField y reemplácelo por un control Literal. Establezca el valor ID del control Literal en RadioButtonMarkup.

Add a Literal Control to the ItemTemplate

Figura 12: Adición de un control Literal a ItemTemplate (Haga clic para ver la imagen a tamaño completo)

A continuación, cree un controlador de eventos para el evento RowCreated de GridView. El evento RowCreated se desencadena una vez por cada fila agregada, independientemente de si los datos se devuelven al control GridView. Esto significa que, incluso en un postback, cuando los datos se vuelven a cargar desde el estado de visualización, el evento RowCreated se desencadena y este es el motivo por el que se usa en lugar de RowDataBound (que se activa solo cuando los datos se enlazan explícitamente al control web de datos).

En este controlador de eventos, solo querrá continuar si se trata de una fila de datos. Para cada fila de datos quiere hacer referencia mediante programación al control Literal RadioButtonMarkup y establecer su propiedad Text en el marcado que se va a emitir. Como se muestra en el código siguiente, el marcado emitido crea un botón de radio cuyo atributo name se establece en SuppliersGroup, cuyo atributo id se establece en RowSelectorX (donde X es el índice de la fila de GridView) y cuyo atributo value se establece en el índice de la fila de GridView.

protected void Suppliers_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // Grab a reference to the Literal control
        Literal output = (Literal)e.Row.FindControl("RadioButtonMarkup");
        // Output the markup except for the "checked" attribute
        output.Text = string.Format(
            @"<input type="radio" name="SuppliersGroup" " +
            @"id="RowSelector{0}" value="{0}" />", e.Row.RowIndex);
    }
}

Cuando se selecciona una fila de GridView y se produce un postback, le interesa el valor SupplierID del proveedor seleccionado. Por tanto, podría pensarse que el valor de cada botón de radio debe ser el valor SupplierID (en lugar del índice de la fila del control GridView). Aunque esto puede funcionar en determinadas circunstancias, sería un riesgo de seguridad aceptar y procesar a ciegas un valor SupplierID. En este control GridView, por ejemplo, solo se enumeran los proveedores de Estados Unidos. Pero si SupplierID se pasa directamente desde el botón de radio, ¿qué impide que un usuario malintencionado manipule el valor SupplierID devuelto en el postback? Al usar el índice de fila como value y obtener el valor de SupplierID en el postback desde la colección DataKeys, puede asegurarse de que el usuario solo utiliza uno de los valores SupplierID asociados a una de las filas del control GridView.

Después de agregar este código de controlador de eventos, dedique un minuto a probar la página en un explorador. En primer lugar, tenga en cuenta que solo se puede seleccionar un botón de radio en la cuadrícula a la vez. Pero al seleccionar un botón de radio y hacer clic en uno de los botones, se produce un postback y los botones de radio vuelven a su estado inicial (es decir, con un postback, el botón de radio seleccionado ya no está seleccionado). Para corregir esto, es necesario aumentar el controlador de eventos RowCreated para que inspeccione el índice del botón de radio seleccionado enviado desde el postback y para que agregue el atributo checked="checked" al marcado emitido de las coincidencias del índice de filas.

Cuando se produce un postback, el explorador devuelve los objetos name y value del botón de radio seleccionado. El valor se puede recuperar mediante programación con Request.Form["name"]. La propiedad Request.Form proporciona una clase NameValueCollection que representa las variables de formulario. Las variables de formulario son los nombres y los valores de los campos de formulario de la página web, y se envían el explorador web cada vez que se produce un postback. Como el atributo name representado de los botones de radio del control GridView es SuppliersGroup, cuando se aplica postback a la página web, el explorador devuelve SuppliersGroup=valueOfSelectedRadioButton al servidor web (junto con los demás campos de formulario). Después, se puede acceder a esta información desde la propiedad Request.Form mediante: Request.Form["SuppliersGroup"].

Como tendrá que determinar el índice del botón de radio seleccionado no solo en el controlador de eventos RowCreated, sino también en los controladores de eventos Click para los controles web Button, se agregará una propiedad SuppliersSelectedIndex a la clase de código subyacente que devuelve -1 si no se selecciona ningún botón de radio y devuelve el índice seleccionado si se selecciona uno de los botones de radio.

private int SuppliersSelectedIndex
{
    get
    {
        if (string.IsNullOrEmpty(Request.Form["SuppliersGroup"]))
            return -1;
        else
            return Convert.ToInt32(Request.Form["SuppliersGroup"]);
    }
}

Con esta propiedad agregada, se agrega el marcado checked="checked" en el controlador de eventos RowCreated cuando SuppliersSelectedIndex es igual a e.Row.RowIndex. Actualice el controlador de eventos para incluir esta lógica:

protected void Suppliers_RowCreated(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        // Grab a reference to the Literal control
        Literal output = (Literal)e.Row.FindControl("RadioButtonMarkup");
        // Output the markup except for the "checked" attribute
        output.Text = string.Format(
            @"<input type="radio" name="SuppliersGroup" " +
            @"id="RowSelector{0}" value="{0}"", e.Row.RowIndex);
        // See if we need to add the "checked" attribute
        if (SuppliersSelectedIndex == e.Row.RowIndex)
            output.Text += @" checked="checked"";
        // Add the closing tag
        output.Text += " />";
    }
}

Con este cambio, el botón de radio seleccionado permanece seleccionado después de un postback. Ahora que tiene la capacidad de especificar qué botón de radio está seleccionado, podría cambiar el comportamiento para que cuando se visite la página por primera vez, se seleccione el primer botón de radio de la fila del control GridView (en lugar de no tener ningún botón de radio seleccionado de forma predeterminada, que es el comportamiento actual). Para que el primer botón de radio se seleccione de forma predeterminada, simplemente cambie la instrucción if (SuppliersSelectedIndex == e.Row.RowIndex) a lo siguiente: if (SuppliersSelectedIndex == e.Row.RowIndex || (!Page.IsPostBack && e.Row.RowIndex == 0)).

Hasta ahora ha agregado una columna de botones de radio agrupados al control GridView que permite seleccionar una sola fila del GridView y recordarla entre postbacks. Los pasos siguientes son mostrar los productos que proporciona el proveedor seleccionado. En el paso 4 verá cómo redirigir al usuario a otra página, enviando también la información de SupplierID. En el paso 5, verá cómo mostrar los productos del proveedor seleccionado en un control GridView en la misma página.

Nota:

En lugar de usar TemplateField (el foco de este largo paso 3), podría crear una clase personalizada DataControlField que represente la interfaz de usuario y la funcionalidad adecuadas. La clase DataControlField es la clase base de la que derivan los objetos BoundField, CheckBoxField, TemplateField y otros campos integrados de los controles GridView y DetailsView. La creación de una clase DataControlField personalizada significaría que la columna de botones de radio se podría agregar simplemente mediante sintaxis declarativa, y también haría que la replicación de la funcionalidad en otras páginas web y otras aplicaciones web fuera significativamente más fácil.

Pero si alguna vez ha creado controles personalizados compilados en ASP.NET, sabe que hacerlo requiere bastante trabajo y conlleva una serie de sutilezas y casos perimetrales que se deben controlar cuidadosamente. Por tanto, se renunciará a implementar una columna de botones de radio como una clase personalizada DataControlField por ahora y seguirá con la opción TemplateField. Es posible que tenga la oportunidad de explorar la creación, el uso y la implementación de clases DataControlField personalizadas en un tutorial futuro.

Paso 4: Presentación de los productos del proveedor seleccionado en una página independiente

Una vez que el usuario ha seleccionado una fila del control GridView, necesita mostrarle los productos del proveedor seleccionado. En algunas circunstancias, es posible que quiera mostrar estos productos en una página independiente, en otras es posible que prefiera hacerlo en la misma página. Examinará primero cómo mostrar los productos en una página independiente; en el paso 5 verá cómo agregar un control GridView a RadioButtonField.aspx para mostrar los productos del proveedor seleccionado.

Actualmente hay dos controles web Button en la página: ListProducts y SendToProducts. Cuando se hace clic en el botón SendToProducts, querrá enviar al usuario a ~/Filtering/ProductsForSupplierDetails.aspx. Esta página se ha creado en el tutorial Filtrado de maestro y detalles en dos páginas y muestra los productos para el proveedor cuyo valor SupplierID se pasa desde el campo de cadena de consulta denominado SupplierID.

Para proporcionar esta funcionalidad, cree un controlador de eventos en el botón SendToProducts para el evento Click. En el paso 3 ha agregado la propiedad SuppliersSelectedIndex, que devuelve el índice de la fila cuyo botón de radio está seleccionado. El objeto SupplierID correspondiente se puede recuperar de la colección DataKeys del control GridView y el usuario se puede enviar a ~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=SupplierID mediante Response.Redirect("url").

protected void SendToProducts_Click(object sender, EventArgs e)
{
    // Send the user to ~/Filtering/ProductsForSupplierDetails.aspx
    int supplierID = 
        Convert.ToInt32(Suppliers.DataKeys[SuppliersSelectedIndex].Value);
    Response.Redirect(
        "~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=" 
        + supplierID);
    }
}

Este código funciona maravillosamente siempre que se seleccione uno de los botones de radio del control GridView. Si, inicialmente, el control GridView no tiene ningún botón de radio seleccionado y el usuario hace clic en el botón SendToProducts, SuppliersSelectedIndex será -1, lo que hará que se inicie una excepción, ya que -1 está fuera del intervalo de índices de la colección DataKeys. Pero esto no es un problema si decide actualizar el controlador de eventos RowCreated como se describe en el paso 3 para que el primer botón de radio del control GridView esté seleccionado inicialmente.

Para dar cabida a un valor SuppliersSelectedIndex de -1, agregue un control web Label a la página situada encima del control GridView. Establezca su propiedad ID en ChooseSupplierMsg, su propiedad CssClass en Warning, sus propiedades EnableViewState y Visible en false, y su propiedad Text en "Please choose a supplier from the grid" (Elija un proveedor de la cuadrícula). La clase CSS Warning muestra texto en una fuente grande, en color rojo, en cursiva y en negrita, y se define en Styles.css. Al establecer las propiedades EnableViewState y Visible en false, el control Label no se representa excepto para los postbacks en los que la propiedad Visible del control se establece mediante programación en true.

Add a Label Web Control Above the GridView

Figura 13: Adición de un control web Label encima del control GridView (Haga clic para ver la imagen a tamaño completo)

A continuación, aumente el controlador de eventos Click para mostrar la etiqueta ChooseSupplierMsg si SuppliersSelectedIndex es menor que cero, de lo contrario, redirija al usuario a ~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=SupplierID.

protected void SendToProducts_Click(object sender, EventArgs e)
{
    // make sure one of the radio buttons has been selected
    if (SuppliersSelectedIndex < 0)
        ChooseSupplierMsg.Visible = true;
    else
    {
        // Send the user to ~/Filtering/ProductsForSupplierDetails.aspx
        int supplierID = 
            Convert.ToInt32(Suppliers.DataKeys[SuppliersSelectedIndex].Value);
        Response.Redirect(
            "~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=" 
            + supplierID);
    }
}

Visite la página en un explorador y haga clic en el botón SendToProducts antes de seleccionar un proveedor en el control GridView. Como se muestra en la figura 14, esto muestra la etiqueta ChooseSupplierMsg. Después, seleccione un proveedor y haga clic en el botón SendToProducts. Esto le llevará a una página que enumera los productos suministrados por el proveedor seleccionado. En la figura 15 se muestra la página ProductsForSupplierDetails.aspx cuando se selecciona el proveedor Bigfoot Breweries.

The ChooseSupplierMsg Label is Displayed if No Supplier is Selected

Figura 14: Se muestra la etiqueta ChooseSupplierMsg si no hay ningún proveedor seleccionado (Haga clic para ver la imagen a tamaño completo)

The Selected Supplier s Products are Displayed in ProductsForSupplierDetails.aspx

Figura 15: Se muestran los productos del proveedor seleccionado en ProductsForSupplierDetails.aspx (Haga clic para ver la imagen a tamaño completo)

Paso 5: Presentación de los productos del proveedor seleccionado en la misma página

En el paso 4 ha visto cómo enviar al usuario a otra página web para mostrarle los productos del proveedor seleccionado. Como alternativa, los productos del proveedor seleccionado se pueden mostrar en la misma página. Para ilustrar esto, agregará otro control GridView a RadioButtonField.aspx para mostrar los productos del proveedor seleccionado.

Como solo quiere que este control GridView de productos se muestre una vez que se haya seleccionado un proveedor, agregue un control web Panel debajo del control GridView Suppliers, establezca su propiedad ID en ProductsBySupplierPanel y su propiedad Visible en false. En el control Panel, agregue el texto "Products for the Selected Supplier" (Productos del proveedor seleccionado) seguido de un control GridView denominado ProductsBySupplier. Desde la etiqueta inteligente del control GridView, elija enlazarla a un nuevo ObjectDataSource denominado ProductsBySupplierDataSource.

Bind the ProductsBySupplier GridView to a New ObjectDataSource

Figura 16: Enlace del control GridView ProductsBySupplier a un nuevo elemento ObjectDataSource (Haga clic para ver la imagen a tamaño completo)

Después, configure ObjectDataSource para usar la clase ProductsBLL. Como solo quiere recuperar los productos proporcionados por el proveedor seleccionado, especifique que ObjectDataSource debe invocar el método GetProductsBySupplierID(supplierID) para recuperar sus datos. Seleccione (Ninguno) en las listas desplegables de las pestañas UPDATE, INSERT y DELETE.

Configure the ObjectDataSource to Use the GetProductsBySupplierID(supplierID) Method

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

Set the Drop-Down Lists to (None) in the UPDATE, INSERT, and DELETE Tabs

Figura 18: Establecimiento de las listas desplegables de las pestañas UPDATE, INSERT y DELETE en (Ninguno) (Haga clic para ver la imagen a tamaño completo)

Después de configurar las pestañas SELECT, UPDATE, INSERT y DELETE, haga clic en Siguiente. Como el método GetProductsBySupplierID(supplierID) espera un parámetro de entrada, el asistente para configuración del origen de datos le pide que especifique el origen para el valor del parámetro.

Aquí hay un par de opciones para especificar el origen del valor del parámetro. Podría usar el objeto Parameter predeterminado y asignar mediante programación el valor de la propiedad SuppliersSelectedIndex a la propiedad DefaultValue de Parameter en el controlador de eventos Selecting de ObjectDataSource. Consulte el tutorial Configuración mediante programación de los valores de parámetro de ObjectDataSource para repasar la asignación de valores mediante programación a los parámetros de ObjectDataSource.

Como alternativa, puede usar una instancia de ControlParameter y hacer referencia a la propiedad SelectedValue del control GridView Suppliers (vea la figura 19). La propiedad SelectedValue del control GridView devuelve el valor DataKey correspondiente a la propiedad SelectedIndex. Para que esta opción funcione, es necesario establecer mediante programación la propiedad SelectedIndex del control GridView en la fila seleccionada cuando se hace clic en el botón ListProducts. Como ventaja agregada, al establecer la propiedad SelectedIndex, el registro seleccionado tomará el elemento SelectedRowStyle definido en el tema DataWebControls (un fondo amarillo).

Use a ControlParameter to Specify the GridView s SelectedValue as the Parameter Source

Figura 19: Uso de un elemento ControlParameter para especificar la propiedad SelectedValue del control GridView como Origen del parámetro (Haga clic para ver la imagen a tamaño completo)

Al completar el asistente, Visual Studio agregará automáticamente campos para los campos de datos de los productos. Quite todos los objetos BoundField excepto ProductName, CategoryName y UnitPrice, y cambie las propiedades HeaderText a Product, Category y Price. Configure la instancia UnitPrice de BoundField para que su valor tenga el formato de moneda. Después de realizar estos cambios, el marcado declarativo de los controles Panel, GridView y ObjectDataSource debe ser similar al siguiente:

<asp:Panel runat="server" ID="ProductsBySupplierPanel" Visible="False">
    <h3>
        Products for the Selected Supplier</h3>
    <p>
        <asp:GridView ID="ProductsBySupplier" runat="server" 
            AutoGenerateColumns="False" DataKeyNames="ProductID"
            DataSourceID="ProductsBySupplierDataSource" EnableViewState="False">
            <Columns>
                <asp:BoundField DataField="ProductName" HeaderText="Product" 
                    SortExpression="ProductName" />
                <asp:BoundField DataField="CategoryName" HeaderText="Category" 
                    ReadOnly="True" SortExpression="CategoryName" />
                <asp:BoundField DataField="UnitPrice" DataFormatString="{0:c}" 
                    HeaderText="Price" HtmlEncode="False" 
                    SortExpression="UnitPrice" />
            </Columns>
        </asp:GridView>
        <asp:ObjectDataSource ID="ProductsBySupplierDataSource" runat="server" 
            OldValuesParameterFormatString="original_{0}"
            SelectMethod="GetProductsBySupplierID" TypeName="ProductsBLL">
            <SelectParameters>
                <asp:ControlParameter ControlID="Suppliers" Name="supplierID" 
                    PropertyName="SelectedValue" Type="Int32" />
            </SelectParameters>
        </asp:ObjectDataSource>
    </p>
</asp:Panel>

Para completar este ejercicio, es necesario establecer la propiedad SelectedIndex del control GridView en SelectedSuppliersIndex, y la propiedad Visible del panel ProductsBySupplierPanel en true cuando se hace clic en el botón ListProducts. Para ello, cree un controlador de eventos para el evento Click del control web Button ListProducts y agregue el código siguiente:

protected void ListProducts_Click(object sender, EventArgs e)
{
    // make sure one of the radio buttons has been selected
    if (SuppliersSelectedIndex < 0)
    {
        ChooseSupplierMsg.Visible = true;
        ProductsBySupplierPanel.Visible = false;
    }
    else
    {
        // Set the GridView's SelectedIndex
        Suppliers.SelectedIndex = SuppliersSelectedIndex;
        // Show the ProductsBySupplierPanel panel
        ProductsBySupplierPanel.Visible = true;
    }
}

Si no se ha seleccionado un proveedor en el control GridView, se muestra la etiqueta ChooseSupplierMsg y el panel ProductsBySupplierPanel se oculta. De lo contrario, si se ha seleccionado un proveedor, se muestra el objeto ProductsBySupplierPanel y se actualiza la propiedad SelectedIndex del control GridView.

En la figura 20 se muestran los resultados después de seleccionar el proveedor Bigfoot Breweries y hacer clic en el botón para mostrar los productos en la página.

The Products Supplied by Bigfoot Breweries are Listed on the Same Page

Figura 20: Los productos suministrados por Bigfoot Breweries se muestran en la misma página (Haga clic para ver la imagen a tamaño completo)

Resumen

Como se describe en el tutorial Maestro y detalles mediante un control GridView maestro seleccionable con un control DetailView de detalles, se pueden seleccionar registros desde un control GridView mediante un elemento CommandField cuya propiedad ShowSelectButton esté establecida en true. Pero CommandField muestra sus botones como botones de inserción, vínculos o imágenes normales. Una interfaz de usuario alternativa de selección de filas sería proporcionar un botón de radio o una casilla en cada fila del control GridView. En este tutorial ha examinado cómo agregar una columna de botones de radio.

Desafortunadamente, agregar una columna de botones de radio no es tan fácil o rápido como podría esperar. No hay ningún objeto RadioButtonField integrado que se pueda agregar con pulsar un botón y el uso del control web RadioButton dentro de un objeto TemplateField presenta su propio conjunto de problemas. Al final, para proporcionar este tipo de interfaz, es necesario que crear una clase DataControlField personalizada o recurrir a insertar el código HTML adecuado en un objeto TemplateField durante el evento RowCreated.

Después de explorar cómo agregar una columna de botones de radio, se agregará una columna de casillas. Con una columna de casillas, un usuario puede seleccionar una o varias filas de un control GridView y realizar alguna operación en todas las filas seleccionadas (por ejemplo, seleccionar un conjunto de correos electrónicos en un cliente de correo electrónico basado en web y, después, eliminar todos los correos electrónicos seleccionados). En el siguiente tutorial verá cómo agregar ese tipo de columna.

¡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. El revisor principal de este tutorial ha sido David Suru. ¿Le interesaría revisar mis próximos artículos de MSDN? Si es así, escríbame a mitchell@4GuysFromRolla.com.