VB.NET

Añadiendo un TemplateField a un GridView de forma dinámica


La mayor parte de los sitios web que se sirven de listados lo hacen utilizando un esquema como el que sigue:

090501Grid
Como vemos, lo normal suele ser utilizar campos enlazados a la consulta (BoundFields), así como campos-plantilla (TemplateFields) con los que se suele interactuar con el listado (ver detalles, edición, etc.).

Si sabemos de antemano qué datos vamos a obtener, es sencillo “maquetar” nuestro GridView de forma sencilla. El problema radica en el momento en el que el número de columnas que nuestro GridView mostrará sea variable. En este caso, el atributo AutoGenerateColumns deberá ser puesto a True, con lo que perderemos el control del orden de los TemplateFields.

Veamos el siguiente caso: queremos un campo plantilla que muestre un LinkButton que nos permita editar el registro, y otro que nos permita visualizar los detalles del mismo. Queremos uno de estos campos en la primera columna (el de edición) y otro en la última (visualización). Si utilizamos TemplateFields con el atributo AutoGenerateColumns=”True”, lo que lograremos será que ambos botones se sitúen juntos en las dos primeras columnas de nuestro GridView. ¿Cómo hacer que uno de ellos se coloque al final? Añadiéndolo de forma dinámica.

Para comenzar, añadiremos un GridView con un TemplateField conteniendo un LinkButton, colocando el atributo AutogenerateColumns=”True”.

090502Grid
El templatefield contendría un LinkButton:

090503Grid

El resultado sería este:

090504Grid

Seguidamente, acudiremos a nuestro código, listos para editar el evento RowDataBound. En él haremos lo siguiente:

  • Declararemos una celda y un LinkButton.
  • Daremos formato al LinkButton (nombre, clase, etc.)
  • Asignaremos al LinkButton un CommandArgument igual al Identificador (DataKey) de esa fila del GridView
  • Añadiremos el LinkButton a la celda, y la celda a la fila

Para ello utilizaremos el siguiente código:


Protected Sub GridView1_RowDataBound(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.GridViewRowEventArgs) Handles GridView1.RowDataBound
Try

    ' Si se trata de una fila de datos...
    If e.Row.RowType = DataControlRowType.DataRow Then
        Dim celda As New TableCell()
        Dim boton As New LinkButton()

        boton.Text = "Detalles"
        If e.Row.Cells.Count > 1 Then
            boton.CommandArgument = GridView1.DataKeys(e.Row.RowIndex).Value.ToString()
        End If

        ' Añadimos el LinkButton a la celda, y la celda a la fila
        celda.Controls.Add(boton)
        e.Row.Controls.Add(celda)
    End If
Catch ex As Exception
    Throw ex
End Try
End Sub

Con esto lograremos el buscado esquema [Botón] [CAMPOS] [Botón].

Ejecutando programas externos a nuestra aplicación


En cierta ocasión almacené en DVD una serie de instaladores de los programas que utilizaba con más frecuencia. Me planteé que sería buena idea utilizar una aplicación de autoarranque con un menú navegable en el que se accediese directamente a la instalación del programa seleccionado. ¿Cómo ejecutar un programa ajeno a nuestra aplicación desde código? Creando un nuevo proceso y asignándole la información necesaria para “saber” qué tiene que ejecutar.
Como ejemplo simple, lanzaremos una consola en la que el directorio de trabajo sea C:\. En tres pasos tendremos nuestra aplicación funcionando:

  • Crearemos una instancia de la clase Process
  • Informaremos de los datos oportunos (ruta del ejecutable, parámetros, directorio de trabajo…)
  • Lanzaremos el proceso.

' Declaramos un objeto que identificará al nuevo proceso
 Dim proceso As New Process()

 ' Indicamos la ruta del ejecutable que queremos lanzar
 proceso.StartInfo.FileName = "cmd.exe"

 ' Indicamos el directorio de trabajo
 proceso.StartInfo.WorkingDirectory = "C:\"

 ' En caso de ser necesario, podemos indicarle argumentos.
 proceso.StartInfo.Arguments = "/T:0A"

 ' Indicamos el estilo de la ventana
 proceso.StartInfo.WindowStyle = ProcessWindowStyle.Normal

 ' Por último, lanzamos el proceso
 proceso.Start()

