domingo, 8 de agosto de 2010

[GridView] ITemplate – Columnas definidas en runtime

 

Introducción


Suelen darse los escenarios en donde se necesite editar distintas entidades pero haciendo uso de un único control gridview.

Una respuesta rápida a este problema seria hacer uso de la propiedad AutoGenerateColumns en true, para que los datos que se le proporciona al control defina las columnas que debe mostrar, lo malo de esta opción es que se pierde control sobre la grilla.

Otra alternativa interesante podría ser el uso de la clase BoundField con esta seria posible definir columnas en tiempo de ejecución, si bien podría ser la solución en la mayoría de los caso, esta no permite un control total del témplate que se debe usar en al edición de las columnas.

La solución definitiva al problema esta en la implementación de témplates de columnas, estas clases especializadas contendrán el código del témplate que define, para que esto se posible se necesitara implementar la interfaz ITemplate 

El modelo del ejemplo de código planteado hace referencia a dos listados, uno de notebooks y otro de televisores, ambos con distintas columnas por mostrar, pero haciendo uso de un solo control de grilla y la definición de las columnas de forma explicita en runtime.

 

Uso del BoundField


La definición de las columnas mediante esta clase podrá apreciarse en el formulario de nombre “GridViewBoundField.aspx”

public partial class GridViewBoundField : System.Web.UI.Page
{
    protected void Page_Init(object sender, EventArgs e)
    {
        DefinirColumnasNotebook();
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            GridView1.DataSource = DataAccess.ObtenerListaNotebook();
            GridView1.DataBind();
        }
    }

    private void DefinirColumnasNotebook()
    {
        //
        // Se define el campo dentro de la grilla, 
        // para poder identificar cada item 
        //
        GridView1.DataKeyNames = new string[] { "Id" };

        GridView1.Columns.Clear();

        BoundField tempDesc = new BoundField();
        tempDesc.HeaderText = "Descripcion Producto";
        tempDesc.DataField = "Descripcion";
        GridView1.Columns.Add(tempDesc);

        BoundField tempPrecio = new BoundField();
        tempPrecio.HeaderText = "Precio";
        tempPrecio.DataField = "Precio";
        GridView1.Columns.Add(tempPrecio);

    }
}

Se notara en el código que las columnas son definidas en el evento Page_Init, mientras que los datos son cargadas en el Page_Load

Esto es porque la definición de las columnas al ser dinámicas se deberán crear cada vez que se realice un postback, mientras que los datos pueden volver a bindear a la grilla, o no, eso dependerá de la funcionalidad que se quiera lograr

 

Definición de ITemplate


La definición de las columnas por medio de la creación de templetes de columnas, podrá apreciarse en el formulario de nombre “GridViewITemplate.aspx”.

Para poder hacer uso de template de columnas en el control GridView, será necesario la implementación de a interfaz ITemplate, a continuación se vera el código de estas clases:

public class GridViewHeaderTemplate : ITemplate
{
    string text;

    public GridViewHeaderTemplate(string text)
    {
        this.text = text;
    }

    public void InstantiateIn(System.Web.UI.Control container)
    {
        Literal lc = new Literal();
        lc.Text = text;

        container.Controls.Add(lc);

    }
}

public class GridViewEditTemplate : ITemplate
{
    private string columnName;

    public GridViewEditTemplate(string columnName)
    {
        this.columnName = columnName;
    }

    public void InstantiateIn(System.Web.UI.Control container)
    {
        TextBox tb = new TextBox();
        tb.ID = string.Format("txt{0}", columnName);
        tb.EnableViewState = false;
        tb.DataBinding += new EventHandler(tb_DataBinding);

        container.Controls.Add(tb);
    }

    void tb_DataBinding(object sender, EventArgs e)
    {
        TextBox t = (TextBox)sender;

        GridViewRow row = (GridViewRow)t.NamingContainer;

        string RawValue = DataBinder.Eval(row.DataItem, columnName).ToString();

        t.Text = RawValue;
    }
}

public class GridViewItemTemplate : ITemplate
{
    private string columnName;

    public GridViewItemTemplate(string columnName)
    {
        this.columnName = columnName;
    }
    
    public void InstantiateIn(System.Web.UI.Control container)
    {
        Literal lc = new Literal();

        lc.DataBinding += new EventHandler(lc_DataBinding);

        container.Controls.Add(lc);

    }

    void lc_DataBinding(object sender, EventArgs e)
    {
        Literal l = (Literal)sender;

        GridViewRow row = (GridViewRow)l.NamingContainer;

        string RawValue = DataBinder.Eval(row.DataItem, columnName).ToString();

        l.Text = RawValue;
    }
}


public class GridViewItemCheckTemplate : ITemplate
{
    private string columnName;

    public GridViewItemCheckTemplate(string columnName)
    {
        this.columnName = columnName;
    }

    public bool CanEdit { get; set; }

    public void InstantiateIn(System.Web.UI.Control container)
    {
        CheckBox check = new CheckBox();
        check.ID = string.Format("chk{0}", columnName);
        check.Enabled = this.CanEdit;
        check.DataBinding += new EventHandler(check_DataBinding);

        container.Controls.Add(check);

    }

    void check_DataBinding(object sender, EventArgs e)
    {
        CheckBox check = (CheckBox)sender;

        GridViewRow row = (GridViewRow)check.NamingContainer;

        string value = DataBinder.Eval(row.DataItem, columnName).ToString();

        check.Checked = bool.Parse(value);
    }

}

Cada una representa un témplate de visualización y edición dentro del Gridview.

El método principal que debe implementarse es InstantiateIn(), dentro de este se definirá el o los control que conformen el témplate de columna para el estado especifico.

Algo que seguramente llamara la atención es el uso del evento DataBinding, el cual es usada para tomar los datos al momento de bindear cada fila de la grilla, este evento será ejecutado tantas veces como filas tenga.

Estas clases serán usadas para definir cada témplate de columna:

