domingo, 2 de mayo de 2010

C# - [DataGridView] DataGridViewComboBoxColumn – Variar contenido del combobox respecto a la fila

 

Introducción

El objetivo del articulo es demostrar algunos problemas que pueden surgir cuando se necesita que el ComboBox ubicado en las celdas del DataGridView, varíe su contenido de acuerdo al registro que se visualiza.

Para demostrar el problema, se ha definido un dominio del ejemplo intentara cargar una grilla de sucursales, de los cuales mostraran el listado de los teléfonos disponibles.

Por supuesto para cada sucursal los teléfonos disponibles varían, por lo tanto definir el contenido del combo como una única lista no es posible.

Carga del contenido variable en ComboBox

En el evento Load del formulario se procede a la carga de las sucursales, e inmediatamente después de esta operación se invoca a un método creado con el fin de la carga de los combos de cada registro de a grilla.

Este método recorre en un ciclo cada registro, toma el id de la sucursal que se encuentra en la celda del registro actual, y también toma la celda que representa el combo.

Con las anteriores dos selecciones procede a realizar una consulta a los datos y cargar en el combo los teléfonos de esa sucursal.

 

private void frmSucursales_Load(object sender, EventArgs e)
{
    SucursalesEntities sucursalesContext = new SucursalesEntities();

    dgvScursales.AutoGenerateColumns=false;
    dgvScursales.DataSource = sucursalesContext.Sucursales;

    //
    // Luego de bindear la grilla, cargo el contenido de cada fila
    // en el combo de telefonos para cada sucursal
    //
    LoadGrillaComboBoxTelefonos();

}

private void LoadGrillaComboBoxTelefonos()
{
    foreach (DataGridViewRow row in dgvScursales.Rows)
    {
        int idSucursal = Convert.ToInt32(row.Cells["IdSucursal"].Value);

        DataGridViewComboBoxCell comboboxCell = row.Cells["Telefonos"] as DataGridViewComboBoxCell;
        
        SucursalesEntities sucursalesContext = new SucursalesEntities();

        comboboxCell.DataSource = sucursalesContext.Telefonos.Where(x => x.IdSucursal == idSucursal);
        comboboxCell.ValueMember = "Id";
        comboboxCell.DisplayMember = "Numero";

    }
}

Algunos Comentarios

Durante la realización del ejemplo se realizaron varias pruebas, con algunos eventos que se adaptaran a la situación y evitaran tener que recorrer en un “foreach” cada registro del DataGridView.

Después de investigar un poco los únicos dos eventos que se adaptarían a este caso serian el RowsAdded y el CellFormatting, pero ambos trajeron problemas, lo que impidió su uso.

El CellFormatting, tiene como problema que se ejecuta contantemente, ya que es lanzado al redibujar la celda, y como esta operación requiere la consulta a los datos, generaba un efecto de bloqueo en la pantalla, el cual no es deseado.

El RowsAdded, si bien se ejecuta por cada fila agregada en la operación de bindeo de los datos, no había forma interna en el evento de detectar que fila se estaba agregando, ya que el argumento del evento e.RowIndex o e.RowCount, no devuelven un valor correcto.

Como conclusión se dejo de lado los eventos y se hizo uso del “foreach”, implementado esto en un método, cuya utilización se realiza luego que se bindearon los datos en el control.

 

