sábado, 20 de marzo de 2010

[DataGridView] - ComboBox y evento SelectedIndexChanged

 

Introducción


Uno de los problemas al trabajar con el DataGridView y los combos en las celdas, es que no hay un eventos preciso que sea lanzado al cambiar la selección por parte del usuario.

El evento CellValueChanged se podría decir que es el mas cercano a utilizar, pero este solo se produce cuando la celda se deja de editar, o sea hay que salir de la edición de la celda, y además haber cambiado el ítem seleccionado para que el evento se produzca.

Es por este punto que este articulo explicara como adjuntar el combo definido en una columna de tipo DataGridViewComboBoxColumn, al evento SelectedIndexChanged, el cual de forma estándar no esta disponible en la grilla.

 

Planteo del problema


Se dispone de una grilla, la cual presenta un combo en una de sus columnas, y un check que habilita la selección de la lista de productos.

El usuario al cambiar la selección del combo, de forma automática el sistema debería marcarse el checkbox en la misma fila en edición.

Primer planteo de solución


Para resolver el problema serán necesarios dos eventos:

- EditingControlShowing, el cual se lanza cuando la celda entre en estado de edición

- SelectedIndexChanged, el cual será asignado al control combo de la celda que se este editando

 

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    DataGridViewComboBoxEditingControl dgvCombo = e.Control as DataGridViewComboBoxEditingControl;  
  
    if (dgvCombo != null)            
    {   
        //
        // se remueve el handler previo que pudiera tener asociado, a causa ediciones previas de la celda
        // evitando asi que se ejecuten varias veces el evento
        //
        dgvCombo.SelectedIndexChanged -= new  EventHandler(dvgCombo_SelectedIndexChanged);                
                   
        dgvCombo.SelectedIndexChanged += new EventHandler(dvgCombo_SelectedIndexChanged);            
    }

}

private void dvgCombo_SelectedIndexChanged(object sender, EventArgs e) 
{
    //
    // se recupera el valor del combo
    // a modo de ejemplo se escribe en consola el valor seleccionado
    //
    ComboBox combo = sender as ComboBox;

    Console.WriteLine(combo.SelectedValue);

    //
    // se accede a la fila actual, para trabajr con otor de sus campos
    // en este caso se marca el check si se cambia la seleccion
    //
    DataGridViewRow row = dataGridView1.CurrentRow; 

    DataGridViewCheckBoxCell cell = row.Cells["Seleccionado"] as DataGridViewCheckBoxCell;
    cell.Value = true;
}

Aquí hay algunos puntos a detallar:

- Como se observa en el evento EditingControlShowing, este tiene un argumento en el evento que permite tomar que control esta siendo editado, puntualmente el e.Control, el cual puede ser convertido a un tipo especifico se quiere trabajar, en este caso el combobox, cualquier otra celda no será del mismo tipo por lo tanto la conversión devolverá null.

Vale aclarar que en este caso usar esta línea:

DataGridViewComboBoxEditingControl dgvCombo = e.Control as DataGridViewComboBoxEditingControl; 

o esta otra:

ComboBox dgvCombo = e.Control as ComboBox;

es indiferente, con ambas funciona correctamente.

- Seguramente se preguntaran porque se esta realizando la desasignación del evento, cuando en la línea siguiente se vuelve adjunta. Esto básicamente se realiza porque si en varias oportunidades es editada la misma celda, en cada ingreso al evento se asignaría un nuevo handler, o sea no es pisado el previo o existente, provocando que se lance mas de una vez el mismo evento, lo cual no es el efecto deseado.

- En este ejemplo no se hizo, pero podría haberse preguntado si el control en edición es del tipo ComboBox, mediante el agregado de if, y el uso del is, para luego en caso de ser afirmativo en ese caso si convertir al tipo necesario.

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
 {
     if (e.Control is ComboBox)            
     {
         DataGridViewComboBoxEditingControl dgvCombo = e.Control as DataGridViewComboBoxEditingControl;  
         
         //
         // se remueve el handler previo que pudiera tener asociado, a causa ediciones previas de la celda
         // evitando asi que se ejecuten varias veces el evento
         //
         dgvCombo.SelectedIndexChanged -= new  EventHandler(dvgCombo_SelectedIndexChanged);                
                    
         dgvCombo.SelectedIndexChanged += new EventHandler(dvgCombo_SelectedIndexChanged);            
     }

 }

 