Como vemos, hemos obtenido lanzar la orden cmd.exe /T:0A (el argumento /T cambia los colores de la consola) en el directorio de trabajo C:\.

ejemploCmd

Como podremos imaginar, así podremos ejecutar cualquier programa a cuya ruta tengamos acceso.

Rellenando un DataSet con una invocación a procedimiento almacenado


Cuando queremos ejecutar un procedimiento almacenado, por norma general necesitaremos dos parámetros: una cadena con el nombre del procedimiento, y una lista de parámetros que contendrá los filtros por los cuales el procedimiento almacenado limitará su búsqueda. Tomando como ejemplo una base de datos Sql Server, pensemos en el siguiente procedimiento almacenado:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	Daniel García
-- Create date: 11/05/2009
-- Description:	Autentica un usuario
-- =============================================
CREATE PROCEDURE SP_AutenticarUsuario

@NombreUsuario	VARCHAR(20)	= NULL,
@Password		INT	= NULL

AS
BEGIN
SET NOCOUNT ON;

SELECT IdUsuario, Nombre, Apellido1, Apellido2 FROM USUARIO WHERE NombreUsuario = @NombreUsuario AND Password = @Password
END
GO

El código necesario para ejecutar un procedimiento almacenado sería el siguiente:


' Declaramos una conexión, pasándole como parámetro la cadena de conexión a la Base de Datos
Dim conexion As New SqlClient.SqlConnection("Data Source=SRVSQL2005;Initial Catalog=DatosCliente;User Id=DBUser;Password=DBPassword;")

' Declaramos un SqlCommand a partir del nombre del procedimiento y la conexión que hemos creado.
Dim command As New SqlClient.SqlCommand("SP_AutenticarUsuario", conexion)

' Indicamos que vamos a ejecutar un procedimiento almacenado
command.CommandType = CommandType.StoredProcedure

' Rellenamos los parámetros de entrada del procedimiento.
' NOTA: Se trata de un EJEMPLO. No hagáis esto en casa, niños, seguridad ante todo... =)
Dim nombreUsuario As String = "admin"
Dim password As String = "123456"

' Añadimos los parámetros al SqlCommand
command.Parameters.Add(New SqlClient.SqlParameter("@NombreUsuario", nombreUsuario))
command.Parameters.Add(New SqlClient.SqlParameter("@Password", password))

' Declaramos un DataAdapter a partir del SqlCommand y un DataSet para almacenar los datos.
Dim da As New SqlClient.SqlDataAdapter(command)
Dim ds As New DataSet

' Por último, rellenamos el DataSet
da.Fill(ds)

Con esto rellenaríamos el DataSet con una tabla que contendría los campos IdUsuario, Nombre, Apellido1 y Apellido2, tal y como mostraba el procedimiento.
Este proceso es sencillo, pero tiene un problema: aumenta de forma dramática el acoplamiento, al codificar la lógica de acceso a datos de forma monolítica. Si posteriormente necesitáramos añadir o eliminar algún campo de la consulta, deberíamos acceder a este código y modificarlo. Más adelante veremos cómo abstraer un poco la entidad a la cual se referencia y el acceso a datos, utilizando el llamado patrón DAO.

Rellenando un DataSet con una invocación a procedimiento almacenado


Cuando queremos ejecutar un procedimiento almacenado, por norma general necesitaremos dos parámetros: una cadena con el nombre del procedimiento, y una lista de parámetros que contendrá los filtros por los cuales el procedimiento almacenado limitará su búsqueda. Tomando como ejemplo una base de datos Sql Server, pensemos en el siguiente procedimiento almacenado:


SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author:	Daniel García
-- Create date: 11/05/2009
-- Description:	Autentica un usuario
-- =============================================
CREATE PROCEDURE SP_AutenticarUsuario