[C#]
[VB.NET]

25 comentarios:

  1. Hola Sr. Leandro, lo hice de esta forma, en el foro de msdn publique el error que tengo ahora y de la forma que estoy intentando.

    ResponderEliminar
  2. hola kalith

    Creo haberte contestado en el foro, pero no pude encontrar el link al mensaje, asi quedaba relacionada la consulta.

    Igualmente espero haber contestado tu consulta.

    saludos

    ResponderEliminar
  3. pues aún tengo otra duda este es el link, de igual manera muchisimas gracias por tomar tiempo y ayudarme :)

    ResponderEliminar
  4. disculpe lo que pasa es q tengo un codigo de esta forma pero no me actualiza el columncombobox pero he visto q en debbuging si que pasa?

    For Each r As DataGridViewRow In Me.DataGridView1.Rows
    Dim id_pregunta As Integer = Convert.ToInt32(r.Cells("Id").Value)
    Dim comboboxCell As DataGridViewComboBoxCell = CType(r.Cells("Respuestas"), DataGridViewComboBoxCell)
    objPregunta.Id = 10 'row.Cells("Id").Value
    objPregunta.TipoPregunta = 1 'row.Cells("TipoPregunta").Value
    comboboxCell.DataSource = objPregunta.Todas_quest_ansert_x_Id
    comboboxCell.ValueMember = "RespuestaCerrada"
    comboboxCell.DisplayMember = "RespuestaCerrada"
    Next

    por su atencion gracias

    ResponderEliminar
  5. hola backspeed

    porque no defines algo como esto

    For Each r As DataGridViewRow In Me.DataGridView1.Rows

    Dim id_pregunta As Integer = Convert.ToInt32(r.Cells("Id").Value)
    Dim tipo As Integer = Convert.ToInt32(row.Cells("TipoPregunta").Value)

    Dim comboboxCell As DataGridViewComboBoxCell = CType(r.Cells("Respuestas"), DataGridViewComboBoxCell)

    comboboxCell.DataSource = Pregunta.Todas_quest_ansert_x_Id(id_pregunta, tipo)

    comboboxCell.ValueMember = "RespuestaCerrada"
    comboboxCell.DisplayMember = "RespuestaCerrada"

    Next

    O sea que el metodo Todas_quest_ansert_x_Id() reciba por parametro los filtos que aplicara para obteenr los items que se cargaran en el combo

    Ademas no necesitas crear una instancia define ese metodo como Shared asi puedes accederlo desde la clase

    saludos

    ResponderEliminar
  6. Hola Sr. Leandro Antes que nada lo Felicito por su blog me ha sido de mucha ayuda para mi proyecto de la Universidad, y bueno, queria saber si me podia ayudar con una cuestio lo que pasa es que quiero llenar un comboboxcolumn de forma variante dependiendo de la fila pero el caso es que no es desde base de datos sino es para cantidad de productos en una factura ya que cada producto debe llegar a un limite de venta segun la cantidad que aya en stock de antemano le agradezco y le deseo muchos éxitos Att. Jhonsson Córdova

    ResponderEliminar
  7. hola Jhonsson Cordova

    dices que no es una base de datos, pero el stock del producto como lo determinas ?

    o sea si es algo referente a ventas y el negocio, mas que nada cuando tiene que ver con el stock, a veces plantear restricciones de este tipo son complejas, porque no dejas que la persona que hace el pedido escriba el nuemro y al momento de confirmar la transaccion aplcias las validaciones, y en caso de alguna no pasar muestras los mensajes de problemas todos juntos para ser corregidos

    los sistemas de invetario no limitan tanto el input sino que aplican als validacion al confirmar

    saludos

    ResponderEliminar
  8. Gracias por la acotacion, ya habia pensado igual la verdad que resulto muy complejo lo que deseaba hacer pero quedo bien con un textboxcolumn y con las validaciones pertinentes se obtiene un muy buen funcional formulario gracias por la ayuda le agradezco

    ResponderEliminar
  9. hola Leandro, gracias por la ayuda. trate de implementar lo que has propuesto, pero sigo teniendo el mismo problema que te comente anteriormente: al realizar el filtrado en el combo para un concepto en particular, este filtrado me toma para todos los combos.
    consultas utilizadas en ambos procedimientos son:

    Dim consulta1 As String = "SELECT valoracion FROM VALORES_CRITERIO_PERITAJE where borrado_logico=0"

    Dim consulta2 As String = "SELECT Id_estado_mctp,descripcion FROM MOTOR_CAJA_TRANS_POTE where borrado_logico=0"

    La carga de la grilla seria asi:

    Sub SubCargarconceptos()
    Dim VAR As String = ""
    Dim conceptosss As conceptos
    Dim lectura As Data.SqlClient.SqlDataReader
    'conex.ConnectionString = NombreConexion
    Abrir()
    comandos.Connection = conex
    comandos.CommandType = CommandType.Text
    comandos.Parameters.Clear()
    comandos.CommandText = consulta2
    lectura = comandos.ExecuteReader
    cmbvaloracion.Items.Clear()
    While lectura.Read()
    conceptosss.id = CInt(lectura.Item("Id_estado_mctp"))
    conceptosss.conc = CStr(lectura.Item("descripcion"))
    array_conceptos.Add(conceptosss)
    End While
    lectura.Close()
    Cerrar()
    dgvperitaje.Rows.Add(array_conceptos.Count)
    Dim concp_valo As conceptos
    For i As Integer = 0 To array_conceptos.Count - 1
    concp_valo = array_conceptos(i)
    dgvperitaje.Rows(i).Cells(0).Value = concp_valo.id
    dgvperitaje.Rows(i).Cells(1).Value = concp_valo.conc
    'SubCargarValoracion(concp_valo.id)
    Next
    end sub

    La carga de mi combobox segun el concepto seria asi:

    Sub SubCargarValoracion(ByVal id_concep As Integer)
    Abrir()
    Dim da As New SqlClient.SqlDataAdapter("SELECT V.id_valoracion, V.valoracion FROM MOTOR_CAJA_TRANS_POTE M INNER JOIN DETALLE_MOTOR_VALORES D ON M.Id_estado_mctp = D.Id_estado_mctp INNER JOIN VALORES_CRITERIO_PERITAJE V ON D.id_valoracion = V.id_valoracion WHERE D.Id_estado_mctp =" + CStr(id_concep), conex)
    Dim ds As New DataSet
    da.Fill(ds)
    cmbvaloracion.DataSource = ds.Tables(0)
    cmbvaloracion.DisplayMember = "valoracion"
    cmbvaloracion.ValueMember = "id_valoracion"
    Cerrar()
    End Sub





    Private Sub LoadGrillaCombovaloracion()

    For Each row As DataGridViewRow In dgvperitaje.Rows

    Dim mctp As Integer = Convert.ToInt32(row.Cells("id").Value)

    Dim comboboxCell As DataGridViewComboBoxCell = TryCast(row.Cells("cmbvaloracion"), DataGridViewComboBoxCell)

    'Dim sucursalesContext As New SucursalesEntities()
    SubCargarValoracion(mctp)

    Next

    End Sub

    En el load del form yo llamo al cargar conceptos y luego llamo al LoadGrillaCombovaloracion. Espero que me puedas ayudar. Gracias.

    ResponderEliminar
  10. Como puedo enviarte una imagen de mi pantalla para que puedas visualizar bien el problema?

    ResponderEliminar
  11. Leandro aca te paso una imagen: http://img843.imageshack.us/img843/9128/pantallaperitaje.jpg

    El concepto Ruidos.. tiene como valoracion "SI" y "NO". Pero como veras todos los combos se setean con BUENO-MALO-REGULAR.

    ResponderEliminar
  12. hola Piloni

    pero si ese concepto deberia tener las opcioens si-no en el combo podrias cargarlas detectando que concepto se esta editando, tomarias la fila detectando la celda de valoracio y cargando las opciones que necesitas para ese registro

    esto lo menciono en el articulo como lograrlo a la celda si la detectas puede asignrle la info que necesites

    saludos

    ResponderEliminar
  13. Tengo un problema tengo un datagridview con N colunas ya tengo una columna con comboboxcolum cargado con una base de datos, ahora lo que se requiere es usar otras 2 columnas con comboboxcolum de igual manera llenada con bases de datos eso ya lo tengo pero lo que no tengo es que cuando seleccione en cualquier columna se ejecuta una consulta y que en las columnas de comobobox me despliegue el texto como una columna de textbox, no se como hacerlo en la cosulta yo agrego el item a las columnas comobobox pero esta mal porque ya con elelmanto tambien me deberia hacer consultas de lamisma manera perono lo hace

    ResponderEliminar
  14. hola jose

    no se si entendi del todo el planteo, pero si podria recomendarte que si vas a realizar edicion compleja en un grid no tomes ese camino, el datagridview no es un control que este prepatado para por ejemplo relacionar combos en uan linea, si es eso lo que buscas

    es conveniente seelccionar la row y editar por fuera en controles simples del formulario

    quiza hace tiempo relaiconar combos en cascada en la row del grid y no pude lograrlo

    saludos

    ResponderEliminar
  15. este es un wjwmplo wur encontre en internet

    DataGridViewComboBoxColumn cmb = new DataGridViewComboBoxColumn ();
    cmb.HeaderText = "Seleccionar datos";
    cmb.Name = "CMB";
    cmb.MaxDropDownItems = 4;
    cmb.Items.Add ("True");
    cmb.Items.Add ("Falso");
    dataGridView1.Columns.Add (cmb);

    DataGridViewComboBoxColumn cmb2 = new DataGridViewComboBoxColumn();
    cmb2.HeaderText = "Seleccionar datos";
    cmb2.Name = "CMB";
    cmb2.MaxDropDownItems = 4;
    cmb2.Items.Add("True");
    cmb2.Items.Add("Falso");
    dataGridView1.Columns.Add(cmb2);


    DataGridViewComboBoxColumn cmb3 = new DataGridViewComboBoxColumn();
    cmb3.HeaderText = "Seleccionar datos";
    cmb3.Name = "CMB";
    cmb3.MaxDropDownItems = 4;
    cmb3.Items.Add("True");
    cmb3.Items.Add("Falso");
    dataGridView1.Columns.Add(cmb3);

    es lago asi pero con conexion a base de datos es una misma tabla

    en estos ejemplos son 3 comobobox los tres debe de consultar de 3 diferentes maneras pero el resultado siempre ba ser el mismo misma tabla mismos resultados si por ejemlo

    fila = new String [] {"2", "producto 2", "2000"};
    dataGridView1.Rows.Add (fila);
    fila = new String [] {"3", "Producto 3", "3000"};
    dataGridView1.Rows.Add (fila);
    fila = new String [] {"4", "Producto 4", "4000"};

    cuando yo aga una conuslta en cada combobox me deneria desplegar estos resultados pero tambien me deberia hacer consultas nuevamente por dichos pero no me queda

    ResponderEliminar
  16. hola jose

    la verdad no se si entendi el planteo

    porque alli veo 3 columnas de tipo combo del grid
    pero la forma en que asignas lo valores no es correcta, para que la seleccion funcione debes asignar el Datasource de la columna del combo y no el Items.Add() porque despues cuando pones un valo no sabe cual seleccionar

    ademas cuando haces el Rows.Add() no veo que alli asignes ningun valor que sea true o false para que seleccione del combo

    saludos

    ResponderEliminar
  17. por alguna razon siempre que comento en tu block alguna duda, termio resolviendola gracias por contestar

    ResponderEliminar
  18. Hola muy Buenas. Estimado Leandro me gustaria saber como pagino una consulta sql la cual tiene más de 20 millones de datos, o como hacer enmascaramientoa una base de datos usando windows form C#.
    Gracias te agradesco la ayuda.

    ResponderEliminar
  19. hola

    pero de los 20 millones siempre vas a traer todos los registros, no aplicas filtros de busqueda, quizas por rango de fecha o algun otro campo para redicir la cantidad de informacion

    por lo general cuando se trata ese volumen de datos se suele proporcionar filtros que permita trabjar mejor la informacion

    es mas tambien se emepiza a pensar en usar algun Datawerehouse para realizar mineria de datos y poder extraer informacion de ese volumen de registros

    saludos

    ResponderEliminar
  20. Eso de la mineria lo estava viendo, pero la cuestion es que estoy desarrollando enmascaramiento de datos a una DB pero como es gran tamaño de datos el for y el foreach se me rebientan ya que tengo que recorrer una tabla con ciertas condiciones y aun así son demasiados datos para compararlos y realizar el enmascaramiento, y lo que se me viene a la cabeza es paginado en un formulario para que no se me caiga el winform aqui es donde estoy atascado. si tienes un ejemplo sencillo de paginar la consulta en windows form de forma sencilla y facil de enterder te agradeceria mucho. Danke Shönn ;)

    ResponderEliminar
  21. hola

    podrias paginar en la base de datos

    http://social.msdn.microsoft.com/Forums/es/vbes/thread/058fd915-7e48-4dd5-aab1-7b04b86d41a1

    http://social.msdn.microsoft.com/Forums/es/vcses/thread/f3ab62c7-1824-44f7-a4ab-c0837333793d

    saludos

    ResponderEliminar
  22. Doy click a boton de Imprimir Etiqueta para que me mande la forma donde esta el crystalreportviewer1 y al cargarme la pantalla de la impresion me pide accesar id inicio de seion y contrasela para conexion a base de datos pero mi etiquetasset.xsd no esta enlazado a mi BD.

    ResponderEliminar
  23. hola Erick

    de casualidad el reporte en algun momento lo coenctaste directo a la db, pero depsues cambiaste a sataset ? lo pregunto proque quizas aun quedo conectado por eso solicuta la informacion de login cuando lo lanzas desde codigo

    valida ademas que desde codigo asignas el datasource de forma correcta

    saludos

    ResponderEliminar
  24. Leandro!!! Un millón de gracias desde Colombia.

    ResponderEliminar