Agregar una columna GridView de botones de radio (VB)

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 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 deberán agregar características adicionales no admitidas. En este tutorial y en los dos siguientes examinaremos 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. Tal y como se explica en el tutorial Maestro y detalles mediante un GridView maestro seleccionable con un DetailView de detalles, podemos agregar un campo CommandField a GridView que incluye un botón Select. 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 Select se ha hecho clic. En el tutorial Maestro y detalles mediante un GridView maestro seleccionable con un DetailView de detalles, vimos cómo usar esta característica para mostrar los detalles de la fila de GridView seleccionada.

Aunque el botón Select 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. Podemos aumentar GridView para que, en lugar de un botón Select, 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 para 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 Select 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: Mejora de las páginas web con GridView

Antes de empezar a mejorar GridView para incluir una columna de botones de radio, primero dediquemos un momento a crear las páginas ASP.NET en nuestro proyecto de sitio web que necesitaremos para este tutorial y los dos siguientes. Empiece agregando una nueva carpeta denominada EnhancedGridView. Después, agregue las siguientes páginas ASP.NET a esa carpeta, asegurándose 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 lo tanto, agregue este control de usuario a Default.aspx arrastrándolo 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 las siguientes cuatro páginas como entradas al archivo Web.sitemap. En concreto, agregue el marcado siguiente después del <siteMapNode> de uso del control 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 tutoriales desde un explorador. El menú de la izquierda ahora incluye elementos para la edición, inserción y eliminación de tutoriales.

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, vamos a 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 haciendo 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 vamos a 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 el ID del GridView en Suppliers y, desde su etiqueta inteligente, elija crear un nuevo 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 un nuevo elemento ObjectDataSource denominado SuppliersDataSource (haga clic para ver la imagen a tamaño completo)

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

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

Puesto que solo queremos 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 on the SELECT tab with the method dropdown menu 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 (Ninguno) y haga clic en Siguiente.

Screenshot of the Configure Data Source - SuppliersDataSource window on the UPDATE tab with the method dropdown menu 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)

Dado que el método GetSuppliersByCountry(country) acepta un parámetro, el asistente Configuración del origen de datos nos 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 todos los objetos BoundField excepto CompanyName, City y Country, y cambie el nombre de la propiedad HeaderText de BoundField de 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, vamos a permitir que el usuario vea los productos del proveedor seleccionado en la misma página que la lista de proveedores o en otra página. Para ello, agregue dos controles web de botón a la página. He establecido los 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, y que cuando se haga clic en SendToProducts, se redirija al usuario a otra página donde enumeran los productos.

En la figura 9 se muestran el control GridView Suppliers y los dos controles web de botón cuando se ven desde 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 objetos BoundField que muestran el nombre de empresa, la ciudad y el país de cada proveedor de Estados Unidos. Sin embargo, todavía falta una columna de botones de radio. Desafortunadamente, GridView no incluye un objeto RadioButtonField integrado; de lo contrario, podríamos agregarlo a la cuadrícula y listo. En su lugar, podemos 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 del GridView.

Inicialmente, podríamos suponer que la interfaz de usuario deseada se puede implementar agregando 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 lo 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 necesitamos, vamos a implementar este enfoque, ya que vale la pena examinar por qué no se agrupan los botones de radio resultantes. Empiece agregando un objeto TemplateField al control GridView denominado 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 del objeto 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 de ID. Los valores adicionales de ID agregados delante de los atributos id y name representados son los ID de los controles primarios de los botones de radio, el ID de GridViewRow, el ID de GridView, el IDdel control de contenido y el ID de Web Forms. Estos ID se agregan para que cada control web representado en el control GridView tenga un valor id y name único.

Cada control representado necesita un valor name y id diferente, ya que así es como el explorador identifica de forma única cada control en el lado cliente y como indica al servidor web qué acción o cambio se ha producido en el postback. Por ejemplo, imagine que queremos ejecutar código del lado servidor cada vez que se cambiaba el estado de selección de un objeto RadioButton. Podríamos hacerlo estableciendo la propiedad AutoPostBack de RadioButton en True y creando un controlador de eventos para el evento CheckChanged. Sin embargo, si los valores name y id representados para todos los botones de radio fueran los mismos, con un postback no podríamos determinar en qué objeto RadioButton específico se ha hecho clic.

