jueves, 23 de junio de 2011

[GridView] Eventos de controles contenidos en el GridView (1/2)

 

Introducción

En ciertas ocasiones puede ser necesario trabajar directamente con los eventos de los controles contenidos en el propio GridView, pero el problema planteado es como trabajar con el control que ejecuta la acción y al mismo tiempo poder acceder a la información de la fila del gridview que contiene a dicho control.

El articulo demuestra como conseguirlo de dos formas distintas:

- una implicara extender el control que se quiera utilizar

- la otra simplemente accederá al objeto que contiene al control que lanza el evento

En los ejemplos se hará uso de un control radiobutton como parte de gridview y será este quien ejecute el evento con el cual se necesitara trabajar.

 

Extendiendo el control

Para poder implementar este camino se necesitara definir una propiedad que será útil cuando se trabaje con el evento del control.

Esta propiedad no se dispone de forma estándar en el control, sino que deberá crearse un control que extienda del original.

public class CustomRadioButtonList : RadioButtonList 
    {
        //
        // La creacion del custom control es justamente para agregar esta propiedad 
        // que por defecto no posee el RadioButtonList
        //
        [DefaultValue("")]
        public string CommandArgument
        {
            get
            {
                string s = ViewState["CommandArgument"] as string;
                return s == null ? String.Empty : s;
            }
            set
            {
                ViewState["CommandArgument"] = value;
            }
        }

    }

Es importante mencionar que el uso de este nuevo control requiere de su declaración en la pagina

<%@ Register Assembly="GridViewEventosControles" Namespace="GridViewEventosControles"
    TagPrefix="cc1" %>

El siguiente paso sea definir del control que se ha creado dentro de la pagina.

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <cc1:CustomRadioButtonList ID="rblRespuesta" runat="server" 
            CommandArgument='<%#Container.DataItemIndex %>' 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </cc1:CustomRadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

La línea donde se usa el Container.DataItemIndex permite asigna el índice de cada row a la propiedad, para que pueda ser tomada desde el código al ejecutarse el evento.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    CustomRadioButtonList radiolist = sender as CustomRadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se toma el index de la row del gridview, el cual fue asociado en el CommandArgument
    //
    int rowindex = Convert.ToInt32(radiolist.CommandArgument);
    
    //
    // Sabiendo el index de la row con que se debe trabajar se pued recuperat el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[rowindex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

