Cómo usar el control ListView en modo detalle

Publicado: 06/Oct/2002
Actualizado: 06/Oct/2002
Link al índice de controles especiales de Visual Basic


Aquí veremos, aunque sea de forma simple, cómo usar el control ListView, (en formato detalle o informe), pero espero que al menos te sirva de base para que investigues un poco más... y aprendas por tu cuenta, ya que, aunque no te lo creas, cuando investigas por tu cuenta aprendes un poco más que si todo te lo dan "hecho".
Espero que te sea de utilidad.
Sí, ya se que me he repetido con este comentario... (si has visto la página del Treeview), pero es que es verdad.


Lo que veremos en este artículo (o ejemplo sobre el ListView), será:

Con respecto a lo de clasificar el contenido según la columna pulsada, he de aclararte que el ListView por defecto clasifica siempre como si fuesen cadenas, por tanto, si tenemos los números 10, 11, 20 y 100, el orden que le daría sería el siguiente: 10, 100, 11, 20. En el código de este artículo, se clasificarán de forma correcta.
Lo mismo ocurrirá con las fechas, también se clasificarán bien.

Tal como comento en el propio código, en un principio iba a usar un código de la KB de Microsoft, en el cual se muestra cómo clasificar los elementos de un ListView (de tipo fecha), pero al final no lo he usado, por la sencilla razón de que el código es poco útil, al menos si queremos usarlo de forma genérica.
Ese código no sería válido, (salvo que hicieramos bastantes cambios y comparaciones), en el código aquí mostrado, en el cual se llena el ListView de dos formas distintas y con distinto número de columnas y además con los datos en columnas diferentes... me refiero a que pulsando en un botón se crean cuatro columnas, siendo la primera de tipo alfanumérico, la segunda de tipo fecha, la tercera y cuarta de tipo numérico (uno con enteros y otro con decimales); mientras que pulsando otro botón se crean tres columnas, siendo la primera con datos de fechas, la segunda con datos alfanuméricos y la tercera con datos numéricos con decimales.
Como te decía, usando el sistema que yo he estado usando hasta ahora, se puede clasificar sin importar cuantas columnas hay y en que orden están los datos... sólo hay que seguir unas pequeñas normas, que por otro lado en el ejemplo de la KB también habría que seguir... me refiero a que hay que indicarle de alguna forma al programa qué tipo de datos contiene cada columna, para ello uso unas constantes, (las cuales puedes ampliar o modificar a tu gusto), cuyo valor se asigna al TAG de cada columna, para indicar el tipo de datos que contiene.
También uso unas constantes para el formato que tendrán los datos, al menos si son datos de fechas o de moneda, para que indiques el que más te apetezca, cuando veas el código lo comprenderás mejor.

Primero te mostraré el aspecto del formulario y a continuación el código.
Al final hay un link al código completo de este ejemplo.
Hay que tener en cuenta que el control usado es el que se incluye con Visual Basic 6 (creo que instalado con el Service Pack 4), por tanto puede ser que necesites cambiarlo si no tienes el VB6. De hecho el que se incluye con el Visual Basic 5.0 es otro...

Este es el aspecto del formulario


Aspecto del formulario en tiempo de diseño

Este es el código de ejemplo:


'------------------------------------------------------------------------------
' Prueba de Listview                                                (06/Oct/02)
' Este código puede servir para saber cómo realizar tareas comunes con un Listview
' El tipo de control usado es el incluido en:
' Microsoft Windows Common Controls 6.0 (SP4) (MsComCtl.ocx)
'
' Tenía previsto mostrar cómo clasificar las columnas usando API y
' llamadas Callback (con AddressOf), pero el ejemplo sólo serviría para
' casos muy concretos y por tanto... pues no me vale, al menos a mí.
' Si estás interesado, te diré que el artículo de la KB es:
' HOWTO: Sort a ListView Control by Date (Q170884)
' En dicho artículo se clasifica un listview con dos columnas,
' si tenemos más columnas, como en el ejemplo que aquí muestro,
' habría que añadirle más código y además siempre deberían estar
' los tipos de datos en las posiciones usadas en dicho código...
' y como a mi el código que sólo vale para una vez no me es de utilidad...
' pues "paso" de él...
'
' Así que, el código mostrado es el que yo uso y con "pequeños" cambios
' es válido para cualquier cantidad de columnas y formatos...
' Tal como puedes comprobar pulsando en cualquiera de los dos botones
' que añaden datos de ejemplo.
'
' Ver las constantes cFormatoXXX para los formatos a aplicar.
' Sólo cambiándolos o añadiendo los que quieras,
' podrás usarlo con prácticamente todo tipo de datos.
'
' ©Guillermo 'guille' Som, 2002
'------------------------------------------------------------------------------
Option Explicit