private void DefinirColumnasNotebook()
{
    //
    // Se define el campo dentro de la grilla, 
    // para poder identificar cada item 
    //
    GridView1.DataKeyNames = new string[] { "Id" };

    GridView1.Columns.Clear();

    //
    // Columna Descripcion
    //
    TemplateField tempDesc = new TemplateField();
    tempDesc.HeaderTemplate = new GridViewHeaderTemplate("Descripcion Producto");
    tempDesc.ItemTemplate = new GridViewItemTemplate("Descripcion");
    tempDesc.EditItemTemplate = new GridViewEditTemplate("Descripcion");
    GridView1.Columns.Add(tempDesc);

    //
    // Columna Precio
    //
    TemplateField tempPrecio = new TemplateField();
    tempPrecio.HeaderTemplate = new GridViewHeaderTemplate("Precio");
    tempPrecio.ItemTemplate = new GridViewItemTemplate("Precio");
    tempPrecio.EditItemTemplate = new GridViewEditTemplate("Precio");
    GridView1.Columns.Add(tempPrecio);
}

private void DefinirColumnasTelevisores()
{
    GridView1.DataKeyNames = new string[] { "Id" };

    GridView1.Columns.Clear();

    //
    // Columna Descripcion
    //
    TemplateField tempDesc = new TemplateField();
    tempDesc.HeaderTemplate = new GridViewHeaderTemplate("Descripcion Televidor");
    tempDesc.ItemTemplate = new GridViewItemTemplate("Descripcion");
    tempDesc.EditItemTemplate = new GridViewEditTemplate("Descripcion");
    GridView1.Columns.Add(tempDesc);

    //
    // Columna PrecioUnitario
    //
    TemplateField tempPrecio = new TemplateField();
    tempPrecio.HeaderTemplate = new GridViewHeaderTemplate("Precio Unitario");
    tempPrecio.ItemTemplate = new GridViewItemTemplate("PrecioUnitario");
    tempPrecio.EditItemTemplate = new GridViewEditTemplate("PrecioUnitario");
    GridView1.Columns.Add(tempPrecio);

    //
    // Columna EsPlasma
    //
    TemplateField tempEsPlasma = new TemplateField();
    tempEsPlasma.HeaderTemplate = new GridViewHeaderTemplate("Plasma");

    GridViewItemCheckTemplate esPlasmaItem = new GridViewItemCheckTemplate("EsPlasma");
    tempEsPlasma.ItemTemplate = esPlasmaItem;

    GridViewItemCheckTemplate esPlasmaEdit = new GridViewItemCheckTemplate("EsPlasma");
    esPlasmaEdit.CanEdit = true;
    tempEsPlasma.EditItemTemplate = esPlasmaEdit;

    GridView1.Columns.Add(tempEsPlasma);
}

 

Se define tanto el témplate del Ítem, como el de edición y encabezado, usando para ello el témplate que corresponda, se debe tener presente que también el tipo de dato a mostrar influye en la decisión de que témplate utilizar, un ejemplo muy claro lo representa el checkbox que marca si el televisor es de plasma o no, representado por un témplate que justamente dibuja un check en la celda.

También hay que comentar que no hay una forma única de crear las clases de témplate, estas podrían tomar la info mediante propiedades o pasarlas en el constructor. Un ejemplo de esto es la clase “GridViewItemCheckTemplate” la cual asigna el nombre del campo al cual vincula los datos, pero si debe permitir la edición o no, es asignada mediante una propiedad, tomando un valor por defecto en caso de no asignar valor.

La edición de un registro en la grilla implica todo un tema:

protected void GridView1_RowEditing(object sender, GridViewEditEventArgs e)
{
    GridView1.EditIndex = e.NewEditIndex;
    DataBindGrid();
}

protected void GridView1_RowCancelingEdit(object sender, GridViewCancelEditEventArgs e)
{
    GridView1.EditIndex = -1;
    DataBindGrid();
}

protected void GridView1_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
    int Id = Convert.ToInt32(GridView1.DataKeys[e.RowIndex].Value);
    
    GridViewRow row = GridView1.Rows[e.RowIndex];

    if (Session["datos"] is List<Notebook>)
    {
        Notebook notebookActualizada = (from item in (List<Notebook>)Session["datos"]
                                         where item.Id == Id
                                         select item).FirstOrDefault();


        TextBox txtDescripcion = row.FindControl("txtDescripcion") as TextBox;
        notebookActualizada.Descripcion = Convert.ToString(txtDescripcion.Text);

        TextBox txtPrecio = row.FindControl("txtPrecio") as TextBox;
        notebookActualizada.Precio = Convert.ToInt32(txtPrecio.Text);

    }
    else if (Session["datos"] is List<Televisor>)
    {
        Televisor televisorActualizado = (from item in (List<Televisor>)Session["datos"]
                                            where item.Id == Id
                                            select item).FirstOrDefault();


        TextBox txtDescripcion = row.FindControl("txtDescripcion") as TextBox;
        televisorActualizado.Descripcion = Convert.ToString(txtDescripcion.Text);

        TextBox txtPrecio = row.FindControl("txtPrecioUnitario") as TextBox;
        televisorActualizado.PrecioUnitario = Convert.ToInt32(txtPrecio.Text);

        CheckBox chkEsPlasma = row.FindControl("chkEsPlasma") as CheckBox;
        televisorActualizado.EsPlasma = chkEsPlasma.Checked;
    }

    GridView1.EditIndex = -1;
    DataBindGrid();
}

Mediante los evento RowEditing y RowCancelingEdit, se controla que la fila este o no en estado de edición, esto indica a la grilla cuando debe cambiar el témplate de edición que se ha definido.

El evento RowUpdating actuara cuando se acepta la edición, es en este momento donde controla que tipo de lista se esta visualizando, como primer paso se localiza la entidad dentro de la colección que se había usado para bindear la grilla, para esta tarea se hizo uso de Linq.