[C#]
[VB.NET]

 

Problema detectado en la primer solución


Si bien el ejemplo anterior funciona correctamente a primera vista, hay un efecto que se puede llegar a manifestarse, el cual no es nada deseable.

Resulta que en ciertas ocasiones luego de haber editado una de las celdas del combo y seleccionado un ítem, esta funciono correctamente y marco el check de la fila.

Pero al editar otra celda en una fila distinta, sin haber cambiado opción alguna, se dispara el evento del combo, marcando el check, cuando no debería hacerlo en ese momento, ya que no hubo cambio de selección alguna.

Esto se debe a que el combo queda con el evento asignado, y lo lanza cuando entra en edición.

 

Segundo Planteo de solución


Este escenario, si bien resuelve el efecto en la selección descripto en los pasos previo, tiene un punto no tan bonito en el código, ya que debe conservar el control que se esta editando de forma global al formulario.

Básicamente la resolución del problema es realizada mediante la quita del evento del combo cuando se deja de editar la celda, para lo cual se agrega el evento CellEndEdit.

 

DataGridViewComboBoxEditingControl dgvCombo;

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    dgvCombo = e.Control as DataGridViewComboBoxEditingControl;  
  
    if (dgvCombo != null)            
    {   
        dgvCombo.SelectedIndexChanged += new EventHandler(dvgCombo_SelectedIndexChanged);            
    }

}

private void dvgCombo_SelectedIndexChanged(object sender, EventArgs e) 
{
    //
    // se recupera el valor del combo
    // a modo de ejemplo se escribe en consola el valor seleccionado
    //
    ComboBox combo = sender as ComboBox;

    Console.WriteLine(combo.SelectedValue);

    //
    // se accede a la fila actual, para trabajr con otor de sus campos
    // en este caso se marca el check si se cambia la seleccion
    //
    DataGridViewRow row = dataGridView1.CurrentRow; 

    DataGridViewCheckBoxCell cell = row.Cells["Seleccionado"] as DataGridViewCheckBoxCell;
    cell.Value = true;
}

private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{

    if (dgvCombo != null)
        dgvCombo.SelectedIndexChanged -= new EventHandler(dvgCombo_SelectedIndexChanged);                

}

Puntos a remarcar:

- Se debe conservar el control combobox editado de forma global del formulario, de esta forma al terminar la edición, poder remover el evento.

Este problema se presenta ya que no existe algún otro evento en la grilla, en donde su argumento devuelva el control que lo genera, de la misma forma en que lo hace el evento EditingControlShowing, con su argumento e.Control

- En el evento EditingControlShowing solo hace falta agregar el handler al evento, ya que la remoción se realiza en esta oportunidad cuando es terminada la edición en el evento CellEndEdit

 