' Constantes para el tipo de datos de cada columna del listview
Const cTexto As String = "Texto"
Const cNumero As String = "Número"
Const cMoneda As String = "Moneda"
Const cFecha As String = "Fecha"
' Los formatos a aplicar según el tipo de datos que contiene
Const cFormatoFecha As String = "dd/mm/yyyy"
Const cFormatoNumero As String = "###,###"      ' "###"
Const cFormatoMoneda As String = "###,###.00"   ' "###.00"
' La cantidad de cifras a tener en cuenta en los números
Const cCuantasCifras As Long = 20&

Private Sub chkLabelAuto_Click()
    ' Cambiar la forma de editar el texto de un nodo
    If chkLabelAuto.Value = vbChecked Then
        ListView1.LabelEdit = tvwAutomatic
    Else
        ListView1.LabelEdit = tvwManual
    End If
End Sub

Private Sub cmdAdd_Click()
    ' Añadir un nuevo elemento
    ' Los subitems se añadirán del contenido del Text4,
    ' los cuales estarán en líneas separadas, (una para cada subitem)
    Dim i As Long
    Dim aSubItems() As String
    '
    ' Creamos un array con el contenido del Text4
    aSubItems = Split(Text4, vbCrLf)
    ' para que aSubItems tenga el mismo número que los subitems
    i = ListView1.ColumnHeaders.Count - 2
    ReDim Preserve aSubItems(i)
    ' Añadimos un nuevo elemento a la lista
    With ListView1.ListItems.Add(, , Text1)
        For i = 2 To ListView1.ColumnHeaders.Count
            .SubItems(i - 1) = aSubItems(i - 2)
        Next
    End With
End Sub

Private Sub cmdBorrarItem_Click()
    Dim i As Long
    '
'    ' Si sólo se permitiera la selección simple (un solo elemento)
'    ' Averiguamos el índice del nodo que está seleccionado
'    i = ListView1.SelectedItem.Index
'    ListView1.ListItems.Remove i
    '
    ' Si se permite la selección de varios elementos
    ' recorrerlos desde el final para que no cambie el número de elementos
    ' mientras borramos...
    ' realmente si cambia, pero al examinarlos desde el final,
    ' no altera el contenido de los primeros
    For i = ListView1.ListItems.Count To 1 Step -1
        ' si está seleccionado
        If ListView1.ListItems(i).Selected Then
            ' lo borramos
            ListView1.ListItems.Remove i
        End If
    Next
End Sub

