sábado, 27 de marzo de 2010

[WinForms] ComboBox Anidados

 

Introducción


En este articulo se mostrara un ejemplo concreto de como trabajar con combobox que deben cargarse en forma anidada, o sea cuando el valor seleccionado de uno es usado como filtro en los datos del siguiente.

Este vendría a ser la representación en versión Winforms, del articulo anterior en asp.net:

[ASP.NET] DropDownList anidados

Hay ciertos detalles que en un desarrollo winforms hay que tener en cuenta, como por ejemplo la asignación de los handler de los eventos.

Para la demostración se utilizara la misma capa de datos usada en el ejemplo en asp.net

 

Cascada de Eventos


Uno de los principales punto que hay que analizar es como trabaja cada evento al cambiar la selección del usuario.

private void Form1_Load(object sender, EventArgs e)
{
    LoadComboArtist();
}

private void cbArtist_SelectionChangeCommitted(object sender, EventArgs e)
{
    int ArtistId = Convert.ToInt32(cbArtist.SelectedValue);

    LoadComboAlbum(ArtistId);
}

private void cbAlbum_SelectionChangeCommitted(object sender, EventArgs e)
{
    int AlbumId = Convert.ToInt32(cbAlbum.SelectedValue);

    LoadComboTrack(AlbumId);
}

private void cbTrack_SelectionChangeCommitted(object sender, EventArgs e)
{
    int TrackId = Convert.ToInt32(cbTrack.SelectedValue);

    LoadGridViewCustomer(TrackId);
}

El código deja bien claro como un evento invoca el método encargado de cargar los ítems combo siguiente.

private void LoadComboArtist()
{
    cbArtist.DataSource = ChinookDAL.GellAllArtist();
    cbArtist.DisplayMember = "Name";
    cbArtist.ValueMember = "ArtistId";

    if (cbArtist.Items.Count != 0)
    {
        int ArtistId = Convert.ToInt32(cbArtist.SelectedValue);

        LoadComboAlbum(ArtistId);
    }
    else
    {
        cbAlbum.DataSource = null; 
        cbTrack.DataSource = null; 
        dgvCustomer.DataSource = null;
    }
    
}

private void LoadComboAlbum(int ArtistId)
{
    cbAlbum.DataSource = ChinookDAL.GellAlbumByArtist(ArtistId);
    cbAlbum.DisplayMember = "Title";
    cbAlbum.ValueMember = "AlbumId";


    if (cbAlbum.Items.Count != 0)
    {
        int AlbumId = Convert.ToInt32(cbAlbum.SelectedValue);

        LoadComboTrack(AlbumId);
    }
    else
    {
        cbTrack.DataSource = null; 
        dgvCustomer.DataSource = null;
    }

}

private void LoadComboTrack(int AlbumId)
{
    cbTrack.DataSource = ChinookDAL.GellTrackByAlbum(AlbumId);
    cbTrack.DisplayMember = "Name";
    cbTrack.ValueMember = "TrackId";

    if (cbTrack.Items.Count != 0)
    {
        int TrackId = Convert.ToInt32(cbTrack.SelectedValue);

        LoadGridViewCustomer(TrackId);
    }
    else
    {
        dgvCustomer.DataSource = null;
    }

}

Un punto muy importante a remarcar en el código presentado es el uso del evento:

SelectionChangeCommitted

este solo se ejecuta cuando una acción del usuario selecciona un ítem del combo, pero solo es una acción desde la interfaz del formulario, este no se ejecuta cuando se cambia desde código la selección del combo, lo cual si sucede si se usara el evento:

SelectedIndexChanged

 

DataGridView especificar varios campos en una celda


Un punto interesante que resultaba simple en el desarrollo en asp.net, ya que el control GridView cuenta con el uso de Eval() para poder tomar los valores bindeados a cada row, lo cual facilita la creación de celdas con datos compuestos de varias propiedades.

El mismo comportamiento puede replicarse en el DataGridView, por medio de la propiedad DataBoundItem de DataGridViewRow.