[C#]
[VB.NET]

33 comentarios:

Luys dijo...

Hola Buen dia.

Ya segui el tutorial de este post y me funciona a madias. Si no me equivoco lo que me hace falta es cargar el evento CellEndEdit, y honestamente no tengo idea de como mandarlo llamar. Ojala puedas ayudarme. Te dejo mi correo luys.carreon@hotmail.com y sistemas.programacion@brantano.com.mx

Leandro Tuttini dijo...

hola Luys

el CellEndEdit es un evento, solo lo defines en el datagridview

seleccionas el grid, presionas F4 para ver sus propiedades y en el solution explorer veras un icono con forma de rayo amarillo, si lo presionas veras los eventos del datagridview, alli tendrias que tener el CellEndEdit

saludos

Jorge dijo...

From Luys:

Hola Leandro.

Muy buen post, me ha sido de gran ayuda. Tuve el mismo problema que Luys y tambien ya quedó resuelto.

Pero tengo otra consulta. Te planteo el problema: Tengo un formulario (aplicacion de Windows) con un DataGridView, este a su ves tiene multiples columnas, algunas son DataGridViewTextBoxColumn y otras DataGridViewComboBoxColumn. Mi objetivo es llenar el DataGridView con datos q tomo de una BD. El problema surge cuando tengo q asignar la informacion recuperada de la consulta al DataGridView especificamente a las columnas DataGridViewComboBox.

Como punto extra: La coleccion de cada una de las columnas las cargo con DataTable's en consultas previas, en todo caso lo unico que necesito hacer es indicarle a la aplicacion cuál es el valor que quiero que seleccione en cada DataGridViewComboBoxCell.

De antemano muchas gracias!

Shark87 dijo...

Muchisimas Gracias me funciono el 2do ejemplo a la perfección! =D

Jonathan Muñoz Solano dijo...

Hola Leaondro, tengo un problema, tengo una grilla que esta en lazada a una base de datos, y con una columna de checkBox, el problema que me surge es que no puedo marcar los checkbox, a q se debe y como podria solucionarlo?

Leandro Tuttini dijo...

hola Jonathan

has verificado que el grid no tenga la propeidad ReadOnly en true

si el control esta como solo lectura no podras modificar las celdas

saludos

Don Gabo dijo...

Estimado junto con agradecer tu ayuda, estoy topando en algo, tengo un gridview con un combobox en la ultima columna, con la alternativa S o N (si o no), cuando seleciono una opcion,sino pierde el foco el combobox el valor no pasa a la celda no actualizando el valor de esta, y la verdad no se como controlar esto, agradecería alguna ayuda.

Leandro Tuttini dijo...

hola Don Gabo

no entendi, como es eso que no se pasa el valor a la celda, si el combo esta dentro de la misma celda del gridview

estas definiendo el grid en modo edicion para que muestre el combo y al aceptar vuelve a mostrar un label ?

como aqui

[ASP.NET][GridView] Edición usando DropDownList

saludos

Don Gabo dijo...

Leandro, gracias por responder, el tema es como automatizar cuando dices "al aceptar", ese enter,recien lo hice mediante un sendkeys y me funciono, habra otra alternartiva, use tu ejemplo.

Leandro Tuttini dijo...

hola Don Gabo

la verdad sigo sin entender
por lo que veo usa un datagridview, y no un gridview (el cual es un control de asp.net)

no logro comprender que es eso del "al aceptar" que comentas, porque seleccionar un item del combo no lanza ningun mensaje que se deba aceptar

saludos

GuiPulk dijo...

Que tal... por favor necesito ayuda, en ejecucion tengo mi datagridview con una celda que posee un combo box de dos items, pero al intentar seleccionar se me detiene la ejecucion... Gracias por su ayuda...

Leandro Tuttini dijo...

hola GuiPulk

creo que deberias aportar algo mas de info sobre el problema, el "detenerse" la aplicacion la verdad no me indica mucho

o sea cuando se detiene es por algun error? pudes poner algun try..catch en el codigo para poder atrapar el problema y ver cual es el mensaje

saludos

Federico Marquez R dijo...

Saludos Leandro,

He estado siguiendo todas las indicaciones que me han hecho por este foro y he logrado avanzar poco a poco en mi proyecto. Hace algun momento me encontre con tu blog y me parecio bastante completo, en especial el articulo siguiente:

[DataGridView] - Parte 6 - ComboBox y evento SelectedIndexChanged de fecha sábado, 20 de marzo de 2010.

He estado leyendo atentativamente tu articulo y algo parecido es lo que necesito hacer, en otras palabras necesito crear una GridVIew (o algo por estilo) donde pueda insertar uno (o varios) comboBox que me permitan escoger entre diferentes instrumentos. La DataGrid o Gridview (o lo que tu me recomiendes) esta compuesta por varias columnas. EN la primera columna tendria los combobox donde seleccionaria los instrumentos y algunos parametros. En las columnas siguientes se visualizarian (solo texto) los parametros e instrumentos escogidos en la combobox.

Desde tu blog no pude descargar el programa en VB.NET. Podrias decirme como descargarlo ya que es en este lenguaje en el que estoy programando mi aplicacion. Como hago para suscribirme a tu blog.

Te agradezco de antemano tu ayuda.

Yo estoy realizando un proyecto final de carrera y es por ello que necesito avanzar en esto.

Gracias.

Leandro Tuttini dijo...

hola Federico

que tipo de desarrollo estas realizando?, porque el gridview es un control de asp.net, pero el articulo trata el grid de winforms

saludos

Federico Marquez R dijo...

Eso es correcto, es un proyecto Windows Application.



Gracias

Leandro Tuttini dijo...

hola Federico

quizas buscas algo como esto

[DataGridView] – Parte 4 - Uso del DataGridViewComboBoxColumn

analiza la seccion: "3 – Realizar una operación al cambiar la selección del combo"

por lo que planteas es lo que necesitas

saludos

Julio César Hernández Ortega dijo...

Hola, que tal...seguí el tutorial y todo va bien...tengo duda de como puedo obtener el id del elemento que selecciono del combobox ya que estoy guardando todo lo que pongo en la grilla a una base de datos y no logro obtener el id de cada combobox para guardarlo..saludos

Leandro Tuttini dijo...

hola Julio

pero el id del item del combo en la celda deberia venir el la propiedad Value

o sea cuando tomas el Value de la celda se recupera el id, eso si vas a grabar accediendo a la celdas

si la idea es tomar el valor desde el evento del combo deberias usar el SelectedValue, pero recuerda definir el ValueMember para poder usar esa propiedad

saludos

Federico Marquez R dijo...

Que tal Leandro,
Tengo un inconveniente con una serializacion que estoy realizando. Quisiera saber si podrias darme algun consejo al respecto. El contenido de mi pregunta se encuentra en el sitio:
http://social.msdn.microsoft.com/Forums/es-ES/3a074b38-ca7f-4398-b47f-7c2128ba4d04/problema-con-serializacion-xml-aplicacion-wpf

Disculpa que te realize esta pregunta por tu blog, pero no encontre manera de enviarte un mensaje directamente desde MSDN.
Espero alguna sugerencia.
Muchas gracias.

Leandro Tuttini dijo...

hola Federico

respondi en el foro

saludos

Nelson Jimmy Cusi CCama dijo...

hola leandro vi tus ejercicios y revice tu ejemplo que por cierto es muy interesante ver los efectos que pueden tener los eventos, quiero consultarte sobre un tema similar.

tengo un datagridview el cual tiene el evento CellFormating este me permite formatear ciertas formulas que yo ingreso y mostrar su resultado me funciona bien. ya que al hacer click me muestrar la formula aritmetica y al salir de la celda queda el resultado de esa formula, pero le acabo de cambiar una columna por un DataGridViewComboBox, este esta enlazado con la Base de Datos. en el DisplayMember Cargo los nombres que quiero mostrar , y en ValueMember el codigo, El problema es que al selecionar un Item(nombre) del DataGridViewComboBox establese el codigo mas no el nombre que quiero mostrar me espero me pueda ayudar.. porfavor..

Leandro Tuttini dijo...

hola Nelson

no entendi al relacion entre el CellFormatting y la columna de tipo combo, porque hasta donde imagino esta columna deberias evitarla en el Cellformatting

la columna combo deberia tener un campo que defina el DatapropertyName y asi poder determinar que valor seleccionar cuando asignes el Datasource

por supuesto la propiedad que definas en el DatapropertyName deberia coincidir con los datoa que usaste para cargar los items del combo

a donde voy es que el Cellformatting en este caso no aplicaria, deberias validar que la columna es la del combo y saltearla

saludos

vriks87 dijo...

exactamente tengo un problema similar tengo una grilla que en su columna 1 debe de tener un campo autocompletado ese ya lo tengo y en la columna 4 el usuario debe de escribir solo numero el problema esta en que al asociar los eventos aunque los 2 controles tienen nombre diferente me toma los mismo valores este es mi codigo:

claveProducto = CType(e.Control, TextBox)
cantidad = CType(e.Control, TextBox)
'dando formato a la celda que debe de mostrar el contro autocompletar
claveProducto.AutoCompleteMode = AutoCompleteMode.SuggestAppend
claveProducto.AutoCompleteSource = AutoCompleteSource.CustomSource
claveProducto.AutoCompleteCustomSource = autocompletarProductos
claveProducto.CharacterCasing = CharacterCasing.Upper
AddHandler claveProducto.KeyDown, AddressOf claveProducto_keyDown

dgvCompras.CurrentRow.Cells(0).Value = claveProducto

' estableciendo el formato para la celda de cantidad unidamente debe de admitir numeros
AddHandler cantidad.KeyPress, AddressOf cantidad_keypress
dgvCompras.CurrentRow.Cells(4).Value = cantidad
dgvCompras.CurrentRow.Cells(4).Value = ""

Abel Deltell dijo...

Hola Leandro siguiendo tu consejo de visitar este tutorial te comento que tengo un problema.

Al seleccionar un dato del combobox (una referencia de producto) se ejecuta bien pero intento capturar en ese mismo el valor del seleccionado y es null por lo que al realizar la consulta a la base de datos no devuelve nada.

Entonces para que me escriba el pvp de ese producto y la cantidad que hay tengo que volver a entrar en modo edición del combobox y ahora si funciona.

Como hago para solucionar este problema? (el check lo hace al instante de entrar en modo edición, pero el resto no).

Tengo el mismo codigo que en la 2 parte esta del tutorial y añado lo siguiente

ComboBox combo = sender as ComboBox;
DataGridViewRow row = dataGridView1.CurrentRow;
DataGridViewCheckBoxCell cell = row.Cells["Seleccionado"] as DataGridViewCheckBoxCell;
cell.Value = true;

//

string descri;
int precioPublico, desc, cant;

string refer = Convert.ToString(row.Cells[1].Value);
DataGridViewComboBoxCell comboCell = row.Cells["comboRef"] as DataGridViewComboBoxCell;


int posicionFila = Convert.ToInt32(row.Index);
//EMPEZAMOS
conexion = new SQLiteConnection("Data Source=NeumaticosMarino.sqlite;Version=3;New=False;Compress=True;");
conexion.Open();

MessageBox.Show("Que vale refer = '" + refer + "'", "Registro");

//Realizamos la consulta con la referencia seleccionada
string consulta1 = "select * from producto where Referencia = '" + refer + "'";

//Creamos el objeto cmd y lo ejecutamos con el adaptador
SQLiteCommand cmd = new SQLiteCommand(consulta1, conexion);
SQLiteDataAdapter adaptador = new SQLiteDataAdapter(cmd);
//Creamos un objeto DataTable para guardar la fila obtenida
DataTable dt = new DataTable();
adaptador.Fill(dt);
//Creamos una fila virtual para guardar los datos de la consulta y poder extraerlos

if (dt.Rows.Count > 0)
{
DataRow fila = dt.Rows[0];
descri = Convert.ToString(fila["Descripcion"]);
precioPublico = Convert.ToInt32(fila["PVP"]);
desc = Convert.ToInt32(fila["Descuento"]);
cant = Convert.ToInt32(fila["Cantidad"]);

row.Cells[1].Value = descri;
row.Cells[2].Value = precioPublico;
row.Cells[3].Value = desc;
row.Cells[4].Value = cant;

}
else
MessageBox.Show("No Existe", "Registro");
conexion.Close();

Explicame donde puedo meter este codigo.
Un saludo.

Leandro Tuttini dijo...

hola Abel

la verdad no creo haber entendido el problema mas que nada porque no veo en que contexto estas trabajando
ese codigo en que evento lo defines ?

ademas si haces esto
string refer = Convert.ToString(row.Cells[1].Value);
o sea tomas el valor de la celda 1, porque despues veo que usas
row.Cells[1].Value = descri;
o sea no tinees que volver asignar la celda de la cual extraes para definir el parametro de la query

saludos

Abel Deltell dijo...

Hola Leandro, es verdad como me indicas estaba cometiendo un error ahí, pero el problema sigue siendo el mismo.
Bueno me intento explicar mejor, te subo el código de la clase a mega:

https://mega.co.nz/#!TRwWHQIZ!PdiA23eUGHTlif17L20saXPrv7QkE9FP7mbiXm7cums

/------------------------------------------------------------/

Te comento, abro el formulario de presupuesto y me carga un datagridview vacio con 1 linea para seleccionar el comboBox (img1). Una vez selecciono la referencia del comboBox (ejmplo 123) salta el messagebox para ver que valor obtiene, y este me muestra que vale '' (img2) y como puedes observar, marca el checkbox.

Bueno, acepto los messagebox que saltan y me crea una 2 linea (img3), que como puedes observar no muestra nada, osea no ha rellenado ningun campo. Al parecer sigue en modo editar celda por lo que me salgo de esta celda pulsando otra cualquiera.
Yo lo que pretendo es, que al seleccionar el elemento del comboBox la consulta a la base de datos recupere esos datos y los carge en las celdas por eso uso row.Cells[posicion].Value = variables;

Eso si, lo hace correctamente (carga las variables en las celdas) cuando vuelvo a situarme en referencia, despues de haber salido del evento cellEndEdit como puedes observar en las img4, img5 y img6

A ver si sabes donde estoy fallando jeje que yo no se que usar ya para no fallar. Si intento usar otro evento como cellValueChanged no llego ni a entrar al formulario de presupuesto me "peta"

Adjunto Imagenes:

Img1

Img2

Img3

Img4

Img5

Img6

Un saldo y muchas grácias, eres un genio con estos temas ;)

