domingo, 10 de enero de 2010

Rellenar un listview con un DataTable y usar filtros.

Muchas veces se pregunta en los foros formas alternativas al datagridview de visualizar los datos en una aplicación WinForms.
Para ello, he hecho un pequeño proyecto y voy a ir detallanado su funcionamiento:
1ª Parte: Definición del Listview
Para que el Listview tenga un aspecto más adecuado a nuestras pretensiones vamos a cambiar 2 de sus propiedades:
-          View: Es la forma de represantar los datos. Vamos a establecer que sea del tipo “Details”.
-          GridLines: Cuando se encuentra en el modo “Details” se pueden ver o no las líneas. Vamos a establecer el valor a “True”
2ª Parte: Obtener los datos
Como en todos los controles de representación de datos necesitamos los datos que vamos a visualizar. Para ello, he creado una pequeña base de datos Access con una tabla Movies, con los siguientes campos:
1 ID
2 Titulo
3 Director
4 Descripcion
Podeis usar vuestras propias clases para obtener los datos, yo lo he hecho con un DataAdapter llenando una tabla.
  Function BuscarDatos(ByRef dt As DataTable, Optional ByVal filtro As String = "") As Boolean
        Dim bolResultado As Boolean = True

        Dim cmd As OleDbCommand
        Try
            If dt Is Nothing Then dt = New DataTable

            Using con As New OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=|DataDirectory|\movies.accdb;Persist Security Info=False;")
                cmd = New OleDbCommand("SELECT ID,TITULO,DIRECTOR,DESCRIPCION FROM MOVIES WHERE TITULO LIKE @FILTRO OR " & _
                                     " DIRECTOR LIKE @FILTRO OR DESCRIPCION LIKE @FILTRO", con)
                cmd.Parameters.AddWithValue("@FILTRO", "%" & filtro & "%")
                Using DA As New OleDbDataAdapter(cmd)
                    DA.Fill(dt)
                End Using
            End Using
        Catch ex As Exception
            bolResultado = False
        End Try
        Return bolResultado
    End Function
La función recibe como parámetros:
Un datatable para guardar los datos. Lo recibe por referencia.
Y de forma opcional un filtro.
La función si se ejecuta correctamente nos va a devolver “True”, en otro caso nos devolverá false.
3ª Rellenar El Listview
Una vez que tenemos nuestro Datatable relleno, es hora de mostrar los datos en el ListView, para ello, haremos uso de la siguiente función:
Function RellenarListview(ByVal dt As DataTable) As Boolean
        Dim bolResultado As Boolean = True
        Dim lstElemento As ListViewItem
        Try
            Me.lvwDatos.Items.Clear()
            Me.lvwDatos.Columns.Clear()
            For Each col As DataColumn In dt.Columns
                lvwDatos.Columns.Add(col.ColumnName, col.ColumnName)
            Next
            For Each row As DataRow In dt.Rows
                lstElemento = New ListViewItem
                lstElemento.Text = row(0).ToString()
                For intcontador As Integer = 1 To dt.Columns.Count - 1
                    lstElemento.SubItems.Add(row(intcontador).ToString())
                Next
                lvwDatos.Items.Add(lstElemento)
            Next
            Me.lvwDatos.AutoResizeColumns(ColumnHeaderAutoResizeStyle.HeaderSize)
        Catch ex As Exception
            bolResultado = False
        End Try
        Return bolResultado
    End Function
4ª Filtrar los datos.
En el ejemplo, al ser una base de datos realmente pequeña, el filtrado de datos lo hago directamente con respecto de la base de datos, pero no es la única forma de hacerlo, también lo podemos hacer a través de filtros en el propio datatable a través de la función “Select”.
En este ejemplo, he creado el botón “Filtrar” y el checkBox “Filtrado Automatico”. Ambas funcionalidades hacen que se filtre el contenido del listview, pero en distintos lugares.
El botón Filtrar, evidentemente, provoca el filtrado en el evento Click del botón, sin embargo cuando tenemos marcado el checkBox de “Filtrado automatico”, se filtran los datos según vayamos escribiendo en la caja de texto.