[C#]
[C# Skydrive]

 

Accediendo al objeto contenedor

Para esta implementación no se requiere el uso de ningún control adiciona, simplemente se hará uso de una propiedad que permite recuperar el contenedor del control.

Específicamente se hace referencia a NamingContainer

La definición del control en el grid será de forma normal

<asp:TemplateField HeaderText="Respuesta">

    <ItemTemplate>
        <asp:RadioButtonList ID="rblRespuesta" runat="server" 
            onselectedindexchanged="rblRespuesta_SelectedIndexChanged" AutoPostBack="True">
        </asp:RadioButtonList>
    </ItemTemplate>

</asp:TemplateField>

Pero el acceso al row que contiene el control es donde esta el truco.

protected void rblRespuesta_SelectedIndexChanged(object sender, EventArgs e)
{
    RadioButtonList radiolist = sender as RadioButtonList;

    if (radiolist == null)
        return;

    //
    // Se recupera la row del GridView que contien el control
    //
    GridViewRow row = radiolist.NamingContainer as GridViewRow;
    
    //
    // Sabiendo el index de la row con que se debe trabajar se puede recuperar el id
    //
    int idpregunta = Convert.ToInt32(GridView1.DataKeys[row.RowIndex].Value);

    bool seleccion = Convert.ToBoolean(Convert.ToInt32(radiolist.SelectedValue));

    PreguntasManager.Update(idpregunta, seleccion);

}

Es allí donde se usa la propiedad para obtener el row donde esta contenido el control que lanzo el evento, y el el index será utilizado para obtener el identificador de la entidad, en este caso de la pregunta a la cual se da la respuesta.

 

[C#]
[C# SkyDrive]

lunes, 20 de junio de 2011

[GridView] Aplicar color en las filas

 

Introducción

Este artículo plantea como aplicar color a las filas del gridview basándose en la definición de cierta regla previamente establecida.

En el ejemplo se tiene una lista de productos, la cual por “error” uno fue duplicado, la idea es poder detectar en que registros del grid se presenta este problema y remarcarlo con un color diferente para un fácil reconocimiento.

Se hará uso de linq trabajando junto a un dataset para poder aplicar la verificación de cada ítem en memoria.

El resultado obtenido será el visualizado en la imagen siguiente:

Determina color GridView

Para aplicar la lógica de colores en cada fila del grid será usado el evento RowDataBound, el mismo se lanza por cada row creada en el grid, es por eso que validando la información de la fila contra el origen de datos será posible determinar si la información esta repetida

 

protected void GridView1_RowDataBound(object sender, GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.DataRow)
    {
        string desc = e.Row.Cells[0].Text;

        int cant = (from item in ((DataTable)Session["datos"]).AsEnumerable()
                      where item.Field<string>("Descripcion") == desc
                      select item).Count();

        if (cant > 1)
        {
            e.Row.BackColor = Color.LightCoral;
        }
        
    }
}

 

[C#]
[C# SkyDrive]

[Winforms] Control global de Errores – Implementar Log

 

Introducción

He notado en varias oportunidades que en ciertas circunstancias surgen errores no controlador que son difíciles de rastrear

Estos es consecuencia de un descuido, al no agregar correctamente en todos los puntos importantes el try..catch, o simplemente por la lógica de la aplicación por se grande implicaría un gran esfuerzo poner en cada evento el control de errores.

Es por eso que definir un control global a nivel de aplicación podría ayudar, atrapando el problema y registrando que sucedió y donde, esta información es muy preciada cuando se esta perdido y no se sabe que produce el problema, mas cuando se esta en el entorno de producción y no se cuenta con una herramienta de debug

En este artículo se trataran los siguientes temas:

  1. Control global de errores
  2. Log usando System.IO.Log

 

1- Control Global Errores

El implementar la lógica para controlar globalmente los errores no es nada difícil, el truco esta en adjuntarse a un evento, concretamente:

Application.ThreadException

Un buen lugar para realizar esta asignación del evento es el archivos Program.cs, dentro del método Main()

[C#]

[STAThread]
static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

    Application.Run(new Form1());
}

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
{
    MessageBox.Show(e.Exception.Message);
}

Para lograr esto mismo en vb.net requiere de algunos paso adicionales, ya que vb.net no brinda un acceso transparente al método Main, ni permite que este sea asignado como inicio de la aplicación, pero en este articulo

Winforms, realizar tareas antes de inicializar aplicación

explico como destrabar esta situación para poder así implementarlo como lo harían en c#, definiendo el Main() como inicio de la aplicación.

[VB.NET]

<STAThread()> _
Friend Shared Sub Main()

    Application.EnableVisualStyles()
    Application.SetCompatibleTextRenderingDefault(False)

    AddHandler Application.ThreadException, AddressOf Application_ThreadException

    Application.Run(New Form1())

End Sub

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    MessageBox.Show(e.Exception.Message)

End Sub

El código en ambos lenguajes preserva el mismo concepto, pero la forma en como se adjunta el evento difiera bastante.

Entonces, ahora si sucediera algo como lo reflejado en la imagen

imagen1

O sea, si se ingresara caracteres en el calculo, el no atrapar el error en un bloque try..catch, haría que la aplicación finalice de forma brusca cerrándose, pero al tener definido el evento ThreadException, entraría en acción tomando el error y mostrando el mensaje.

Con estos simples paso ya tenemos el control global de errores implementado.

 

2- Log usando System.IO.Log

Si bien en una primer instancia hacer uso de un mensaje podría ayudar a detectar el problema en la aplicación, este podría evolucionar en un log a un archivo para dejar tracking de lo sucedido, en este caso no solo se pondría el mensaje del problema, sino que además se podría agregar el StackTrace para poder analizar que métodos se fueron ejecutando hasta causar el fallo.

En este caso el log a un archivos será muy simple

[C#]

static void Application_ThreadException(object sender, System.Threading.ThreadExceptionEventArgs e)
        {
            using(FileRecordSequence record = new FileRecordSequence("application.log", FileAccess.Write))
            {

                string message = string.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, 
                                                                                    e.Exception.Message, 
                                                                                    e.Exception.StackTrace);

                record.Append(CreateData(message), SequenceNumber.Invalid, 
                                                            SequenceNumber.Invalid, 
                                                            RecordAppendOptions.ForceFlush);
            }
        }


        private static IList<ArraySegment<byte>> CreateData(string str)
        {
            Encoding enc = Encoding.Unicode;

            byte[] array = enc.GetBytes(str);

            ArraySegment<byte>[] segments = new ArraySegment<byte>[1];
            segments[0] = new ArraySegment<byte>(array);

            return Array.AsReadOnly<ArraySegment<byte>>(segments);
        }

[VB.NET]

Private Shared Sub Application_ThreadException(ByVal sender As Object, ByVal e As System.Threading.ThreadExceptionEventArgs)

    Using record As New FileRecordSequence("application.log", FileAccess.Write)

        Dim message As String = String.Format("[{0}]Message::{1} StackTrace:: {2}", DateTime.Now, e.Exception.Message, e.Exception.StackTrace)

        record.Append(CreateData(message), SequenceNumber.Invalid, SequenceNumber.Invalid, RecordAppendOptions.ForceFlush)
    End Using

End Sub


Private Shared Function CreateData(ByVal str As String) As IList(Of ArraySegment(Of Byte))

    Dim enc As Encoding = Encoding.Unicode

    Dim _array As Byte() = enc.GetBytes(str)

    Dim segments As ArraySegment(Of Byte)() = New ArraySegment(Of Byte)(0) {}
    segments(0) = New ArraySegment(Of Byte)(_array)

    Return Array.AsReadOnly(Of ArraySegment(Of Byte))(segments)

End Function

Ahora el evento global de errores tiene bastante mas código, en donde se arma un mensaje bastante mas útil, el cual será enviado a la funcionalidad de log para registrar el suceso.

Para recuperar el archivo, solo deben ir a la ubicación donde esta el .exe, en este caso debería esta en la carpeta \bin\Debug del proyecto.

Lo mas probable es que el archivo no se legible a simple vista porque este sistema de log trabaja con el concepto de entradas de registros, es por eso que se confecciono un formulario muy simple para poder recuperar la información de forma visual.

[C#] 
[VB.NET] 

domingo, 12 de junio de 2011

[ASP.NET][GridView] - Como seleccionar una fila

 

Introducción


He visto en reiteradas oportunidad que una operación simple como es el caso de operar con una fila de un GridView se puede transformarse en algo complejo, mas que nada motivado por la distintas formas que hay para realizar esta acción.

En este articulo veremos las algunas formas de lograrlo y como difieren las técnicas que se puede aplicarse.

Temas que se tratar:

1- Seleccionar una Row

a- Definiendo un CommandField

b- Usando un ImageButton y CommandName

c- Usando el evento RowCommand

2-Uso de DataKeyNames y DataKeys

   a- DataKeyNames con campos Múltiples

Para todos los casos planteados partiremos del mismo gridview, el cual se ira modificando para agregarle opciones y ver los distintos temas.

 

1- Seleccionar una Row


Existen varias formas de realizar una misma tarea, pero veremos aquí las dos mas simples y directas que se suelen encontrar cuando se necesita seleccionar un registro en el control gridview.

1a- Definiendo un CommandField

Iremos realizando los paso de forma visual así se comprende como proceder, remarcando luego como impacta esto en el html del grid

El primer paso será editar las columnas del GridView hasta visualizar el cuadro con las opciones de CommandField disponibles.

imagen1

imagen2

Para este caso en particular se agregara solo la opción de selección. Mediante las flechas laterales se puede posicionar el comando. También se dispone de distintos tipos de representación visual, como ser un Link, Button o Image.

imagen3

Para este caso usaremos un comando del tipo Image, por lo tanto se deberá definir la propiedad “SelectImageUrl”. Si se define del tipo Link y se quiere cambiar el texto, se usaría la propiedad “SelectText”.

El próximo paso será el de definir el evento de selección, para esto solo marcamos el gridview, y yendo a sus propiedades se podrá activar el evento SelectedIndexChanged

imagen4

El html resultante debería tener resaltadas las siguiente características

 imagen5

Con estos pasos ya estamos listos para capturar la acción de selección del gridview.

 

1b - Usando un ImageButton y CommandName

En esta alternativa se hará uso de un TemplateField, se procede de la misma forma del paso 1a, pero se agrega un item diferente

imagen6

Una vez que esta el témplate, se adapta modificando directamente en el html, incluyendo de esta forma el control ImageButton.

Es muy importante remarcar que el ImageButton deberá tener la propiedad CommandName=”Select” para que esta ejecute el evento SelectedIndexChanged

imagen7

A nivel de código de la pagina se encontrara la definición del evento

imagen8

Hay que aclarar que en este caso se uso un ImageButton para corresponder con la acción del punto 1a, en donde se define una imagen, pero si se requiere de un link solo será cuestión de usar un LinkButton, definiendo en este el CommandName=”Select”, es justamente el CommandName quien define que evento será lanzado al presionarse.

 

1c- Usando el evento RowCommand

Seguramente a estas alturas se preguntaran que cantidad de formas de hacer lo mismo, asi es, y para completarlo una opción extra.

Resulta que al definir un ImageButton (o LinkButton) en un TemplateItem y usar el CommandName=”Select” se habilita un evento adicionar para poder capturar esta acción, si es que el SelectedIndexChanged no nos convence.

Se trata del evento RowCommand.

Hay un pequeño detalle con este evento y se trata de la definición del CommandArgument para determinar que fila lanza la acción.

imagen9

[C#]

protected void gvPerson_RowCommand(object sender, GridViewCommandEventArgs e)
{
    if (e.CommandName == "Select")
    {
        //
        // Se obtiene indice de la row seleccionada
        //
        int index = Convert.ToInt32(e.CommandArgument);
        
        //
        // Obtengo el id de la entidad que se esta editando
        // en este caso de la entidad Person
        //
        int id = Convert.ToInt32(gvPerson.DataKeys[index].Value); 

    }

}

[VB.NET]

Protected Sub gvPerson_RowCommand(sender As Object, e As GridViewCommandEventArgs)
	
        If e.CommandName = "Select" Then
		'
		' Se obtiene indice de la row seleccionada
		'
		Dim index As Integer = Convert.ToInt32(e.CommandArgument)

		'
		' Obtengo el id de la entidad que se esta editando
		' en este caso de la entidad Person
		'

		Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(index).Value)
	End If

End Sub

En el ejemplo de la pagina WebForm4.aspx, se podrá probar como ambos eventos, tanto el RowCommand y el SelectedIndexChanged, pueden definirse, aunque lo normal es utilizar solo uno de estos.

 

2 - Uso de DataKeyNames y DataKeys


Una de las mejores técnicas usada para detectar que entidad se esta editando o seleccionado es por medio de id o código que esta tenga asignada, pero como logar hacerlo sin mostrar el identificador al usuario en una columna ?, es justamente el trabajo de estas dos propiedades que se consigue resolver el problema.

Si se presta atención al html este contaba con al definición de esta propiedad de nombre DataKeyNames

imagen10

Solo debe definirse que campo del origen de datos identifica a la entidad que se esta trabajando.

Cuando se lance el evento solo será cuestión de tomar la row que ejecuta la acción, y de esta, por el índice recuperar el valor del id de la entidad, en este caso el PersonID.

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Value);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Value)


End Sub

2a- DataKeyNames con campos Múltiples

En este ejemplo solo se utilizo un valor simple para identificar a la entidad, pero el CommandName puede definir mas de un campo de información.

Por ejemplo que sucede si se quiere enviar el PersonId y el Nombre, esto es tan solo un ejemplo para demostrar las funcionalidad, no tiene una aplicación práctica en este caso, ya que con solo el PersonID seria mas que suficiente.

Para definir el DataKeyNames en el grid es tan simple como separar los campos por una coma.

imagen11

y en el código solo se toma el valor de la propiedad Values

[C#]

protected void gvPerson_SelectedIndexChanged(object sender, EventArgs e)
{
    //
    // Se obtiene la fila seleccionada del gridview
    //
    GridViewRow row = gvPerson.SelectedRow;

    //
    // Obtengo el id y el nombre  de la entidad que se esta editando
    // en este caso de la entidad Person
    //
    int id = Convert.ToInt32(gvPerson.DataKeys[row.RowIndex].Values["PersonID"]);

    string nombre = Convert.ToString(gvPerson.DataKeys[row.RowIndex].Values["FirstName"]);


}

[VB.NET]

Protected Sub gvPerson_SelectedIndexChanged(sender As Object, e As EventArgs)
	'
	' Se obtiene la fila seleccionada del gridview
	'
	Dim row As GridViewRow = gvPerson.SelectedRow

	'
	' Obtengo el id y el nombre  de la entidad que se esta editando
	' en este caso de la entidad Person
	'
	Dim id As Integer = Convert.ToInt32(gvPerson.DataKeys(row.RowIndex).Values("PersonID"))

	Dim nombre As String = Convert.ToString(gvPerson.DataKeys(row.RowIndex).Values("FirstName"))


End Sub

Ejemplo Código


Para el ejemplo se hizo uso de Visual Studio 2008 con SP1, el service pack es útil para poder hacer uso de Entity Framework y poder crear el ADO.NET Entity Data Model haciendo simple el acceso a la db.

La base de datos es Sql Server 2008 Express R2, y se encuentra el mdf dentro de la carpeta App_Data, pero se podría adjuntar al servicio de Sql Server o hacer uso del script que se encuentra en el proyecto de DataAccess.