Luego se toma la información de los controles que genero cada template, hay que remarcar en este punto que los template usan internamente el agregado de un prefijo con respecto al tipo de control que agregan, agregando este al nombre de la columna que se le asigno, por ejemplo:

tb.ID = string.Format("txt{0}", columnName);

Esta línea agrega el prefijo “txt” al nombre del campo, es por esto que luego al buscar el control se uso

TextBox txtDescripcion = row.FindControl("txtDescripcion") as TextBox;

En donde “Descripcion” es el nombre del campo, y “txt” el prefijo.

Al igual que se hizo con el BoundField, en este caso la definición de las columnas se realiza en el Page_Init

protected void Page_Init(object sender, EventArgs e)
{
    if (Session["datos"] == null || Session["datos"] is List<Notebook>)
    {
        DefinirColumnasNotebook();

    }
    else if (Session["datos"] is List<Televisor>)
    {
        DefinirColumnasTelevisores();
    }
}

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        Session["datos"] = DataAccess.ObtenerListaNotebook();
        DataBindGrid();
    }
}

private void DataBindGrid()
{
    GridView1.DataSource = Session["datos"];
    GridView1.DataBind();
}

Hay un método adicional que por ahí no este tan claro, DataBindGrid(), este método simplemente toma la info de session y bindea la grilla, este cache de información en session me pareció importante ya que por cada accion que se realiza la grilla debe ser bindeada a los datos, lo cual podría producir una sobrecarga de comunicación si en todo momento debe ir controla la db para buscar la información.

 

Ejemplos de Código


El ejemplo fue confeccionado con Visual Studio 2008.

 