Private Sub cmdBuscar_Click()
    ' Buscar el texto indicado en los elementos del listview
    Dim tItem As ListItem
    Dim lvwFind As ListFindItemHowConstants
    Dim lvwWhere As ListFindItemWhereConstants
    Dim i As Long
    Dim s As String
    '
    ' si tenemos que buscar la palabra exacta o parcial
    ' (aunque esto parece ser que no funciona...
    ' en los subitems siempre busca la cadena completa,
    ' y en el Text, la búsqueda parcial sólo es desde el principio)
    If chkExacta.Value = vbChecked Then
        lvwFind = lvwWhole
    Else
        lvwFind = lvwPartial
    End If
    ' si tenemos que buscar en el texto o en los subitems
    If chkSubitems.Value = vbChecked Then
        lvwWhere = lvwSubItem
    Else
        lvwWhere = lvwText
    End If
    ' realizamos la búsqueda
    Set tItem = ListView1.FindItem(Text1, lvwWhere, 1, lvwFind)
    '
    ' Si tItem es Nothing, es que no existe...
    If Not tItem Is Nothing Then
        ' quitamos la selección anterior
        'ListView1.SelectedItem.Selected = False
        ' (si hubiera más de uno, habría que hacer un bucle)
        For i = 1 To ListView1.ListItems.Count
            If ListView1.ListItems(i).Selected Then
                ListView1.ListItems(i).Selected = False
            End If
        Next
        '
        ' Seleccionamos el hallado
        tItem.Selected = True
        '
        ' Mostramos el contenido del que hemos hallado
        Text1 = tItem.Text
        ' Mostramos cada uno de los subitems
        s = ""
        For i = 2 To ListView1.ColumnHeaders.Count
            s = s & tItem.SubItems(i - 1) & vbCrLf
        Next
        Text4 = s
    Else
        Text4 = "No se ha hallado el elemento buscado"
    End If
End Sub

Private Sub cmdLlenarLvw_Click()
    Dim i As Long
    '
    ' Eliminar las cabeceras
    ListView1.ColumnHeaders.Clear
    '
    ' Asignar las cabeceras
    With ListView1.ColumnHeaders.Add(, , "Nombre", 1620)
        .Tag = cTexto
    End With
    With ListView1.ColumnHeaders.Add(, , "Fecha", 1110, lvwColumnRight)
        .Tag = cFecha
    End With
    With ListView1.ColumnHeaders.Add(, , "Tamaño", 840, lvwColumnRight)
        .Tag = cNumero
    End With
    With ListView1.ColumnHeaders.Add(, , "Importe", 840, lvwColumnRight)
        .Tag = cMoneda
    End With
    '
    ListView1.ListItems.Clear
    ' Asignar algunos datos aleatorios
    Randomize
    For i = 1 To 10
        With ListView1.ListItems.Add(, , "Nombre número " & CStr(i))
            ' Cada subitem debe corresponder con cada una de las cabeceras
            ' la segunda cabecera es el Subitems(1) y así sucesivamente
            .SubItems(1) = Format$(Now + Int(Rnd * 30) + 1, cFormatoFecha)
            ' Si quieres probar con números con decimales
            '.SubItems(2) = CStr(i * (Rnd * 1500) + i)
            ' usando el formato de número indicado
            .SubItems(2) = Format$(i * Int(Rnd * 1500) + i, cFormatoNumero)
            ' El formato moneda
            .SubItems(3) = Format$((Rnd * 150000 + 1) / 100, cFormatoMoneda)
        End With
    Next
End Sub

Private Sub cmdOtroFormato_Click()
    Dim i As Long
    '
    ' Eliminar las cabeceras
    ListView1.ColumnHeaders.Clear
    '
    ' Asignar las cabeceras
    ' (la primera columna SIEMPRE debe estar alineada a la izquierda)
    With ListView1.ColumnHeaders.Add(, , "Fecha", 1110)
        .Tag = cFecha
    End With
    With ListView1.ColumnHeaders.Add(, , "Nombre", 1620)
        .Tag = cTexto
    End With
    With ListView1.ColumnHeaders.Add(, , "Importe", 1110, lvwColumnRight)
        .Tag = cMoneda
    End With
    '
    ListView1.ListItems.Clear
    ' Asignar algunos datos aleatorios
    Randomize
    For i = 1 To 11 '1100
        With ListView1.ListItems.Add(, , Format$(Now + Int(Rnd * 30) + 1, cFormatoFecha))
            .SubItems(1) = "Nombre número " & CStr(i)
            .SubItems(2) = Format$((Rnd * 150000 + 1) / 100, cFormatoMoneda)
        End With
    Next
End Sub

