Buscar elementos en un ListView usando APIPublicado el 12/Dic/2004
|
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 FunctionDe 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:
- La clase ListViewApi y el formulario de prueba tanto para VB como para C#:
ListViewApi.zip - 45 KB
- La clase ListViewFind (control ListView con funciones de búsqueda) con el proyecto de Windows Forms de prueba, tanto para VB como para C#:
ListViewFind.zip - 65 KB
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
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