@NombreUsuario	VARCHAR(20)	= NULL,
@Password		INT	= NULL

AS
BEGIN
SET NOCOUNT ON;

SELECT IdUsuario, Nombre, Apellido1, Apellido2 FROM USUARIO WHERE NombreUsuario = @NombreUsuario AND Password = @Password
END
GO

El código necesario para ejecutar un procedimiento almacenado sería el siguiente:


' Declaramos una conexión, pasándole como parámetro la cadena de conexión a la Base de Datos
Dim conexion As New SqlClient.SqlConnection("Data Source=SRVSQL2005;Initial Catalog=DatosCliente;User Id=DBUser;Password=DBPassword;")

' Declaramos un SqlCommand a partir del nombre del procedimiento y la conexión que hemos creado.
Dim command As New SqlClient.SqlCommand("SP_AutenticarUsuario", conexion)

' Indicamos que vamos a ejecutar un procedimiento almacenado
command.CommandType = CommandType.StoredProcedure

' Rellenamos los parámetros de entrada del procedimiento.
' NOTA: Se trata de un EJEMPLO. No hagáis esto en casa, niños, seguridad ante todo... =)
Dim nombreUsuario As String = "admin"
Dim password As String = "123456"

' Añadimos los parámetros al SqlCommand
command.Parameters.Add(New SqlClient.SqlParameter("@NombreUsuario", nombreUsuario))
command.Parameters.Add(New SqlClient.SqlParameter("@Password", password))

' Declaramos un DataAdapter a partir del SqlCommand y un DataSet para almacenar los datos.
Dim da As New SqlClient.SqlDataAdapter(command)
Dim ds As New DataSet

' Por último, rellenamos el DataSet
da.Fill(ds)

Con esto rellenaríamos el DataSet con una tabla que contendría los campos IdUsuario, Nombre, Apellido1 y Apellido2, tal y como mostraba el procedimiento.
Este proceso es sencillo, pero tiene un problema: aumenta de forma dramática el acoplamiento, al codificar la lógica de acceso a datos de forma monolítica. Si posteriormente necesitáramos añadir o eliminar algún campo de la consulta, deberíamos acceder a este código y modificarlo. Más adelante veremos cómo abstraer un poco la entidad a la cual se referencia y el acceso a datos, utilizando el llamado patrón DAO.

Control Repeater


El control Repeater permite, como su propio nombre indica, repetir una plantilla compuesta por diversos controles un número de veces determinado, de acuerdo a una estructura de datos que pueda proveer los datos necesarios para poder rellenar esos controles. A continuación veremos un ejemplo en ASP.NET y VB.NET

Creación del control

Lo primero que debemos hacer es acceder al cuadro de herramientas y seleccionar el Repeater dentro de los controles de datos.

Control Repeater

Control Repeater

Una vez hayamos creado el control en nuestro aspx, deberemos introducir dentro del mismo aquellos controles que queramos repetir. Nuestro Repeater tendrá, en principio, el siguiente aspecto:

<asp :Repeater ID="Repeater1" runat="server">
</asp>

En el interior del mismo podremos introducir lo que queramos: controles HTML, tablas, controles ASPX… teniendo en cuenta que todo lo que introduzcamos se repetirá un número de veces igual al número de elementos del DataSource que le asignemos al Repeater.

Es aconsejable crear la sección a repetir antes de añadirla al Repeater. Si por ejemplo queremos insertar una tabla con un GridView y un Label, cuyo código sería el siguiente:

<table cellpadding="2" cellspacing="1" bgcolor="#f6f7f9">
  <tr>
    <td>
      <asp :GridView ID="DatosGridView" runat="server" AutoGenerateColumns="False"
              BackColor="White" BorderColor="#F6F7F9" BorderStyle="Solid"
              BorderWidth="1px" CellPadding="1"
              DataKeyNames="Id">
        <columns>
          <asp :BoundField DataField="Id" HeaderText="Id" SortExpression="Id" />
          <asp :BoundField DataField="Descripcion" HeaderText=" Descripcion " SortExpression=" Descripcion" />
        </columns>
      </asp>
      <asp :Label ID="NoResultadosLabel" runat="server" Text="Label"></asp>
    </td>
  </tr>