En resumen, no podemos crear una columna de botones de radio en un control GridView mediante el control web RadioButton. En su lugar, debemos 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 hará que los botones de radio de la cuadrícula no se agrupen. Si no está familiarizado 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 está interesado en aprender más, consulte 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 queramos 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 lo tanto, cada botón de radio necesitará un atributo value único. Por último, en el postback debemos asegurarnos 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 explicó por primera vez en el tutorial Uso de objetos TemplateField en el control GridView. En nuestro 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 lo tanto, si el 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 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 profundizaré más. Sin embargo, 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 diferente a insertar marcado personalizado y de bajo nivel en una plantilla, es decir, el enfoque que usaremos para este tutorial, es agregar 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 del elemento ItemTemplate de TemplateField y reemplácelo por un control Literal. Establezca el 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)

Después, 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 lo usamos 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 queremos continuar si estamos tratando con una fila de datos. Para cada fila de datos queremos 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 del GridView), y cuyo atributo value se establece en el índice de la fila del GridView.

Protected Sub Suppliers_RowCreated(sender As Object, e As GridViewRowEventArgs) _
    Handles Suppliers.RowCreated
    
    If e.Row.RowType = DataControlRowType.DataRow Then
        ' Grab a reference to the Literal control
        Dim output As Literal = _
            CType(e.Row.FindControl("RadioButtonMarkup"), Literal)
        ' 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)
    End If
End Sub

Cuando se selecciona una fila del GridView y se produce un postback, nos interesa el valor SupplierID del proveedor seleccionado. Por lo 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 GridView). Aunque esto puede funcionar en determinadas circunstancias, sería un riesgo de seguridad aceptar y procesar ciegamente un valor SupplierID. Nuestro GridView, por ejemplo, enumera solo los proveedores de Estados Unidos. Sin embargo, 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, podemos asegurarnos de que el usuario solo usa uno de los valores SupplierID asociados a una de las filas del 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. Sin embargo, 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 usando 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 los envía el explorador web cada vez que se produce un postback. Dado que 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").

Dado que tendremos 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, vamos a 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 ReadOnly Property SuppliersSelectedIndex() As Integer
    Get
        If String.IsNullOrEmpty(Request.Form("SuppliersGroup")) Then
            Return -1
        Else
            Return Convert.ToInt32(Request.Form("SuppliersGroup"))
        End If
    End Get
End Property

Con esta propiedad agregada, sabemos agregar 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 Sub Suppliers_RowCreated(sender As Object, e As GridViewRowEventArgs) _
    Handles Suppliers.RowCreated
    
    If e.Row.RowType = DataControlRowType.DataRow Then
        ' Grab a reference to the Literal control
        Dim output As Literal = _
            CType(e.Row.FindControl("RadioButtonMarkup"), Literal)
        ' 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 Then
            output.Text &= " checked="checked""
        End If
        ' Add the closing tag
        output.Text &= " />"
    End If
End Sub

Con este cambio, el botón de radio seleccionado permanece seleccionado después de un postback. Ahora que tenemos la capacidad de especificar qué botón de radio está seleccionado, podríamos 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 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 Then a lo siguiente: If SuppliersSelectedIndex = e.Row.RowIndex OrElse (Not Page.IsPostBack AndAlso e.Row.RowIndex = 0) Then.

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

Nota:

En lugar de usar TemplateField (el foco de este largo paso 3), podríamos 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 personalizada DataControlField significaría que la columna de botones de radio podría agregarse 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.

Sin embargo, 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 deben controlarse cuidadosamente. Por lo tanto, vamos a renunciar a implementar una columna de botones de radio como una clase personalizada DataControlField por ahora y seguiremos con la opción TemplateField. Quizás tengamos la oportunidad de explorar la creación, el uso y la implementación de clases personalizadas DataControlField en un tutorial futuro.

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

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