[C#] 
[VB.NET] 

66 comentarios:

  1. Hola Leandro

    Una consulta.
    Como puedo llamar a otro formulario haciendo click en una celda del datagridview de otro formulario.
    Me ha salido con una columna del tipo
    DataGridViewButtonColumn
    en visual .net 2008

    algo asi
    Private Sub DataGridView1_CellContentClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.DataGridViewCellEventArgs) Handles DataGridView1.CellContentClick
    If e.ColumnIndex = 0 Then
    Dim formulario As New Form2()
    formulario.ShowDialog()
    End If
    End Sub

    pero no me sale cuando la columna es del tipo
    DataGridViewTextBoxColumn
    que codigo podria implementar
    para conseguir el mismo efecto

    Muchas Gracias
    Saludos

    ResponderEliminar
  2. Leandro masomenos resolvi este tema con un artículo tuyo.
    Para complementar , quería saber como puedo hacer para truncar al form.Osea de un Form1
    llamo al Form2, pero no quisiera crear varias instancias hasta que seleccione algo del Form2 para pintarlo en el Form1.
    Me guie de este ejemplo tuyo
    C# – DataGridView – Parte 3 – Pasaje de información entre grillas en distintos formulario.
    Agradezco de antemano tu respuesta.
    Saludos

    ResponderEliminar
  3. hola Augusto

    Que bueno que los articulos son de utilidad.

    Bien con respecto al planteo me surgen algunas dudas, no veo la relacion entre crear instancias y pintar en un formulario.

    Las acciones no me quedan claras, basicamente lo que pasas es informacion de un form al otro, y las instancias no son varias, son siempre las mismas que se pasan de in form al otro para poder comunicarse

    Si la pregunta es algo compleja de explicar, te aconsejaria que la realices en los foros de MSDN:

    Foro C#

    Alli participo activamente, sino soy yo quien la responde seguro habra mucho colaboradores con ganas de ayudar.

    saludos

    ResponderEliminar
  4. Buenos dias Leandro. No he tenido mucho tiempo de revisar todo el material que has colocado en este sitio razón por la que te planteo esta pregunta: ¿puedo crear dataadapters asi: select * from productos where IdePro = @IdePro
    usando el asistente?
    Uso vs2010 express

    ResponderEliminar
  5. hola Marlon

    Aunque no lo has mencionado imagino que por asitente te refieres al que provee la creacion de DataSet tipados.

    O sea cuando concluye el asistente obtiene un archivo de extension .xsd, en donde se definen datatable con campos y puede usar los TablaAdapter de cada datatable.

    Si es asi podrias comentarte que si puedes hacer esto que mencionas, podrias extrender la funcionalidad, revisa este link:

    Building a DAL using Strongly Typed TableAdapters and DataTables in VS 2005 and ASP.NET 2.0


    Alli veras como agregar nuevos metodos con parametros en tu consulta para extender la funcionalidad.

    De esta forma podrias crear un metodo que toma como parametro IdePro, que usaras luego en la query del TableAdapter.

    Bueno espero esto sea de utilidad

    saludos

    ResponderEliminar
  6. Buenas noches Leandro.
    Mi planteamiento: resulta que tengo un proyecto en donde si coloco un picturebox y le asigno una imagen no me muestra nada. tuve problemas con un módulo y observé que me falta el archivo Resources.Designer.cs. Estoy seguro que este es el problema. Ahora, no se como recuperarlo o reconstruirlo. Gracias por adelantado...

    ResponderEliminar
  7. hola MarlonVillamizar

    No has probado regenerando el archivo de recurso, como explica este link

    Missing auto-generated Resources.Designer.cs file


    Veras que este recursos puede encontrarse oculto en la carpeta "Properties" del proyecto:
    imagen

    saludos

    ResponderEliminar
  8. Hola Leandro.

    Tengo una duda que si bien es algo que parece superficial me gustaría si hay alguna forma practica de resolverla.

    Resulta que no he querido usar AutoGenerateEditButton="True" y me he creado mis Clases para poder usar ImageButtons: Public Class GridViewItemCommandTemplate(aqui agregue un ImageButton para Edit y otro para Delete) y en GridViewEditCommandTemplate los ImageButtons para Update y Cancel, a pesar de que todo me funciona OK(auque tal vez no sea la mas adecuada ), me gustaria saber si hay alguna manera de InstantiateIn en ambas clases, crear/agregar algo en medio de ambos imagebuttons de manera que haya espacios en blanco entre los botones(tanto entre edit y delete como entre Update y cancel) porque quedan muy pegados.

    ya ves porque te digo q es algo q parece muy superficial, pero me gustaría saber si hay una forma practica de lograrlo antes de intentar salidas no muy practicas. :D

    Saludos y gracias de antemano

    ResponderEliminar
  9. hola Axel

    no se si entendi del todo el planteo, pero si estas diseñando tu mismo el template podrias agregar html que le de formato

    por ejemplo podrias agregar un   para poner espacios, pero como se que es por codigo esto lo haces con el HtmlGenericControl
    aunqru creo que con el LiteralControl tambien funciona

    entonces creas una instancia

    LiteralControl espacio = new LiteralControl(" ");

    contenedor.Controls.Add(espacio);

    es mas podrias hasta usar tablas de html si lo necesitas, recuerda que esta el HtmlTable como clase y puedes desde codigo .net armar html con estas

    saludos

    ResponderEliminar
  10. Buenas Leandro, tengo una duda sobre Linq, si bien es cierto la reducción de código es bastante notoria con Linq, que me podrías decir del rendimiento, es este mayor, menor o igual??

    Saludos y gracias

    ResponderEliminar
  11. hola Edalo

    te cuento que la situacion varia segun que buscas si sitieen uan lista con quizas 100 items si aplcias linq puedo asegurater que elr endimiento anda perfecto

    ahora si hablamos de imaginemos 10000 items bueno aqui hay que tenee algo asm de cuidado, primero porque son items en memoria, y depsues porque si apcais alguna operacion compleja como ser la union con otra lista, o agrupados, etc
    puedes algun problema

    pero yo lo he usado con listas de 200000 items y la verdad sin problemas

    algio igual que no has mencionado es a que linq haces referencia porque yo apunto a alinq basico, no a linq to sql o a linq to entities

    aunque igual estos son muy bueno tambien para mapear las entidades a la tabla de tu db

    saludos

    ResponderEliminar
  12. Hola Leandro, tengo una duda, tengo un gridview con 3 columnas, una de ellas tiene un LABEL, la segunda tiene un Combobox y la ultima un textbox, quisiera poder llenar solamente la primera columna donde estan los label, pasandole una lista de una clase materia: list();

    tengo este codigo:

    public void Inicio()
    {
    int i;
    GridViewRow row;
    Label chequeado = new Label();
    for (i = 0; i < GridView1.Rows.Count; i++)
    {
    row = GridView1.Rows[i];
    if (row.RowType == DataControlRowType.DataRow)
    {
    chequeado = (Label)row.FindControl("LabelMateria");
    chequeado.Text ="Calculo II";
    row.FindControl("LabelMateria").Controls.Add(chequeado);
    GridView1.Controls.Add(row);
    ;
    }
    }
    }

    creo que la linea que dice GridView1.Controls.Add(row); esta mala, como le asigno propiamente a esa columna del Gridview la lista de materias, de antemano muchas gracias!

    ResponderEliminar
  13. hola Kleyvert

    un primero punto, para que haces esto:
    Label chequeado = new Label();

    si despues haces:
    chequeado = (Label)row.FindControl("LabelMateria");

    porque directamente no haces:
    Label chequeado = (Label)row.FindControl("LabelMateria");

    o sea defines y asignas directamente



    ademas estas lineas:

    row.FindControl("LabelMateria").Controls.Add(chequeado);
    GridView1.Controls.Add(row);

    no hacen falta para nada
    si has usado el FindControl y localizado el label en la row y cambias su valor, con eso alcanza


    saludos

    ResponderEliminar
  14. Hola Leandro,

    Muchas gracias por la entrada.
    Me sirvió mucho para un gridview dinámico que estoy haciendo.
    Ahora quería añadirle al checkbox dinámico la propiedad CommandName para controlar qué checkboxes se marcan y actualizarlos en la BD, pero no encuentro la forma.

    Muchas Gracias,
    Saludos

    ResponderEliminar
  15. hola Ivan

    pero en el articulo ya se trata el tema de crear un template dinamico con checkbox

    seria similar a la clase GridViewItemCheckTemplate


    quizas en tu caso el tema del CanEdit no lo tendrias
    pero la tecnica seria igual

    saludos

    ResponderEliminar
  16. Hola Leandro,

    Muchas gracias por tu atención.
    Me ayudó mucho tu sugerencia.
    Al final usé tu ejemplo para generar el gridview dinámico pero para el checkbox utilicé la idea de "http://aspadvice.com/blogs/joteke/archive/2006/10/13/Command_2D00_capable-CheckBox-with-GridView.aspx"

    Saludos

    ResponderEliminar
  17. Hola Leandro, una consulta, las clases me funcionaron bie, muchas gracias; pero al momento de exportar los datos a excel me muestra una tabla vacia. ¿Como puedo solucionarlo y a que se debe?

    De antemano muchas gracias.

    ResponderEliminar
  18. hola Omar

    pero al momento de exportar estas recreando la estructura del gridview?

    porque recuerda para exportar seguro lance el render con lo cual debes volver a asignar estas columnas que creas desde codigo

    saludos

    ResponderEliminar
  19. Hola Leandro, muchas gracias por la ayuda, eso es lo que me estaba faltando ahora ya se muestran los datos cuando exporto la tabla a excel.

    Saludos

    ResponderEliminar
  20. Excelente post, quería bajar los archivos pero no están disponibles, vuelve a subirlo por favor.

    ResponderEliminar
  21. hola marco

    yo pude descargarlo sin problemas


    saludos

    ResponderEliminar
  22. hola Leandro esta bueno el ejemplo yo estoy trabajando algo similar en c# pero me gustaria si me pudieras ayudar, como recuperar los valores que son escritos en los textbox dinamicos creados dentro del Gridview, ya ke cuando los creo fuera del grid , puedo recuperar pero los ke estan dentro del grid no puedo saber los valores ke son escritos... gracias

    ResponderEliminar
  23. hola Elmer

    en que evento defines las columnas del gridview? porque deberias ahcerlo en el Load de la pagina

    si lo haces alli podrias suar el FindControl() sobre la row que quieres recuperar los valores para localizar el control

    saludos

    ResponderEliminar
    Respuestas
    1. Esto no me esta funcionando. Sabras el porque?

      Eliminar
  24. hola Leandro una pregunta como puedo crear un datatable y un gridview dinámicos q al momento de mandar datos en el datatable se llene el grid y aparesca

    ResponderEliminar
  25. o un método lo que estoy haciendo que al seleccionar los checkbox de un gridview me manda a otra ventana con un textbox y un label para cada selección lo que yo quiero que también por cada selección me mande un datatable y aparesca el gridview con los datos de ese datatable solo que todo lo estoy haciendo dinamico agradecería tu ayuda
    te mando mi código
    List list = new List();
    if (Session["ruta"] != null)
    {
    list = (Session["ruta"] as List);
    foreach (Ruta ruta in list)
    {
    Panel panel = new Panel();
    TextBox textbox = new TextBox();
    Label eti = new Label();

    textbox.Text = ruta.Id_Ruta.ToString();

    Label salto = new Label();
    salto.Text = "
    ";
    eti.Text = "RUTA " + textbox.Text + "";

    panel.Controls.Add(eti);
    panel.Controls.Add(textbox);
    pnlCuerpo1.Controls.Add(salto);
    pnlCuerpo1.Controls.Add(panel);
    }
    }

    Listpanel = new List();
    if (Session["centro"] != null)
    {
    txtcentro.Text = (Session["centro"] as string);
    }

    }
    }

    ResponderEliminar
  26. hola alex

    la verdad el concepto de seccion no se cual sera, pero si necesitas un datatable puedes crearlo y volcar los registros recorriendo las rows del gridview

    DataTable dt = new DataTable();
    dt.Columns("col1");
    dt.Columns("col2");

    foreach(GridViewRow row in GridView1.Rows){
    DataRow dtrow = dt.NewRow();
    row["col1"] = row.Cells[0].Text;
    row["col2"] = row.Cells[1].Text;
    dtrow.Rows.Add(row);
    }

    saludos

    ResponderEliminar
  27. sesión sirve un ejemplo declaraste algo y para no volver a declararlo solo lo jalas con la session.
    me salen los siguientes errores con tu código:
    Error 1 No se puede utilizar como método el miembro 'System.Data.DataTable.Columns' no invocable.
    y el otro
    Error 4 No se puede aplicar la indización con [] a una expresión del tipo 'System.Web.UI.WebControls.GridViewRow'
    y el otro:
    Error 7 'System.Data.DataRow' no contiene una definición de 'Rows' ni se encontró ningún método de extensión 'Rows' que acepte un primer argumento de tipo 'System.Data.DataRow' (¿falta una directiva de uso o una referencia de ensamblado?)
    y me aparece que no encuentra el gridview eso es xq no tengo uno pero xq lo quiero hacer dinamico




    ResponderEliminar
  28. saludos espero me puedas ayudar gracias

    ResponderEliminar
  29. hola alex

    la verdad es que los error no me dicen mucho, ademas de ser algo raros, ya que algunos hacen referencia a un datatable lo cual no tiene nada que ver con el control gridview

    lo que no entiendo es porque comentas que no tiene un gridview, que los campos sean dinamicos no quiere decir que no pongas un control gridview en el form

    podrias plantear el problema en el foro
    foro asp.net

    donde plantees en que linea se produce el error
    ademas de algo del codigo que estas implementando, te comento lo del foro porque por este medio poner codigo sera algo complicado

    saludos

    ResponderEliminar
  30. hola Leandro molestándote de nuevo
    estoy creando mi gridview dinamico los datos los traigo de un datatable mi pregunta es como le puedo hacer´para que al momento que aparesca mi gridview en el aparesca checkbox gracias espero me puedas ayudar ya visite el foro y los link q me mandaste y nada de eso me ah servido espero me ayudes gracias

    ResponderEliminar
  31. hola alex

    si las columans las defines de forma dinamica desde codigo deberias definir como se plantea en el articulo un ITemplate que defina ese checkbox

    es mas en el articulo lo veras en la clase GridViewItemCheckTemplate

    saludos

    ResponderEliminar
  32. me podrias definir que es lo que tengo que poner de código pues soy nuevo y es mucho código realmente no se para que es todo mi código esta en el page load
    espero me ayudes gracias

    ResponderEliminar
  33. hola Leandro así te planteare mi pregunta esta mas clara
    espero me ayudes con el código que debo asignar, tengo un problema... resulta que tengo un gridview(asp.net 2010) el cual es dinámico, se me ha pedido crear un nueva columna el cual contenga checkbox para selección múltiple.
    gracias

    ResponderEliminar
  34. hola alex

    validaste el codigo de GridViewItemCheckTemplate

    porque alli se define un checckbox en un template dinamico

    saludos

    ResponderEliminar
  35. Hola Leandro,

    como le puedo hacer para que el control que definimos dinamicamente le genere su ID dinamico. por lo que entiendo el ID del Checkbox, siempre es el mismo para todos los que se generaron automaticamente.

    como puedo hacer para que el ID de cada checkbox sea distinto trayendome el valor del campo [id] de una tabla. El campo tiene los valores identity.

    en este caso seria algo como:

    Check1
    Check2
    Check3
    Check4
    Check5
    .
    .
    .
    .
    .


    ResponderEliminar
  36. hola osvaldo

    hasta donde se el propio asp.net es quien define el id (en realidad el ClientID) por lo que deberia variarlo, has inspeccionado el html resultante en el browser para ver si esto es asi?

    saludos

    ResponderEliminar
  37. hola Leandro me gusto mucho tu ejemplo yo estoy tratando de hacer algo similar
    tengo un menu dinamico q me muestra los datos de la tabla sublineas de la bd pero quiero que al dar clic en un producto de la sublinea me muestre en el formulario de productos todos los productos de ese producto de forma dinamica pero no se como hacerlo ¿podrias orientarme porfavor?

    ResponderEliminar
  38. hola Liseth

    tienes un boton de seleccion definido en el GridView? porque si a este le defines el CommandName="Select", podrias usar el evento SelectedIndexChanged

    en ese evento tomand el id que defines en el DataKeyNames, usnado el DataKeys, con el id ejecutarias la query

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

    saludos

    ResponderEliminar
  39. si es cierto lo intentare.Gracias por la ayuda.
    !!!Bendiciones!!!

    ResponderEliminar
  40. Hola Leandro,

    Muchas gracias por tu ayuda. Tengo un problema, al momento de realizar el evento de actualizacion en la grilla, no me toma ningun control de los que se agregar dinamicamente a traves de estas clases, hay que hacer algo adicional??

    Agrego la columna de la siguiente manera
    Dim tmpNomProgramaEN As New TemplateField
    Select Case Session("Idioma")
    Case 1 : tmpNomProgramaEN.HeaderTemplate = New GridViewTemplate.GridViewHeaderTemplate("Name of the Program EN")
    Case 2 : tmpNomProgramaEN.HeaderTemplate = New GridViewTemplate.GridViewHeaderTemplate("Nombre del Programa EN")
    End Select
    tmpNomProgramaEN.ItemTemplate = New GridViewTemplate.GridViewItemTemplate("programaEN")
    tmpNomProgramaEN.EditItemTemplate = New GridViewTemplate.GridViewEditTextBoxTemplate("programaEN")
    tmpNomProgramaEN.ItemStyle.HorizontalAlign = HorizontalAlign.Center
    gvProgramas.Columns.Add(tmpNomProgramaEN)

    y despues en el evento RowUpdating trato de recuperar los valores de la siguiente manera :
    Dim txtEN As TextBox = DirectCast(gvProgramas.Rows(e.RowIndex).FindControl("txtprogramaEN"), TextBox)

    Y me aparece el siguiente error:
    Se produjo una excepción de tipo 'System.NullReferenceException'

    Alguna sugerencia

    ResponderEliminar
  41. hola Julián

    pero estas seguro que defines el nombre correctamente, porque alli veo que haces

    tmpNomProgramaEN.EditItemTemplate = New GridViewTemplate.GridViewEditTextBoxTemplate("programaEN")

    y luego lo intntas buscar como

    Dim txtEN As TextBox = DirectCast(gvProgramas.Rows(e.RowIndex).FindControl("txtprogramaEN"), TextBox)

    o sea le has puesto un prefijo txt al nombre que antes no defines

    no sera por esto que no lo encuentra

    saludos

    ResponderEliminar
  42. try
    {
    if (e.CommandName == "link2")
    {
    GridViewRow fila2 = (GridViewRow)((Control)e.CommandSource).NamingContainer;
    String cod2 = GridView2.DataKeys[fila2.RowIndex].Values[0].ToString();
    Ver_Pasajeros(cod2);
    //int fila = 0;
    //lblNroAsiento.Text = GridView2.Rows[fila].Cells[0].Text;
    //lblPago.Text = GridView2.Rows[fila].Cells[3].Text;


    }


    }
    catch (Exception)
    {

    }
    tengo este codigo y al ejecutar el proyecto codigo me gustaria k al darle click a cada linkbutton,en los label me aparezca el nro d viaje y el costo d los viajes seleccionados asi tal km lo hacia en C# de escritorio

    ResponderEliminar
  43. hola Ruben

    lo primero que piedo comentar es que hacer esto

    catch (Exception)
    {

    }

    es una pesima idea, NUNCA se define un catch vacio porque despues tienes un error y no te enteras
    si defines un catch usalo para transformar el error o para loguearlo a un archivo asi dejas el registro de lo que sucede

    saludos

    ResponderEliminar
  44. hola Ruben

    que evento usas para trabajar el CommandName porque podrias usar el RowCommand

    pero si usas esto no necesitas el NamingContainer

    saludos

    ResponderEliminar
  45. Hola estuve viendo tu código y me sirvió para crear los campos de manera manual y luego cargar el GridView, pero solo me carga una consulta cuando creo una nueva consulta, no me deja me sale error parece que los otros campos de la primer consulta quedaron asociados al Grid y no se como hacer para cancelar eso.

    BoundField Fecha = new BoundField();
    Fecha.DataField "AB_fechaTransaccion";
    Fecha.HeaderText = "Fecha Transaccion";
    GridViewReportes.Columns.Add(Fecha);

    BoundField Nombre = new BoundField();
    Nombre.DataField = "TE_razonSocial";
    Nombre.HeaderText = "Nombre Tercero";
    GridViewReportes.Columns.Add(Nombre);

    BoundField Valor = new BoundField();
    Valor.DataField = "AB_valor";
    Valor.HeaderText = "Valor Abono";
    Valor.DataFormatString = "{0:C}";
    GridViewReportes.Columns.Add(Valor);
    this.CargarGrillaAbonosDiarios();

    este es mi código y esta consulta me corre, pero si muestro otra consulta diferente en este mismo grid, me sale error a pesar de que creo los otros campos, parece que los campos que cree anteriormente quedaron asociados al grid.

    ResponderEliminar
  46. Hola a todos tengo un problemita que no he podido resolver, resulta que estoy cargando un GridView de la siguiente manera:

    protected void CargarReporteAbonosDiarios()
    {

    string conn = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
    SqlConnection cnn = new SqlConnection(conn);

    DataTable dt = new DataTable();

    cnn.Open();
    SqlCommand cmd = new SqlCommand("PRC_REPORTES", cnn);

    cmd.Connection = cnn;
    cmd.CommandType = CommandType.StoredProcedure;
    /*Variables para elegir la operacion y opcion de la misma a ejecutar. */
    cmd.Parameters.Clear();
    cmd.Parameters.Add("i_operacion", SqlDbType.VarChar).Value = "S";
    cmd.Parameters.Add("i_opcion", SqlDbType.VarChar).Value = "E1";

    cmd.Parameters.AddWithValue("i_FechaInicial", TxtFechaInicio.Text);
    cmd.Parameters.AddWithValue("i_FechaFinal", TxtFechaFin.Text);
    cmd.Parameters.AddWithValue("i_TE_US_id", (string)Session["id"]);


    //Ejecutamos el procedimiento almacenado.

    SqlDataReader dr = cmd.ExecuteReader();
    GridViewReportes.DataSource = String.Empty;
    GridViewReportes.DataSourceID = String.Empty;

    dt.Clear();
    dt.Load(dr);
    GridViewReportes.DataSource = dt;
    GridViewReportes.DataBind();
    cnn.Close();
    cnn.Dispose();
    cmd.Dispose();
    dt.Dispose();





    }

    Y resulta que necesito que después de que se cargue el Grid, me agregue una fila de ultima, en al cual van a ir los totales de los datos que cargue en el Grid anteriormente.

    Mejor dicho yo lo que necesito saber es como insertar una fila en el GridView en blanco y después meterle los totales que capturo en el DataReader, en la posición que necesito, para que me quede como una especie de reporte, con sus precios y sus totales.

    Este es mi GridView en HTML por si lo necesitan:








    Los campos del GridView se los inserto de esta forma:

    this.GridViewReportes.Columns.Clear();

    BoundField Fecha = new BoundField();
    Fecha.DataField = "FA_cFecha";
    Fecha.HeaderText = "Fecha Creacion";
    GridViewReportes.Columns.Add(Fecha);

    BoundField Nombre = new BoundField();
    Nombre.DataField = "TE_razonSocial";
    Nombre.HeaderText = "Nombre Tercero";
    GridViewReportes.Columns.Add(Nombre);

    BoundField Subtotal = new BoundField();
    Subtotal.DataField = "FA_subtotal";
    Subtotal.HeaderText = "Subtotal";
    Subtotal.DataFormatString = "{0:C}";
    GridViewReportes.Columns.Add(Subtotal);

    BoundField Iva = new BoundField();
    Iva.DataField = "FA_ivaValor";
    Iva.HeaderText = "IVA";
    GridViewReportes.Columns.Add(Iva);

    BoundField Abono = new BoundField();
    Abono.DataField = "FA_abono";
    Abono.HeaderText = "Abono";
    Abono.DataFormatString = "{0:C}";
    GridViewReportes.Columns.Add(Abono);

    BoundField Total = new BoundField();
    Total.DataField = "FA_subtotal";
    Total.HeaderText = "Total";
    Total.DataFormatString = "{0:C}";
    GridViewReportes.Columns.Add(Total);

    this.CargarReporteResumenCompras();

    Muchísimas gracias por todo y espero me ayuden.

    ResponderEliminar
  47. hola Emerson

    si quieres agregar una row adicional al grid debes ponerla en la entidad que asignas al datasource

    ademas no uses esto
    dt.Load(dr);
    usa un DataAdapter

    DataAdapter da = new DataAdapter(cmd);
    da.Fill(dt);

    a ese dt le agregas una ultima row usando

    DataRow row = dt.NewRow();
    dt.Rows.Add(row);

    entonces al asignar al grid tendras una ultima fila

    sino la otra es habilitar el footer del grid

    Tutorial 15: Displaying Summary Information in the GridView's Footer

    saludos

    ResponderEliminar
  48. Hola Leandro,

    podrías decirme en vez de usar un DataAccess, cómo puedo usar mi cadena de conexión y en dónde implementarla?.

    Lo quiero así porque pienso tomar mis datos de una base de datos pero con tu método solo admite listas.

    Gracias

    Espero puedas ayudarme

    ResponderEliminar
  49. Este es mi código, te doy más detalle mira...

    namespace BLAH
    {
    public partial class Reuniones : System.Web.UI.Page
    {
    private void Page_Init(object sender, EventArgs e)
    {
    definirColumnasDocumentos();
    }

    SqlConnection Conexion = new SqlConnection("Data Source=BREMASS-AARON\\SQLEXPRESS;Initial Catalog=DBPrueba;Integrated Security=True"); // REALIZA CONEXIÓN
    }

    private void cargarDocumentos()
    {
    String str = "SELECT Descripcion,Vista,Descarga FROM documentos";
    SqlCommand comando = new SqlCommand(str, Conexion);
    Conexion.Open();
    comando.ExecuteNonQuery();
    SqlDataAdapter Da = new SqlDataAdapter();
    Da.SelectCommand = comando;
    DataSet Ds = new DataSet();
    Da.Fill(Ds, "Descripcion,Vista,Descarga");
    gridDocumentos.DataSource = Ds;
    gridDocumentos.DataBind();
    Conexion.Close();
    }


    private void definirColumnasDocumentos()
    {
    gridDocumentos.Columns.Clear();
    BoundField columna = new BoundField();
    columna.HeaderText = "Vista";
    columna.DataField = "Vista";
    gridDocumentos.Columns.Add(columna);
    }
    }

    Al momento de hacer esto, tal y como está la tabla de SQL así me la muestra, agrego la columna pero me la agrega al inicio, lo que quiero hacer es:

    1. Agregar la columna al final para que sea mostrada como linkfield
    2. No mostrar las columnas Vista y Descarga, ya que esas las quiero convertir a linkfield

    Te comento que me marca error al querer hacerlo como tú, ya que solo acepta listas, podrías ayudarme.

    Gracias de antemano

    ResponderEliminar
  50. hola Aaron

    recuerda que tienes que definir la propiedad

    gridDocumentos.AutoGenerateColumns = false;

    con eso si defines las columnas solo se mostraran estas y no se generarn las automaticas

    saludos

    ResponderEliminar
  51. Hola Leandro.

    El articulo es bastante útil pero tengo un problema y es que cuando cambio la página del gridView se me pierden los datos de la columna.

    Muchas Gracias

    ResponderEliminar
  52. hola ritoscue

    es que los datos debes conservarlos cuando cambias de pagina

    [ASP.NET] Mantener información al cambiar de pagina

    saludos

    ResponderEliminar
  53. Hola Leandro.
    Tengo una consulta.
    Yo cargo los datos de una base de datos y no de una lista. Todo parece que funciona perfectamente hasta que intento buscar el control en el evento Row_Updating,
    y lo que he observado es que me desaparecen todas las celdas del gridview, aunque en pantalla tengo la información, si consulto e.row.cells esta a 1,. Como te comento yo tanto las cabeceras como los datos los cargo dinámicamente de la base de datos.
    Sabrías porque puede ser??

    Muchas Gracias.
    Saludos

    ResponderEliminar
  54. Leandro Buen día
    Implemente tu ejemplo para crear un control de usuario pero necesito manipular el texto que se inserta en los TextBox que se crearon en la clase GridViewHeaderTemplate : ITemplate como puedo pasar ese valor al CodeBehind del control intente por medio de variables de Sesion y creando métodos públicos pero no me ha funcionado me puedes sugerir algo?.
    Saludos

    ResponderEliminar
  55. hola OscarMC

    si es una columna de template deberias usar el FindControl() para localizar el control que esta dentro de la row,
    como ser

    TextBox txt = row.FindControl("TextBox1") as TextBox;

    conociendo la row podrias buscar el control que contiene y define la columna de template

    saludos

    ResponderEliminar
  56. Hola!! primero que nada muchas gracias por este post, la información es muy buena y me ha ayudado mucho, pero tengo una duda, me gustaría saber como hace la recuperación de los datos que ingresa el cliente, ya que al hacer el postback en mi caso pierdo esa información, de ante mano muchas gracias.

    ResponderEliminar
  57. Disculpa tengo una duda, checke el código y no entiendo en que parte declaraste la variable Session["Datos"],
    el programa ya no se puede descargar ojala me puedas orientar.

    ResponderEliminar
  58. hola Sakurita

    actualice la publicacion del codigo

    saludos

    ResponderEliminar
  59. hola Jackeline

    si ese es uno de los principales problemas cuando se genera codigo de forma dinamica

    una forma simple es tomar el dato directo del objeto Request
    aunque tambien podrias evaluar aplicar la tecnica
    Cómo: Crear dinámicamente controles en ASP.NET utilizando .NET Visual C#
    como veras en cada request los controles dinamics debes volver a crearlos para poder accederlos

    saludos

    ResponderEliminar
  60. Hola Leandro.
    Estoy implementado un gridView con columnas checkbox dinámicas siguiendo el patrón de diseño que has colgado en la web.

    Se generan perfectamente.
    Dejo pulsar varios checkbox y mi intención es controlar las que han pulsado y registrarlo.

    El problema viene que cuando ejecuto el método FindControl, tal como tu comentas, no me encuentra los controles checkbox, solo devuelve null, o sea que no lo encuentra.

    He Observado en tu ejemplo que el orden de eventos es:
    1-Page_Init
    2-evento InstantiateIn de la plantilla checkbox
    3-Page_load.
    y asi veo que funciona.

    Pero en mi programa el orden de los eventos es:
    1-Page_Init
    2-Page_load.
    y no se produce el evento InstantiateIn por lo que no se carga bien el gridView y luego no funciona FindControl.
    ¿Alguna sugerencia?

    ResponderEliminar
  61. Hola Leandro.

    Como puedo localizar las columnas ya que no me funciona el método FindControl.
    ¿Hay alguna otra forma? Porque, si recorro el grid me indica que tengo 4 registros, pero no puedo localizar los checkbox utilizando FindControl.

    ResponderEliminar
  62. Estimado, estoy utilizando tu código el cual funciona muy bien para crear el GridView pero al momento de recuperar el dato no me lo trae siempre en NULL

    string FECHAENTREGA = (row.FindControl("txtFECHAENTREGA") as TextBox).Text;

    El GV lo cargo asi:

    DefinirColumnas((DataTable)Session["Table"]);

    GridView1.DataSource = Session["Table"];
    GridView1.DataBind();

    Y definir columnas lo arme de este modo.

    GridView1.Columns.Clear();

    for (int i = 0; i < 7; i++)
    {
    TemplateField temp = new TemplateField
    {
    HeaderTemplate = new GridViewHeaderTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper()),
    ItemTemplate = new GridViewItemTemplate(table.Columns[i].ToString().Trim().ToUpper())
    };
    GridView1.Columns.Add(temp);
    }

    //Columnas dinamicas
    for (int i = 7; i < table.Columns.Count - 3; i++)
    {
    TemplateField temp = new TemplateField
    {
    HeaderTemplate = new GridViewHeaderTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper()),
    ItemTemplate = new GridViewItemTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper()),
    EditItemTemplate = new GridViewEditTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper())
    };
    GridView1.Columns.Add(temp);
    }

    for (int i = table.Columns.Count - 3; i < table.Columns.Count; i++)
    {
    TemplateField temp = new TemplateField
    {
    HeaderTemplate = new GridViewHeaderTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper()),
    ItemTemplate = new GridViewItemTemplate(table.Columns[i].ToString().ToString().Trim().ToUpper())
    };
    GridView1.Columns.Add(temp);
    }


    Donde puedo adjuntar lo que personalice para que tengas más detalle?

    ResponderEliminar
  63. buenas tardes Alex, tengo una consulta, me encuentro realizando gridview de forma dinamica, sin embargo cuando agrego la opcion de paginado y agrego el evento pageindexchanging , al seleccionar un boton del paginado el evento no funciona tendras algun ejemplo de como realizar un griview con paginado de forma dinamica? de antemano muchas gracias quedo pendiente de tus publicaciones

    ResponderEliminar