Cómo acceder a controles dentro de un GridView

martes 3 de junio de 2008

Esta entrada salió como resultado de una pregunta que hicieron en el Foro de la Comunidad .NET.  La pregunta, esencialmente, es: ¿Cómo accedo a un control que tengo en una columna templeteada dentro de un GridView de ASP.NET?

Me pareció buena la pregunta, así que hice un pequeño ejemplo para ilustrarlo.   Una columna templeteada (TemplateField) es distinta a una normal (BoundField) en que tienes más control sobre los controles que aparecen en la columna.  En otras palabras, tú especificas qué quieres que se muestre cuando el renglón está en modo "normal" (ItemTemplate) o cuando está en modo "edición" (EditItemTemplate).  Puedes tener múltiples controles dentro de la columna, en lugar de solo uno que represente el dato.

La siguiente página contiene un GridView con tres columnas que vienen de un SqlDataSource.  Los datos provienen de la clásica base de datos Northwind.   En este caso estoy recuperando datos de la tabla de productos:

<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb"
  Inherits="_Default" %>
 
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
  <title>Accediendo a Elementos en un GridView</title>
  <style type="text/css">
    #miGrid
    {
      width: 450px;
      float: left;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <asp:Button runat="server" ID="leerElementosButton" Text="Leer elementos" />
  <div id="miGrid">
    <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False"
      DataKeyNames="ProductID" DataSourceID="SqlDataSource1" 
      EmptyDataText="No hay registros que mostrar.">
      <Columns>
        <asp:BoundField DataField="ProductID" HeaderText="ID Producto" 
          InsertVisible="False" ReadOnly="True" />
        <asp:BoundField DataField="ProductName" HeaderText="Nombre Producto" />
        <asp:TemplateField HeaderText="Descontinuado">
          <EditItemTemplate>
            <asp:CheckBox ID="CheckBox1" runat="server" 
              Checked='<%# Bind("Discontinued") %>' />
          </EditItemTemplate>
          <ItemTemplate>
            <asp:CheckBox ID="CheckBox1" runat="server" 
              Checked='<%# Bind("Discontinued") %>' Enabled="false" />
          </ItemTemplate>
        </asp:TemplateField>
      </Columns>
    </asp:GridView>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:NorthwindConnectionString %>"
      ProviderName="<%$ ConnectionStrings:NorthwindConnectionString.ProviderName %>"
      SelectCommand="SELECT [ProductID], [ProductName], [Discontinued] FROM [Products]">
    </asp:SqlDataSource>
  </div>
  <div id="resultados">
    <asp:Label runat="server" ID="resultadosLabel" />
  </div>
  </form>
</body>
</html>

Para acceder, por ejemplo, al CheckBox que está dentro de la columna templeteada, primero necesitas una referencia al renglón, ya que el ID del control se repetirá n veces (una por cada renglón del Grid). 

Esto se puede hacer de varias formas, por ejemplo, si tienes un manejador para un evento clic en alguno de los controles dentro del renglón pues en ese caso el sender viene siendo el renglón en sí, entonces únicamente te falta encontrar el CheckBox de ese renglón para leer sus propiedades.   Otra forma de hacerlo, como en el siguiente ejemplo, es leer todos los renglones y por cada uno encontrar el CheckBox que le corresponde al renglón:

Partial Class _Default
  Inherits System.Web.UI.Page
 
  Protected Sub leerElementosButton_Click(ByVal sender As Object, _
    ByVal e As System.EventArgs) Handles leerElementosButton.Click
 
    Dim grid As GridView = Me.GridView1
    Dim checkBox As CheckBox
    Dim resultado As New StringBuilder
 
    For Each renglon As GridViewRow In grid.Rows
      checkBox = CType(renglon.FindControl("CheckBox1"), CheckBox)
      If checkBox.Checked Then  'Hacer algo con esta información
        resultado.Append(String.Format( _
          "El renglón {0} está descontinuado <br />", renglon.DataItemIndex))
      End If
    Next
 
    resultadosLabel.Text = resultado.ToString()
 
  End Sub
End Class

En resúmen, la idea clave aquí es que el GridView tiene una colección de renglones (de tipo GridViewRow) y cada uno de estos renglones tiene su correspondiente colección de controles que están dentro de ese renglón.  El GridViewRow también te da acceso al DataItem que contiene los datos asociados al renglón, en caso de que lo requirieras.

El resultado de correr el código anterior (dando clic al botón) es el siguiente:

Espero les ayude.

Enjoy smile_shades

categorías: , , ,

17 comentarios:

  1. Anónimo dijo:

    Muy bien... pero como puedo manejar los eventos de los controles dentro de un item template de un gridview?

  2. Carlos Rubalcava dijo:

    Sencillo, agregas un "handler" a tu control, como cualquier otro.

    Por ejemplo, podrías hacerlo en el Page_Load:

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _
    Handles Me.Load

    For Each renglon As GridViewRow In GridView1.Rows
    Dim checkBox As CheckBox = CType(renglon.FindControl("CheckBox1"), CheckBox)
    If Not checkBox Is Nothing Then
    AddHandler checkBox.CheckedChanged, AddressOf CheckBox1_CheckedChanged
    End If
    Next

    End Sub


    Esto asume, claro, que tienes un manejador como este:


    Protected Sub CheckBox1_CheckedChanged(ByVal sender As Object, _
    ByVal e As System.EventArgs)

    Dim checkBox As CheckBox = sender

    resultadosLabel.Text = _
    String.Format("Diste clic al checkbox en el renglon {0}. El checkbox esta palomeado: {1}.", _
    CType(checkBox.Parent.Parent, GridViewRow).RowIndex + 1, checkBox.Checked)

    End Sub

  3. Miguel dijo:

    Buen articulo, muy util para quienes utilizamos al maximo la potencialidad de una grilla...

  4. salvador dijo:

    Hola que tal carlos probe eso de manejar los eventos de los controles en un gridview y el ejemplo que pones del handles no me funciona me marca un error cuando hago AddHandler checkBox.CheckedChanged, AddressOf CheckBox1_CheckedChanged no me ponel el CheckedChanged y si lo dejo asi me dice que no es un evento del checkbox, el detalle es que quiero que cuando se chequea un checkbox actualice mi base de datos, el detalle es que al hacer esto me imagino que no se seleciona ninguna row entonces no puedo obtener el valor del datakey que es mi indice con el cual realizo el update , espero que me puedas echar la mano por que ya llevo 2 dias y no puedo hacerlo jajajajaja (me rio de mi mismo). de antemano muchas gracias . a y que interesante tu blog la neta, espero aprender mucho de ustedes ya que no soy muy bueno en esto de asp.net

  5. Sergio dijo:

    Hola, muy bueno el articulo, estoy tratando de utilizarlo para un problema que tengo en el trabajo, voy creando dinamicamente imageButtons, que estan enlazadas a labels, y quiero que cuando pinche ese imagebutton me vuelva a eliminar el label creado y el propio imagebutton, lo de eliminarlos es facil, pero no me va al evento cuando hago click, no se porque puede ser...¿alguna idea?

  6. Carlos Rubalcava dijo:

    Sergio--
    Dependiendo de cómo esté configurada tu página (¿AutoEventWireup="true" o "false"?) a veces se batalla un poco para que agregue correctamente los manejadores de eventos. Me he topado con casos que tienes que hacerlo en el evento Init o Page_Load para que los agregue correctamente después de cada postback.

  7. Anónimo dijo:

    Genial, así da gusto!!
    Muchas gracias!!

  8. Sergio dijo:

    Si, finalmente lo he hecho en el page load, mediante una coleccion de controles que esta declarada como privada voy metiendo dinamicamente todos los imagebuttons y los labels, dandole el mismo número de id, es decir si elimino mi imgbtn0 se que tengo que eliminar el lbl0, asi es mucho mas facil, se que no se van a crear muchos y son faciles de controlar, ya que doy el mismo evento click a todos los imagebutton.Gracias por la ayuda, un saludo.

  9. Anónimo dijo:

    Disculpa Carlos, tengo problemas con un grid que tiene una columna de checkbox, quiero que al presionar un boton identifique los checkbox que estan seleccionados, pero siempre me regresa false.
    De antemano, gracias por tu ayuda

  10. Sergio dijo:

    Hola, seguramente sera porque no tienes el ViewState de algun elemento activado, comprueba que el viewstate del grid este a true, y si tiene algun contenedor mas ese checkbox tambien. ¿Como recorres el grid?

  11. Anónimo dijo:

    Genial, mucas gracias Carlos.

  12. MichWDC dijo:

    Muxas gracias
    Casi Me Mato para poder
    Hacer esoo...
    T Agradezcoo.

  13. Elizabeth dijo:

    Hola que tal, y como seria para c#? agregr un hadler, lo que quiero hacer es k cada vez k cambie de radiobutton dentro del grid al lado del radiobutton me aparezca un dropdownlist, para seleccionar un elemento, pero no hayo como manejar el evento checkedchanged del radiobutton

  14. Carlos Rubalcava dijo:

    Elizabeth:
    Agregar un handler en C# es casi igual (yo diría que más sencillo).

    AddHandler es la sintaxis de VB.

    Para C# sería algo así como:

    checkBox.CheckedChanged += new EventHandler(CheckBox1_CheckedChanged)

    Visual Studio te ayuda a completar la sintaxis despues de teclear el +=. Normalmente esto te sugiere el resto de la oración, así que solo tienes que dar TAB para que lo complete, tecleas el nombre del método que manejará (o aceptas el default) y das otro TAB para que cree el esqueleto del método que maneja el evento.

  15. Elizabeth dijo:

    Muchas gracias Carlos por tu pronta respuesta, si lo hice así pero nunca entra al evento, tengo AutoEventWireup="true" porque si lo pongo a false, al acceder a la pagina no me carga un dropdownlist y ya que lo pongo a true, si me muestra el ddl con información, pero volviendo al punto anterior, puse el handler pero no entra al evento, por lo k no hace nada. Sabrás que me hara falta?
    Gracias.

  16. Carlos Rubalcava dijo:

    Elizabeth:
    No creo que funcione con AutoEventWireup="true". Debes entender que si usas esto, ASP.NET generará los "wireups" [en este caso las oraciones con control.evento += new EventHandler(manejador)] automáticamente, basado en el nombre del control y del evento. Esto ahorra un poco de chamba como desarrollador, pero te forza a usar la convención correcta de nombramiento. Sin embargo como los controles dentro de tu Grid se van a repetir "n" veces esto impediría que lo pueda hacer automáticamente.

    Nota que en mi ejemplo, AutoEventWireup está explícitamente puesto en "false".

    Es posible que te funcione si mueves el código de "wireup" a algún evento como el PreRender o alguno de los anteriores en el ciclo de vida de la página antes del Load, pero no creo (la explicación del porqué estaría un poco larga).

    O quizá sea más fácil simplemente agregar también el "wireup" explícito para el DropDown que no funciona con AutoEventWireup="false".

    Todo depende de la situación. Tendrás que jugar un poco.

  17. Elizabeth dijo:

    muchas gracias carlos, y pues entonces veré la forma de hacerlo. Si es un poco complicado pero ahorita le hechamos un vistazo mas..
    Saludos y buen día.