Private Sub cmdSubst_Click()
    ' Substituir el último elemento seleccionado
    ' Los subitems se añadirán del contenido del Text4,
    ' los cuales estarán en líneas separadas, (una para cada subitem)
    Dim i As Long
    Dim aSubItems() As String
    '
    ' Creamos un array con el contenido del Text4
    aSubItems = Split(Text4, vbCrLf)
    ' para que aSubItems tenga el mismo número que los subitems
    i = ListView1.ColumnHeaders.Count - 2
    ReDim Preserve aSubItems(i)
    '
    ' Vamos a asignar en el elemento que esté seleccionado
    ' (o que se haya pulsado el último,
    ' por ejemplo al quitar la selección...)
    With ListView1.SelectedItem
        .Text = Text1
        For i = 2 To ListView1.ColumnHeaders.Count
            .SubItems(i - 1) = aSubItems(i - 2)
        Next
    End With
End Sub

Private Sub Form_Load()
    ' Asignar lo valores predeterminados
    With ListView1
        ' Las pruebas serán en modo "detalle"
        .View = lvwReport
        ' al seleccionar un elemento, seleccionar la línea completa
        .FullRowSelect = True
        ' Mostrar las líneas de la cuadrícula
        .GridLines = True
        ' No permitir la edición automática del texto
        .LabelEdit = lvwManual
        ' Permitir múltiple selección
        .MultiSelect = True
        ' Para que al perder el foco,
        ' se siga viendo el que está seleccionado
        .HideSelection = False
    End With
    '
    ' Asignar las cabeceras
    ' Este mismo código se repite en el procedimiento cmdLlenarLvw_Click,
    ' pero lo dejo para saber cómo eliminar tanto las cabeceras
    ' como los elementos propiamente dichos.
    '
    ' El valor asignado al TAG es para saber que tipo de clasificación
    ' hay que realizar, el ListView siempre los ordena como cadenas
    With ListView1.ColumnHeaders.Add(, , "Nombre", 1620)
        .Tag = cTexto
    End With
    With ListView1.ColumnHeaders.Add(, , "Fecha", 1110, lvwColumnRight)
        .Tag = cFecha
    End With
    With ListView1.ColumnHeaders.Add(, , "Tamaño", 840, lvwColumnRight)
        .Tag = cNumero
    End With
    With ListView1.ColumnHeaders.Add(, , "Importe", 840, lvwColumnRight)
        .Tag = cMoneda
    End With
    '
    Text1 = ""
    Text2 = ""
    Text3 = ""
    Text4 = ""
    '
    ' Añadir algunos datos de prueba
    cmdLlenarLvw_Click
End Sub

Private Sub ListView1_AfterLabelEdit(Cancel As Integer, NewString As String)
    ' El nuevo texto después de editarlo manualmente
    Text3 = NewString
End Sub

Private Sub ListView1_BeforeLabelEdit(Cancel As Integer)
    ' El texto antes de editarlo
    ' Los Listview sólo permiten editar el texto del elemento
    ' no los Subitems...
    Text2 = ListView1.SelectedItem.Text
End Sub

Private Sub ListView1_Click()
'    ' Mostramos el contenido del último item seleccionado
'    ' (esto también se puede hacer, pero el ListView tiene un evento
'    ' para saber cuando se ha pulsado en un elemento, ver: ListView1_ItemClick)
'    Dim tItem As ListItem
'    Dim i As Long
'    Dim s As String
'    '
'    Set tItem = ListView1.SelectedItem
'    Text1 = tItem.Text
'    ' Mostramos cada uno de los subitems del item seleccionado
'    s = ""
'    For i = 2 To ListView1.ColumnHeaders.Count
'        s = s & tItem.SubItems(i - 1) & vbCrLf
'    Next
'    Text4 = s
End Sub

