Índice de la sección dedicada a .NET (en el Guille) Cómo... en .NET

Buscar elementos en un ListView usando API

Publicado el 12/Dic/2004
Actualizado el 12/Dic/2004
Autor: Guillermo 'guille' Som

 

Código para Visual Basic.NET (VB.NET)

Código para C Sharp (C#)

 

Introducción

El control ListView que se incluye con Visual Studio .NET no tiene funcionalidad para buscar elementos, mediante el API de Windows podemos agregar esa funcionalidad, (que ya se incluía en el ListView de VB6).
En este artículo vamos a crear una clase que proporcione un método para buscar en los elementos (Items) de un ListView de .NET.
También incluyo en el zip con el código una clase (control) ListView que incluye la misma funcionalidad que muestro en la clase.

La clase que vamos a crear, se llama ListViewApi y estará en el espacio de nombres elGuille.ListViewUtil (aunque lo puedes cambiar para usar el tuyo propio).

Para buscar elementos en el ListView, vamos a usar la función SendMessage del API de Windows, a la que le pasaremos una estructura en la que se indicará lo que queremos hacer. El mensaje que le pasaremos a SendMessage será LVM_FINDITEM.

Los miembros de esta clase los he declarado "compartidos" (Shared en VB, static en C#), para que se puedan usar sin necesidad de crear una instancia de la clase.

En el zip con el código además de la clase, tienes un proyecto de Windows Forms para probar que todo esto funciona, en el artículo sólo te mostraré parte de ese código.
También incluyo una librería (DLL) con un control ListView que incorpora la función de buscar elementos, además de un proyecto para que puedas probarlo.

Cómo funciona la búsqueda de elementos en un ListView

Como te he comentado, la función del API usada para buscar elementos en un ListView es SendMessage. Esta función se usa indicando cuatro parámetros:
El Handle de la ventana, el mensaje a enviar a dicha ventana y dos parámetros, los cuales dependen del tipo de mensaje enviado.

En nuestro caso le pasaremos a SendMessage el "Handle" del ListView, el mensaje LVM_FINDITEM, para que Windows sepa que queremos buscar en los elementos del ListView, en los dos últimos parámetros le indicaremos el índice (en base cero) después del que queremos buscar, si queremos buscar desde el principio, le pasaremos -1, en el último parámetro le pasamos una estructura del tipo LVFINDINFO en la que indicaremos que tipo de búsqueda queremos hacer (si queremos buscar una cadena completa o sólo el trozo que indiquemos, pero empezando por el principio, también le podemos indicar que siga buscando desde el principio si no encuentra lo que buscamos desde el índice indicado) y la cadena que se debe buscar.

La definición de la estructura la he extraído de la documentación del SDK y para C sería esta, (aunque nuestra versión será más simple):

typedef struct tagLVFINDINFO {
    UINT flags;
    LPCTSTR psz;
    LPARAM lParam;
    POINT pt;
    UINT vkDirection;
} LVFINDINFO, *LPFINDINFO;

La función SendMessage habría que declararla de esta forma (recuerda que es en C):

lResult = SendMessage(      // returns LRESULT in lResult
    (HWND) hWndControl,     // handle to destination control
    (UINT) LVM_FINDITEM,    // message ID
    (WPARAM) wParam,        // = (WPARAM) (int) iStart;
    (LPARAM) lParam         // = const (LPARAM) (LPLVFINDINFO) plvfi; 
);

En el último parámetro realmente espera una estructura del tipo LVFINDINFO.

El código de Visual Basic (ya modificado) sería el siguiente:

Private Structure tagLVFINDINFO
    Public flags As FindItemFlags
    Public psz As String
    Dim lParam As Integer
    Dim pt As Integer
    Dim vkDirection As Integer
End Structure
'
<System.Runtime.InteropServices.DllImport("user32.dll", entryPoint:="SendMessage")> _
Private Shared Function SendMessageLVFIND(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal iStart As Integer, ByRef plvfi As tagLVFINDINFO) As Integer
End Function

De la estructura sólo necesitamos los dos primeros campos, el primero para indicar el tipo de búsqueda, (los valores posibles los he incluido en una enumeración), y el segundo, en el que indicaremos la cadena a buscar. Estos dos "elementos" están declarados privados, ya que solo lo usaremos desde la propia clase, con idea de que el usuario de la misma "no se complique la vida".

Las funciones públicas de la clase

Esta clase tiene dos funciones públicas, aunque sólo una usa esta función del API, la otra la he incluido en la misma clase, pero realmente usa "código normal":

FindItem, de la cual existen varias sobrecargas, para que podamos usarla de forma genérica o "afinar" en lo que queremos buscar y cómo buscarlo.
UnSelectAll, para quitar la selección a todos los elementos que estén seleccionados.

Realmente la que nos interesa aquí es la primera, a la que podemos indicar:
- En el primer parámetro el Handle del ListView o simplemente podemos pasar el ListView que tiene los elementos en los que queremos buscar.
- En el segundo parámetro la cadena a buscar
- En el tercer parámetro el tipo de búsqueda a realizar (opcional, predeterminado búsqueda exacta).
- El el cuarto parámetro el índice (en base cero) a partir del que queremos buscar (opcional, predeterminado -1, para buscar desde el principio).
Los dos últimos parámetros 8que son opcionales), sólo los podemos indicar con la sobrecarga en la que se indica el Handle del ListView.

Para buscar, haremos algo como esto:

' Si se indica búsqueda exacta
If chkCompleta.Checked Then
    i = ListViewApi.FindItem(listView1, cboBuscar.Text)
Else
    i = ListViewApi.FindItem(listView1, cboBuscar.Text, ListViewApi.FindItemFlags.StartsWith)
End If
'
' Devuelve -1 si no se ha encontrado lo que se busca
If i = -1 Then
    lblInfo.Text = " No se ha hallado el elemento"
Else
    lblInfo.Text = " Es el elemento " & i
    ' quitar la selección que haya
    ListViewApi.UnSelectAll(listView1)
    ' seleccionar el encontrado
    listView1.Items(i).Selected = True
End If

 

Nota:
La enumeración FindItemFlags le he aplicado el atributo FlagsAttribute para que se puedan indicar varios valores usando Or.
Ver más abajo en el código.

 

Y esto es casi todo lo que te puedo explicar, para saber cómo se ha declarado la enumeración y ver el ejemplo completo, mejor te bajas el zip con el código.

Al final te muestro el código de la clase tanto para Visual Basic .NET (2003) como para C#.

Recuerda que el código está es para Visual Basic y para C# y que también hay más ejemplos para crear un control que incorpore esa misma funcionalidad, junto con un formulario de ejemplo para usar dicho control.

Espero que te sea de utilidad.

Nos vemos.
Guillermo
Nerja, 12 de diciembre de 2004


Los zips con el código fuente:

 


Código para Visual Basic.NET (VB.NET)El código de la clase ListViewApi para VB .NET

 

'------------------------------------------------------------------------------
' Clase para buscar en los elementos de un ListView                 (09/Dic/04)
'
' ©Guillermo 'guille' Som, 2004
'------------------------------------------------------------------------------
Imports System

Namespace elGuille.ListViewUtil

' clase para búsqueda en ListViews usando API
Public Class ListViewApi
    '----------------------------------------------------------------------
    ' Miembros privados
    '----------------------------------------------------------------------
    Const LVM_FIRST As Integer = &H1000
    Const LVM_FINDITEM As Integer = (LVM_FIRST + 13)
    '
    Private Structure tagLVFINDINFO
        Public flags As FindItemFlags
        Public psz As String
        Dim lParam As Integer
        Dim pt As Integer
        Dim vkDirection As Integer
    End Structure
    '
    <System.Runtime.InteropServices.DllImport("user32.dll", entryPoint:="SendMessage")> _
    Private Shared Function SendMessageLVFIND(ByVal hWnd As IntPtr, ByVal msg As Integer, ByVal iStart As Integer, ByRef plvfi As tagLVFINDINFO) As Integer
    End Function
    '----------------------------------------------------------------------
    ' Miembros públicos
    '----------------------------------------------------------------------
    <FlagsAttribute()> _
    Public Enum FindItemFlags As Integer
        FullString = &H2        ' Busca la cadena completa (LVFI_STRING)
        StartsWith = &H8        ' Busca la cadena indicada desde el principio (LVFI_PARTIAL)
        ContSearching = &H20    ' Continúa buscando desde el principio si no halla nada (LVFI_WRAP)
    End Enum
    '
    ' Busca en los elementos de un listview, devuelve -1 si no lo encuentra
    Public Shared Function FindItem(ByVal listView As System.Windows.Forms.ListView, ByVal buscar As String) As Integer
        Return FindItem(listView.Handle, buscar, FindItemFlags.FullString, -1)
    End Function
    '
    Public Shared Function FindItem(ByVal listView As System.Windows.Forms.ListView, ByVal buscar As String, ByVal flags As FindItemFlags) As Integer
        Return FindItem(listView.Handle, buscar, flags, -1)
    End Function
    '
    Public Shared Function FindItem(ByVal hWnd As IntPtr, ByVal buscar As String) As Integer
        Return FindItem(hWnd, buscar, FindItemFlags.FullString, -1)
    End Function
    '
    Public Shared Function FindItem(ByVal hWnd As IntPtr, ByVal buscar As String, ByVal flags As FindItemFlags) As Integer
        Return FindItem(hWnd, buscar, flags, -1)
    End Function
    '
    Public Shared Function FindItem(ByVal hWnd As IntPtr, ByVal buscar As String, ByVal flags As FindItemFlags, ByVal iStart As Integer) As Integer
        Dim LV_FINDINFO As New tagLVFINDINFO
        LV_FINDINFO.flags = flags
        LV_FINDINFO.psz = buscar
        Return SendMessageLVFIND(hWnd, LVM_FINDITEM, iStart, LV_FINDINFO)
    End Function
    '
    ' función para quitar la selección a todos los elementos
    ' realmente no usa el API, pero...
    Public Shared Sub UnSelectAll(ByVal listView As System.Windows.Forms.ListView)
        ' sólo recorrer los que estén seleccionados
        For i As Integer = listView.SelectedIndices.Count - 1 To 0 Step -1
            listView.Items(listView.SelectedIndices(i)).Selected = False
        Next
    End Sub
End Class

End Namespace

 


Código para C Sharp (C#)El código de la clase ListViewApi para C# (y cómo usarla)

 

//-----------------------------------------------------------------------------
// Clase para buscar en los elementos de un ListView                (09/Dic/04)
// Usando API de Windows (SendMessage)
//
// ©Guillermo 'guille' Som, 2004
//-----------------------------------------------------------------------------
using System;

namespace elGuille.ListViewUtil
{

/// <summary>
/// Esta clase expone una función para buscar en los elementos de un ListView
/// Autor: Guillermo 'guille' Som
/// Fecha: 09/Dic/2004
/// </summary>
public class ListViewApi
{
    //---------------------------------------------------------------------
    // Miembros privados
    //---------------------------------------------------------------------
    const int LVM_FIRST = 0x1000;
    const int LVM_FINDITEM = (LVM_FIRST + 13);
    //
    private struct tagLVFINDINFO
    {
        public FindItemFlags flags;
        public string psz;
        int lParam;
        int pt;    //tagPOINT pt;
        int vkDirection;
    }
    //
    // SendMessage para usar con FindItem
    [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "SendMessage")]
    private static extern int SendMessageLVFIND(IntPtr hWnd, int msg, int iStart, ref tagLVFINDINFO plvfi);
    //
    //// SendMessage genérica
    //[System.Runtime.InteropServices.DllImport("user32.dll")]
    //private static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);
    //
    //---------------------------------------------------------------------
    // Miembros públicos
    //---------------------------------------------------------------------
    [FlagsAttribute]
    public enum FindItemFlags
    {
        FullString = 0x0002,        // Busca la cadena completa
        StartsWith = 0x0008,        // Busca la cadena indicada desde el principio
        ContSearching = 0x0020      // Continúa buscando desde el principio si no halla nada
    }
    //
    // Funciones para buscar elementos en el ListView
    public static int FindItem(System.Windows.Forms.ListView listView, string buscar)
    {
        return FindItem(listView.Handle, buscar, FindItemFlags.FullString, -1);
    }
    public static int FindItem(System.Windows.Forms.ListView listView, string buscar, FindItemFlags flags)
    {
        return FindItem(listView.Handle, buscar, flags, -1);
    }
    public static int FindItem(IntPtr hWnd, string buscar)
    {
        return FindItem(hWnd, buscar, FindItemFlags.FullString, -1);
    }
    public static int FindItem(IntPtr hWnd, string buscar, FindItemFlags flags)
    {
        return FindItem(hWnd, buscar, flags, -1);
    }
    public static int FindItem(IntPtr hWnd, string buscar, FindItemFlags flags, int iStart)
    {
        tagLVFINDINFO LV_FINDINFO = new tagLVFINDINFO();
        LV_FINDINFO.flags = flags;
        LV_FINDINFO.psz = buscar;
        return SendMessageLVFIND(hWnd, LVM_FINDITEM, iStart, ref LV_FINDINFO);
    }
    //
    // función para quitar la selección a todos los elementos
    // realmente no usa el API, pero...
    public static void UnSelectAll(System.Windows.Forms.ListView listView)
    {
        // sólo recorrer los que estén seleccionados
        for(int i = listView.SelectedIndices.Count - 1; i >= 0; i--)
            listView.Items[listView.SelectedIndices[i]].Selected = false;
    }
}

}

 

 

Cómo usar la clase ListViewApi en un formulario

 

// Si se indica búsqueda exacta
if(chkCompleta.Checked)
    i = ListViewApi.FindItem(listView1, cboBuscar.Text);
else
    i = ListViewApi.FindItem(listView1, cboBuscar.Text, ListViewApi.FindItemFlags.StartsWith);
//
// Devuelve -1 si no se ha encontrado lo que se busca
if(i == -1)
    lblInfo.Text = " No se ha hallado el elemento";
else
{
    lblInfo.Text = " Es el elemento " + i;
    // quitar la selección que haya
    ListViewApi.UnSelectAll(listView1);
    // seleccionar el encontrado
    listView1.Items[i].Selected = true;
}

 


Espacios de nombres usados:

System
System.Windows.Forms
System.Runtime.InteropServices


la Luna del Guille o... el Guille que está en la Luna... tanto monta...