</table>

Lo que obtendríamos en nuestra página sería lo siguiente:

Ejemplo de Repeater

Ejemplo de Repeater

Una vez creado el componente a repetir, cortamos su código asociado y lo introducimos dentro del Repeater, del siguiente modo:

<asp :Repeater ID="Repeater1" runat="server">
  <itemtemplate>
    <table cellpadding="2" cellspacing="1" bgcolor="#f6f7f9">
      <tr>
        <td>
          <asp :GridView ID="DatosGridView" runat="server" AutoGenerateColumns="False"
                  BackColor="White" BorderColor="#F6F7F9" BorderStyle="Solid"
                  BorderWidth="1px" CellPadding="1"
                  DataKeyNames="Id">
            <columns>
              <asp :BoundField DataField="Id" HeaderText="Id" SortExpression="Id" />
              <asp :BoundField DataField="Descripcion" HeaderText="Descripcion" SortExpression=" Descripcion" />
            </columns>
</asp>
          <asp :Label ID="EtiquetaLabel" runat="server" Text="Label"></asp>
        </td>
      </tr>
    </table>
  </itemtemplate>
</asp>

Ya tenemos una tabla con un GridView y un Label dentro del Repeater. Ahora sólo tendremos que asociarle datos para que funcione.


Enlazado de datos

El Repeater tiene un evento llamado ItemDataBound, el cual se lanza al llamar al método DataBind() del Repeater, del siguiente modo:

Dim lista As New ArrayList
lista.Add(1)
lista.Add(2)
lista.Add(3)
lista.Add(4)

Repeater1.DataSource = lista
Repeater1.DataBind()

La llamada al DataBind() lanzará un evento ItemDataBound por cada uno de los elementos de su DataSource. El DataSource puede ser un ArrayList, un DataTable, un DataSet, etc. En nuestro caso, el evento se lanzará cuatro veces, uno por cada valor del ArrayList.

Además, el contenido del elemento del DataSource será accesible desde el evento ItemDataBound a través de la propiedad e.Item.

Protected Sub RepeaterRecibo_ItemDataBound(ByVal sender As Object, _
             ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) _
             Handles RepeaterRecibo.ItemDataBound

Dentro de este evento será donde realizaremos la asignación de los datos del interior del Repeater, es decir, la asignación de la propiedad Text del Label y el DataBind del GridView. Todo ello dependerá (si así lo deseamos, y es donde radica la potencia de este control) del elemento que en ese momento estemos manejando.

Si quisiéramos hacer dicho enlace, utilizaríamos el método FindControl() para instanciar el objeto en función de la iteración:


Dim EtiquetaLabel As Label
Dim DatosGridView As GridView
Dim dt As DataTable
Dim id As Integer

Select Case e.Item.ItemType
  Case ListItemType.Item, ListItemType.AlternatingItem
    id = CInt(CType(Repeater1.DataSource, ArrayList).Item(e.Item.ItemIndex))

    ' Se rellena el DataTable que obtenemos a partir del elemento del ArrayList
    dt = ConsultarPorId(id)

    ' Se realiza el enlace de los datos, tanto del GridView como del Label.
    EtiquetaLabel = CType(e.Item.FindControl("EtiquetaLabel"), Label)

    DatosGridView = CType(e.Item.FindControl("DatosGridView"), GridView)
    EtiquetaLabel.Text = dt.Rows(0).Item("Nombre").ToString
    DatosGridView.DataSource = dt
    DatosGridView.DataBind()

  End Select

Esto hará que se cargue un GridView y un Label para cada ID (en este caso se trata de IDs, podemos almacenar cualquier tipo de dato en el DataSource), repitiendo el código DataSource.Item.Count veces.