Private Sub ListView1_ColumnClick(ByVal ColumnHeader As MSComctlLib.ColumnHeader)
    '
    ' NOTA: Al tener cargado dos proyectos, me daba error si
    '       tenía el MSComctlLib. delante del ColumnHeader
    '
    Dim i As Long, col As Long
    Dim s As String
    '
    col = ColumnHeader.Index - 1
    '
    If ListView1.SortOrder = lvwAscending Then
        ListView1.SortOrder = lvwDescending
    Else
        ListView1.SortOrder = lvwAscending
    End If
    '
    ' Según el tipo de datos, asignar el formato correspondiente:
    Select Case ColumnHeader.Tag
    Case cNumero, cMoneda
        ' Para clasificar el tipo momenda y el número normal no importa
        For i = 1 To ListView1.ListItems.Count
            If col = 0 Then
                s = ListView1.ListItems(i).Text
            Else
                s = ListView1.ListItems(i).SubItems(col)
            End If
            ' Añadimos unos cuantos ceros al principio
            ' para que se clasifique correctamente
            s = Right$(String$(cCuantasCifras, "0") & s, cCuantasCifras)
            If col = 0 Then
                ListView1.ListItems(i).Text = s
            Else
                ListView1.ListItems(i).SubItems(col) = s
            End If
        Next
    Case cFecha
        For i = 1 To ListView1.ListItems.Count
            If col = 0 Then
                s = ListView1.ListItems(i).Text
            Else
                s = ListView1.ListItems(i).SubItems(col)
            End If
            ' Las fechas se indicarán empezando con el año
            s = Format$(s, "yyyy/mm/dd")
            If col = 0 Then
                ListView1.ListItems(i).Text = s
            Else
                ListView1.ListItems(i).SubItems(col) = s
            End If
        Next
    End Select
    ListView1.SortKey = col
    ListView1.Sorted = True
    '
    ' Restaurar los valores al formato original
    ' aquí es donde entran en juego las constantes de formato...
    Select Case ColumnHeader.Tag
'    ' si no queremos usar un formato en particular,
'    ' podemos usar esta forma de restaurar los valores,
'    ' aunque es un poco más lenta...
'    Case cNumero, cMoneda
'        For i = 1 To ListView1.ListItems.Count
'            If col = 0 Then
'                s = ListView1.ListItems(i).Text
'            Else
'                s = ListView1.ListItems(i).SubItems(col)
'            End If
'            ' Quitamos los ceros que haya a la izquierda
'            Do While Left$(s, 1) = "0"
'                s = Mid$(s, 2)
'            Loop
'            If col = 0 Then
'                ListView1.ListItems(i).Text = s
'            Else
'                ListView1.ListItems(i).SubItems(col) = s
'            End If
'        Next
    Case cNumero
        For i = 1 To ListView1.ListItems.Count
            If col = 0 Then
                s = ListView1.ListItems(i).Text
            Else
                s = ListView1.ListItems(i).SubItems(col)
            End If
            s = Format$(s, cFormatoNumero)
            If col = 0 Then
                ListView1.ListItems(i).Text = s
            Else
                ListView1.ListItems(i).SubItems(col) = s
            End If
        Next
    Case cMoneda
        For i = 1 To ListView1.ListItems.Count
            If col = 0 Then
                s = ListView1.ListItems(i).Text
            Else
                s = ListView1.ListItems(i).SubItems(col)
            End If
            s = Format$(s, cFormatoMoneda)
            If col = 0 Then
                ListView1.ListItems(i).Text = s
            Else
                ListView1.ListItems(i).SubItems(col) = s
            End If
        Next
    Case cFecha
        For i = 1 To ListView1.ListItems.Count
            If col = 0 Then
                s = ListView1.ListItems(i).Text
            Else
                s = ListView1.ListItems(i).SubItems(col)
            End If
            s = Format$(s, cFormatoFecha)
            If col = 0 Then
                ListView1.ListItems(i).Text = s
            Else
                ListView1.ListItems(i).SubItems(col) = s
            End If
        Next
    End Select
    '
End Sub

Private Sub ListView1_ItemClick(ByVal Item As MSComctlLib.ListItem)
    ' Mostramos el contenido del último item seleccionado
    Dim i As Long
    Dim s As String
    '
    Text1 = Item.Text
    ' Mostramos cada uno de los subitems del item seleccionado
    s = ""
    For i = 2 To ListView1.ColumnHeaders.Count
        s = s & Item.SubItems(i - 1) & vbCrLf
    Next
    Text4 = s
End Sub

El código de ejemplo: Listview.zip 6.08KB

Nos vemos.
Guillermo


ir al índice