Actualmente hay dos controles web de botón en la página: ListProducts y SendToProducts. Cuando se hace clic en el botón SendToProducts, queremos enviar al usuario a ~/Filtering/ProductsForSupplierDetails.aspx. Esta página se creó en el tutorial Filtrado de maestro y detalles en dos páginas y muestra los productos para el proveedor cuyo valor SupplierID se pasa a través del 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 hemos 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 GridView el usuario se puede enviar a ~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=SupplierID mediante Response.Redirect("url").

Protected Sub SendToProducts_Click(sender As Object, e As EventArgs) _
    Handles SendToProducts.Click
    
    ' Send the user to ~/Filtering/ProductsForSupplierDetails.aspx
    Dim supplierID As Integer = _
        Convert.ToInt32(Suppliers.DataKeys(SuppliersSelectedIndex).Value)
    Response.Redirect( _
        "~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=" & _
        supplierID)
End Sub

Este código funciona maravillosamente siempre que se seleccione uno de los botones de radio del GridView. Si, inicialmente, el 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 produzca una excepción, ya que -1 está fuera del intervalo de índices de la colección DataKeys. Sin embargo, 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 GridView esté seleccionado inicialmente.

Para dar cabida a un valor SuppliersSelectedIndex de -1, agregue un control web Label a la página encima del 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 está definida 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 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 Sub SendToProducts_Click(sender As Object, e As EventArgs) _
    Handles SendToProducts.Click
    
    ' make sure one of the radio buttons has been selected
    If SuppliersSelectedIndex < 0 Then
        ChooseSupplierMsg.Visible = True
    Else
        ' Send the user to ~/Filtering/ProductsForSupplierDetails.aspx
        Dim supplierID As Integer = _
            Convert.ToInt32(Suppliers.DataKeys(SuppliersSelectedIndex).Value)
        Response.Redirect( _
            "~/Filtering/ProductsForSupplierDetails.aspx?SupplierID=" & _
            supplierID)
    End If
End Sub

Visite la página en un explorador y haga clic en el botón SendToProducts antes de seleccionar un proveedor en 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: Visualización de los productos del proveedor seleccionado en la misma página

En el paso 4 hemos 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, agregaremos otro control GridView a RadioButtonField.aspx para mostrar los productos del proveedor seleccionado.

Puesto que solo queremos que este control GridView de productos se muestre una vez que se haya seleccionado un proveedor, agregue un control web Panel debajo del 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. En la etiqueta inteligente del 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. Puesto que solo queremos 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. Dado que el método GetProductsBySupplierID(supplierID) espera un parámetro de entrada, el asistente Configuración del origen de datos nos pide que especifiquemos el origen para el valor del parámetro.

Aquí tenemos un par de opciones para especificar el origen del valor del parámetro. Podríamos 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, podemos usar un ControlParameter y hacer referencia a la propiedad SelectedValue del GridView Suppliers (vea la figura 19). La propiedad SelectedValue del 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 GridView en la fila seleccionada cuando se hace clic en el botón ListProducts. Como ventaja agregada, estableciendo 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 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 el objeto 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 Sub ListProducts_Click(sender As Object, e As EventArgs) _
    Handles ListProducts.Click
    
    ' make sure one of the radio buttons has been selected
    If SuppliersSelectedIndex < 0 Then
        ChooseSupplierMsg.Visible = True
        ProductsBySupplierPanel.Visible = False
    Else
        ' Set the GridView's SelectedIndex
        Suppliers.SelectedIndex = SuppliersSelectedIndex
        ' Show the ProductsBySupplierPanel panel
        ProductsBySupplierPanel.Visible = True
    End If
End Sub

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 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 Maestro y detalles mediante un GridView maestro seleccionable con un 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 es proporcionar un botón de radio o una casilla en cada fila del GridView. En este tutorial hemos 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, tenemos que crear una clase personalizada DataControlField o recurrir a insertar el HTML adecuado en un objeto TemplateField durante el evento RowCreated.

Después de explorar cómo agregar una columna de botones de radio, pasemos a 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 veremos cómo agregar dicha columna.

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