Abel Deltell dijo...

Hola Leandro, añado una duda más que no se donde exponerla.

Yo tengo el programa creado con el framework 4.5.1 y usando sqlite. Si cogo mi proyecto y voy a un pc que no dispone de SQlite no funciona el programa.
Como soluciono este problema? como puedo hacer un instalador que instale en la máquina del cliente los archivos necesarios como actualizacion del framework y sqlite?

Un saludo

Leandro Tuttini dijo...

hola Abel

cuando usas esta linea
row.Cells[posicion].Value = variables;
como determinar la posicion ? o sea, usas el CurrentRow

porque entiendo que quizas no cargue nada porque en el messagebox de la img2 no aparece ningun valor, recuerda que hay eventos que no reaccionan hasta que no termines de editar la celda
estas usando el evento SelectedIndexChanged del combo que esta en el grid?

entiendo que el sqlite solo requiere que junto al .exe pongas la dll de la libreria de ado.net

saludos

Abel Deltell dijo...

Hola Leandro, te deje el codigo en el enlace de Mega
enlace No entiendo lo que me quieres decir.

Yo estoy usando los mismos eventos que explicas en este tutorial.

Por cierto creo que lo he arreglado.

En el evento CellEndEdit despues de usar
private void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
if (dgvCombo != null)
{
dgvCombo.SelectedIndexChanged -= new EventHandler(dvgCombo_SelectedIndexChanged);

Aqui uso mi código para seleccionar la row seleccionada y cargar los datos}


Un saludo y grácias por la ayuda.
PD: lo del sqlite lo probaré en poder.

Leandro Tuttini dijo...

hola Abel

el evento CellEndEdit es un buen evento para tomar la seleccion de una celda

pero no necesitas lo del EventHandler en el SelectedIndexChanged del combo ya que podrias tomar directo el Value de la celda para tomar la seleccion

si usas el SelectedIndexChanged se asocia en el evento EditingControlShowing

saludos

ronny alvarado dijo...

hola Leandro
me preguntaba si me podrias ayudar a como llamar esta linea de código
que pusiste aqui.
DataGridViewComboBoxEditingControl dgvCombo = e.Control as DataGridViewComboBoxEditingControl;
04.

es que el ejemplo en VB no funciona, no puedo descargarlo y deverdad seria de mucha ayuda

Leandro Tuttini dijo...

hola ronny

actualice los link del codigo asi puedes descargar los ejemplos

saludos

ronny alvarado dijo...

Hola Leandro, Muchisimas gracias!!
me sirvio bastante el ejemplo que hiciste.
saludos.