private void dgvCustomer_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
    Customer cust = dgvCustomer.Rows[e.RowIndex].DataBoundItem as Customer;

    if (dgvCustomer.Columns[e.ColumnIndex].Name == "Nombre")
        e.Value = string.Format("{0}, {1}", cust.FirstName, cust.LastName);


    if (dgvCustomer.Columns[e.ColumnIndex].Name == "Direccion")
        e.Value = string.Format("{0} - {1}, {2}", cust.Address, cust.City, cust.State);

}

A la grilla le estamos asignando una colección o lista del tipo Customer, es por eso que mediante la propiedad DataBoundItem  se puede recuperar esta entidad y utilizarla para armar el valor de la celda combinando varias propiedades de la entidad.

Cómo: Obtener acceso a objetos enlazados a filas DataGridView de formularios Windows Forms

En en el mismo evento se pregunta a que columna pertenece la celda que esta lanzando el evento y se procede a utilizar la entidad Customer bindeada para la fila del DataGridView actual.

 

Aclaraciones


La base de datos utilizada en el ejemplo fue obtenido del sitio codeplex: Chinook

Para poder ejecutar la aplicación simplemente deben tener el Sql Server Express 2008 ejecutándose en la PC local

 

[C#]
[VB.NET]

23 comentarios:

Arx dijo...

el selectedvalue al cambiarlo en codigo no activa el evento ? ... tengo ese problema ... solo el selectedindex puede entonces?

Gracias

Leandro Tuttini dijo...

hola Arx

a que evento haces referencia ?

es al SelectedChangeCommited, ese evento solo se dispara cuando hay una selecion por parte del usuario, asignacion desde codigo no lo modifica

saludos

Javier dijo...

Seguí el tutorial, pero me sale un error en linea de código y no se que sera ya que al comparar la del ejemplo con el mio son casi lo mismo, pero me sale el siguiente error:

No se puede enlazar con el nuevo miembro de presentación.
Nombre del parámetro: newDisplayMember

Leandro Tuttini dijo...

hola Javier

como es que obtienes un mensaje
Nombre del parámetro: newDisplayMember

si el DisplayMember es una propiedad no un obneto, este no se instancia con el new, hay algo raro en el codigo

saludos

Javier dijo...

Una pregunta, en el método GellTrackByAlbum para que lo usas; lo usas para que cuando yo seleccione el artista y luego el álbum se cargue las canciones asociadas a ese álbum escogido?

Gracias

Leandro Tuttini dijo...

hola Javier

exacto
como veras se usa en LoadComboTrack() y se asigna al combo cbTrack, filtrando por un AlbumId

con este se respeta la cascada que se va cargando tomando el id de la entidad relacionada para traer los datos que relaciona

saludos

luis dijo...

como se puede hacer lo mismo pero con linq to SQL, mi problema radica en el bucle while no se como se puede hacer para que muestre todos los datos el combobox.

Leandro Tuttini dijo...

hola luis

para que necesitas un while ?

si suas linq to sql tendrias que tener una lista de tu entidad que puedes vincular directo al datasource del control

usarias linq para armar la expression que filtre por la seleccion del combo relacionado, pro luego el resultado se asigna directo

saludos

luis dijo...

esto es lo que ago lo cual me funciona pero con el metodo llenarLocalidades()
lo que sucede, es que hace bien el filtrado pero en el combobox cmbLocalidad
solo me muestra una localidad por provincias suponiendo tengo la provincia bs as
y 3 localidades correspondientes a bs y solo me muestra la primera localidad
y no las demas y con todas las provincias hace lo mismo.
deberia ir un bucle que me agregue todas las ciudades pero no se como se puede hacer
disculpas soy nuevo en esto.

private void Form1_Load(object sender, EventArgs e)
{
var pro = from p in BD.PROVINCIAS
select p;

cmbProvincias.DataSource = pro;
cmbProvincias.DisplayMember = "PROVINCIA";
cmbProvincias.ValueMember = "PK_ID_PROVINCIA";
//cmbProvincias.Items.Add(pro);
llenarLocalidades();
}

private void cmbProvincias_SelectedIndexChanged(object sender, EventArgs e)
{

llenarLocalidades();

}


public void llenarLocalidades()
{
//Deberia ir un bucle?
var Loc = from l in BD.LOCALIDADES
where l.PK_ID_LOCALIDAD == Convert.ToInt32(cmbProvincias.SelectedValue)
select l;

cmbLocalidad.DataSource = Loc;
cmbLocalidad.DisplayMember = "LOCALIDAD";
}

Leandro Tuttini dijo...

hola luis

porque piensas que en llenarLocalidades() deberia ir un bucle, si alli filtras y asigna los datos al combo

lo que si veo incorrecto es que si cargas localidades filtres el campo PK_ID_LOCALIDAD, se supone que deberias alli filtrar por el valor del combo anterior relacionado

[WinForms] ComboBox Anidados

o sea es una relacion, el filtro del combo siguiente lo armas con el valor de la seleccion del anterior

saludos

luis dijo...

genial anduvo a la perfección gracias son de mucha ayuda tus respuestas y material que aportas.

tengo una duda mas si no es molestia en la misma tabla localidades tengo un campo de codigo postal "CP" y cuando selecciono el combobox cmbLocalidad quiero que se muestre en un Textbox el codigo postal lo que ago es lo siguiente pero me sale error.

public void llenarLocalidades()
{
var Loc = from l in BD.LOCALIDADES
where l.FK_ID_PROVINCIA == Convert.ToInt32(cmbProvincias.SelectedValue)
select l;

cmbLocalidad.DataSource = Loc;
cmbLocalidad.DisplayMember = "LOCALIDAD";
//cmbLocalidad.ValueMember = "PK_ID_LOCALIDAD";//aqui dedusco deberia ir el campo"CP"
//que es el que uso para asignar a txtcp pero me da error.
}
private void cmbLocalidad_SelectedIndexChanged(object sender, EventArgs e)
{

txtCP.Text = cmbLocalidad.SelectedValue.ToString();
}

Leandro Tuttini dijo...

hola luis

es correcto que debas usar CP para el ValueMemeber

quedaria
cmbLocalidad.DisplayMember = "LOCALIDAD";
cmbLocalidad.ValueMember = "CP"
cmbLocalidad.DataSource = Loc;

pero no uses el evento SelectedIndexChanged, usa el SelectedChangecommited
porque este evento si es ejecutado solo ante una seleccion del usuario

saludos

luis dijo...

un millón de gracias, me anduvo de 10. cualquier duda volveré a preguntar.

jempdulintre dijo...

Hola yo tengo una pregunta tengo un combobox que se llena con

SqlDataAdapter daDos = new SqlDataAdapter(sqlUno, conection);
DataTable dtDos = new DataTable();
daDos.Fill(dtDos);
ya tengo el dispalmember y value member
ya me muestra los datos pero lo que quiero es que en el primmer item del combobox me aparesca el tipico texto(Seleecione o un espacio enblanco) y no o eh podido lograr espero me ayuden gracias por leer este comentario.

Leandro Tuttini dijo...

hola jempdulintre

no has analiza este articulo

ComboBox - DropDownList – Opción “Todos”

alli trato el tema

saludos

Ing. Allen Walker dijo...

ChinookDAL.GellAllArtist()
ME PODRIAS EXPLICAR QUE HACE ESTA SENTENCIA? SI CHINOOKDAL ES EL NOMBRE DE LA BASE DE DATOS Y LO DEMAS

Leandro Tuttini dijo...

hola Allen

te comento que lo que alli vidualizas el la clase que implemente ala capa de acceso a datos

por supuesto lleva el mismo nombre que la db porque justamente trabaja contra esta (pero podria llevar cualqueir otro si lo necesitas), pero es la clase cuya responsabilidad sera permitir el acceso a las tabla de la db y abstraer al resto de la aplicacion de esta tarea

mantiene ordenado el codigo separando las responsabilidades


saludos

TINGO_U_NORTE dijo...

hey tio como puedo hacer para obtener los codigos de las opciones seleccionadas (check) en un UltraComboeditor y almacenarlo en una caja de texto, todo esto es el C#. saludos

Leandro Tuttini dijo...

hola

la verdad nunca he usado el UltraComboeditor, pero imagino este tiene documentacion que explique como se utiliza

no hay nada en la ayuda que se brinda del compomente, o quizas quien lo vende en la pagina tenga algun foro o mail de soporte donde consulta (aunque imagino se debera redactar en ingles la pregunta)

saludos

Bere dijo...

Hola tengo dos combobox en mi formulario, lo cuales lleno a partir de dos tablas en mi BD ,y lo hago de la siguiente forma:





Sub CargarPersonal()
Try

Dim da As New SqlDataAdapter("Select * from Personal", dbalmacen)
Dim ds As New DataSet
da.Fill(ds, "Personal")
Me.cmbprecibe.DataSource = ds.Tables("Personal")
Me.cmbprecibe.DisplayMember = "Nombre"
Me.cmbprecibe.ValueMember = "IdPersonal"
'cmbprecibe.SelectedIndex = -1
Catch ex As Exception
MsgBox(ex.Message)

End Try
End Sub

Sub CargarArea()
Try
Dim da As New SqlDataAdapter("Select * from Area", dbalmacen)
Dim ds As New DataSet
da.Fill(ds, "Area")
Me.cmbarea.DataSource = ds.Tables("Area")
Me.cmbarea.DisplayMember = "Descripcion"
Me.cmbarea.ValueMember = "IdArea"
'cmbarea.SelectedIndex = -1
Catch ex As Exception
MsgBox(ex.Message)

End Try
End Sub

Lo que quiero lograr es que al seleccionar un elemento del combobox de personal, en el de área me aparezca automaticamente a que área pertenece,

La estructura de las tablas la tengo asi:

PERSONAL

IdPersonal IdArea Nombre

1 1 victor

2 3 lupita

3 2 karen

4 4 mine

AREA

IdArea Descripcion

1 Sistemas

2 Patrimonial

3 Copiado

4 Contraloria

Realice un ejemplo y modifique el codigo que carga el personal asi:

Sub CargarPersonal()
Try
Dim da As New SqlDataAdapter("Select * from Personal Where personal.IdArea='" & cmbarea.SelectedValue & "'", dbalmacen)
Dim ds As New DataSet
da.Fill(ds, "Personal")
Me.cmbprecibe.DataSource = ds.Tables("Personal")
Me.cmbprecibe.DisplayMember = "Nombre"
Me.cmbprecibe.ValueMember = "IdPersonal"
'cmbprecibe.SelectedIndex = -1
Catch ex As Exception
MsgBox(ex.Message)

End Try
End Sub

y en el evento SelectedIndexChanged del combo area escribí las siguientes lineas:

If Not Me.cmbarea.SelectedValue.GetType Is GetType(DataRowView) Then
CargarPersonal()
End If

Estos cambios hacen que al seleccionar un elemento del cobo de areas en el de personal me aparecen los elementos correspondiente a esa area, pero yo lo necesito hacer al revés, como puedo solucionarlo gracias!!

Leandro Tuttini dijo...

hola Bere

en realidad esta pregunta hubiera estado mejor palntearla desde el foro de vb.net, mas que nada porque permite trabajar con el codigo de forma mas simple

lo que veo es que aqui
Dim da As New SqlDataAdapter("Select * from Personal Where personal.IdArea='" & cmbarea.SelectedValue & "'", dbalmacen)

no estas saudno parametros ,si analizas el ejemplo de este articulo veras que uso el Parameter del objeto command para asignar los valores de filtro a la query, eso mismo deberias hacer en este caso

al reves no puedes hacerlo si la relacion es uno a muchos del area a las personas, si lo haces al contrario solo tendrias un unico area que seria la que tiene una persona vinculada, pero eso no seria valido para cargar los items del combo de area porque seria una unica row

saludos

Wilmar Cabezas dijo...

Muchas gracias Ing. leandro.

Su ejemplo funciona excelente y el contenido del blog me ayudo bastante.

Wilmar Cabezas dijo...

Muchas gracias Ing. Leandro.