Espero que os haya gustado el articulo.
PD: El ejemplo está hecho en VB 2010 Beta 2, pero es completamente aplicable a vb2008 y vb2005.
PD2: Si necesitais el ejemplo en c# no dudéis en pedírmelo.


22 comentarios:

  1. Me sirvio muchas gracias!

    ResponderEliminar
  2. Gracias. No sabés cuánto busqué esto. En realidad me cuesta bastante "extraer" datos de los controles de VB 2008. Tengo 30 de programación, pero hace años que no programo y lo último fue en VB6.

    No sabés lo que me costó obtener la fecha de un control MonthCalendar. Tuve que hacer un truco de "dirty programming":
    TextBox1.Text = Mid(Me.MonthCalendar1.SelectionRange.ToString, 50, 10)
    ¡una mugre!, jaja. Pero ni idea de cómo obtener el dato.

    Tengo que publicar esto como Anonimo, pero mi nombre es Gerry. Gracias por el blog

    ResponderEliminar
  3. Soy un imbécil, pero no sé cómo llamar a las funciones. Primero puse Imports System.Data.OleDb para que me tome los objetos OleDB... pero ¿cómo llamo a las funciones?

    Mi Base se llama Seguimiento y la tabla, Clientes:

    b = BuscarDatos(SeguimientoDataSetTableAdapters.ClientesTableAdapter, "")

    Esto me da error, probé varias opciones y ninguna funciona.

    Gracias

    ResponderEliminar
  4. Gerry, mandame tu código completo y le doy un vistazo rastagar2003@gmail.com

    ResponderEliminar
  5. Muchas gracias, pero estuve viendo tu código en VB 2010 (lo convertí a VB 2008) y ahora lo entendí.

    ResponderEliminar
  6. Ahora tengo otra duda (te voy a sacar canas verdes, jeje) ¿Cómo obtengo el dato contenido en un campo de una tabla?
    Me explico:
    Tabla Clientes
    -Id (autonumérico)
    -Nombre (string)
    -Provincia (integer)

    Tabla Provincias
    -Id (autonum)
    -Nombre (string)

    Link típico de dos tablas de una base (access en este caso, llamada Seguimiento)

    Estuve "jugando" con tus rutinas y bastante bien voy, pero me gustaría obtener un dato:
    Tengo leído el registro de clientes mediante Me.ClientesTableAdapter.Fill(Me.SeguimientoDataSet.Clientes) o sea, automático, lleno una combobox con los registros de la tabla Provincias.Nombre y ahora quiero obtener el dato de Clientes.Provincia para seleccionar el ítem correspondiente de la combobox, pero no logor encontrar el comando que me traiga el dato.

    ¿Se entiende?

    ResponderEliminar
  7. Umh la verdad es que no me ha quedado muy claro :(

    ResponderEliminar
  8. Jeje.
    En una tabla (Clientes) tengo un dato numérico (Clientes.Provincia) que se linkea con otra tabla (Provincias) en la que están los nombres de las mismas.

    El link sería Clientes.Provincia -> Provincias.Id

    Lleno una Combobox con el contenido de Provincias.Nombre

    Cuando lleno el formulario con los datos del cliente (el llenado "automático que hace VB con el binding), quisiera que la combobox me mostrase el nombre correspondiente.

    Lo hice de una forma sucia: tengo un textbox oculto con el campo Clientes.Provincia y tomo ese valor para la selección en el combobox, pero eso es muy sucio y me gustaría obtener el valor directamente de la tabla de Clientes.

    ¿Se entiende?

    ResponderEliminar
  9. Prueba algo asi:
    Function BuscarId(ByVal strValorBusqueda As String) As Integer
    Dim iIndice As Integer = -1
    Dim bolEncontrado As Boolean = False
    Dim intContador As Integer = 0
    While Not bolEncontrado And intContador <= ComboBox1.Items.Count - 1
    If ComboBox1.Items(intContador).ToString = strValorBusqueda Then bolEncontrado = True : iIndice = intContador
    End While
    Return iIndice
    End Function

    ResponderEliminar
  10. Gracias, pero me parece que no me hice entender. Lo que yo no logro entender es cómo leer el valor de la base de datos.

    Olvidemos todo lo que escribí antes. Supongamos que quiero saber qué valor tiene el campo Provincia de la tabla Clientes en la base Seguimiento.

    En VB6 si mal no recuerdo hacía:

    p = Clientes.Field("Provincia").Value

    pero en VB2008 no tengo idea de cómo obtener el valor.

    Gracias por tu dedicación y buena onda.

    ResponderEliminar
  11. Hola, en vb6 estabas haciendo uso de un recordset verdad?,

    Para leer datos puedes hacer uso de :
    http://msdn.microsoft.com/es-es/library/system.data.oledb.oledbdatareader(VS.80).aspx (DataReader solo Lectura)
    http://msdn.microsoft.com/es-es/library/system.data.oledb.oledbdataadapter(VS.80).aspx (DataAdapter te va a servir para lectura y escritura...)
    Yo el ejemplo lo hago con un dataadapter y un datatable...

    tuDatatable.Rows(elIndicedelaFila).Item("tuColumna").ToString()...

    ResponderEliminar
  12. ¡Finally! Perdón por ser tan complicado explicando las cosas. Esto es lo que estaba buscando desde que conocí el VB2008 y rebuscando en manuales, ejemplos, foros...

    Si alguna vez gano algo de plata con mi programa te debo un importante porcentaje ;)

    Gracias.

    ResponderEliminar
  13. Jaja, con que sigas visitando el blog y preguntando inquietudes me doy por satifecho..!

    ResponderEliminar
  14. Te envié un mail a la dirección que me diste, pero tal vez se mezcló con el spam.

    La cosa es que me cuesta mucho meterme en la programación orientada a objetos tan "pura" o me falla algo en el cerebro, pero no doy pie con bola.

    Por ahora estoy trabajando con los Binding navigators de VB 2008. Cuando programaba en VB6 no solía usar los formularios "automáticos" de acceso a bases de datos, pero en VB2008 no sé todavía cómo hacerlo de otro modo. Además, de que no parecen malos.

    La cuestión es que mi formulario dice:

    Private Sub frmCli_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
    Me.ClientesTableAdapter.Fill(Me.SeguimientoDataSet.Clientes)
    End Sub

    Con lo cual se hace un link entre los campos del mismo y los de la base de datos. Hasta allí todo bien, pero, mi Base de Datos tiene dos tablas linkeadas así:

    Tabla Clientes

    Id --> autonumérico
    Nombre --> String
    Domicilio --> String
    Provincia --> Long integer

    Tabla Provincias

    Id --> autoinumérico
    Nombre --> String

    Por supuesto, el campo Clientes.Provincia corresponde a Provincias.Id.

    Cuando quiero representar eso en el formulario, si lo hago automáticamente tendré un TextBox de Provincia que muestre un 2 o un 3 o un 5. Lo que hago es usar un combobox (cmbProvincia) que previamente está completado con todos los nombres de todas las provincias ordenados por Provincias.Id. Con lo que si yo obtengo de Clientes.Provincia un 2 tendré que ir a la fila 1 de la combobox (la combo empeza de cero pero la tabla, de uno)

    El problema es que no logro obtener el dato de Clientes.Provincia, como no sea linkeando una textbox y leyendo el número desde allí.

    Tu sugerencia fue:

    tuDatatable.Rows(elIndicedelaFila).Item("tuColumna").ToString()...

    pero como no estoy trabajando con DataTables sino con BindingNavigator, lo que tengo es

    ClientesTableAdapter, que no tiene un campo Rows ni nada parecido.

    ¿Me explico?

    ResponderEliminar
  15. puedes enviarme tu email a mi correo
    yaakovjew@gmail.com

    ResponderEliminar
  16. Viejo eres de lo mejor codigo sencillo efectivo sin tanta vuelta
    Saludos desde Panama

    ResponderEliminar
  17. hola amigo sabes necesito un ejemplo pero en c# y igual que se pueda rellenar desde una base de datos de access

    ResponderEliminar
  18. Hola enviame un mail y te envio un ejemplo.

    ResponderEliminar
  19. como hago que un Listview almacene datos de un listbox automaticamente???

    ResponderEliminar
  20. hola maestro
    puedes ayudarme como cargo los datos de una hoja de excel en un lisview en visual studio 2010

    ResponderEliminar
  21. Muy bueeno, muchas gracias por el aporte.

    ResponderEliminar