miércoles, 3 de febrero de 2010

Buscar en una base de datos mientras se escribe

Introducción:

En Las versiones anteriores de Visual Studio debemos hacerlo de forma manual, y en este ejemplo que te pongo aquí, aunque está hecho con Visual Basic 2005, también será válido para usarlo con las versiones anteriores, aunque yo no lo he probado, ya que hace unas semanas decidí desinstalar el Visual Studio 2003, así que... supongo que se podrá hacer de igual forma. De todos modos, en este artículo uso como ejemplo un control DataGridView, que aunque no es "obligatorio" para el ejemplo, si que hace que este código que te muestro solo sea válido para Visual Studio 2005, pero... con unos pocos cambios puedes convertirlo para las versiones anteriores.

¿Qué hace este ejemplo?

Te explico lo que he hecho para que lo entiendas bien y puedas adaptarlo a tus necesidades.
En este ejemplo uso una base de datos de SQL Server 2005, pero sirve para cualquier tipo de base de datos, solo que tendrás que modificar la cadena de conexión y el tipo del "DataAdapter" usado para acceder a los datos. 
La tabla tiene varios campos, pero en este ejemplo solo se usan dos: Nombre y Apellidos.
En el formulario de ejemplo uso estos controles: (ver la figura 1)
Figura 1. El formulario en modo de diseño
Figura 1. El formulario en modo de diseño

Una etiqueta, una caja de textos llamada txtApellidos, un ListBox para mostrar lo que se vaya encontrando conforme escribes, esa lista se llama listaApellidos. También hay un control de tipo DataGridView en el que se mostrarán los datos que hemos leído de la base de datos, ese control se llama datosClientes.
Cuando escribas algo en la caja de textos, se busca en la base de datos (en realidad en el contenido del DataTable que hemos cargado al iniciar la aplicación), y se van mostrando los datos en la lista. En la figura 2 puedes ver un ejemplo al escribir la letra M.
Figura 2. La aplicación en ejecución
Figura 2. La aplicación en ejecución

¿Qué se necesita para hacer esto?

Aparte de los controles que te he comentado, tendrás que definir un objeto del tipo DataTable que será donde estén los datos que hay en la tabla de Clientes:
 
' El DataTable lo necesitamos a nivel del formulario
Private dt As DataTable

Para obtener esos datos y llenar la tabla, necesitas un DataAdapter, que dependiendo de si accedes a una base de datos de Access o de SQL Server (o de cualquier otro tipo), tendrás que definirlo de la forma adecuada, en este ejemplo uso una base de datos de SQL Server, por tanto, el "adaptador" que uso es del tipo SqlDataAdapter. Y como es posible que use otras cosas (que  en este caso no las uso), lo mejor es añadir una importación al espacio de nombres en el que está definido el adaptador:

Imports System.Data
Imports System.Data.SqlClient
El adaptador (recuerda que la base es de SQL Server), lo declaramos de esta forma:
' El adapatador para obtener los datos
Private da As SqlDataAdapter
También necesitas la cadena de conexión a la base de datos, en mi caso, la base de datos está en la instancia de SQLEXPRESS, por tanto, uso esta cadena:
' La cadena de conexión
Private conexion As String = _
            "Data Source = (local)\SQLEXPRESS; " & _
            "Initial Catalog=PruebasGuille; " & _
            "Integrated Security=true"
La cadena de selección (la que indica que datos debemos traer de la base de datos), es muy simple: traemos todos los nombres y apellidos de la tabla Clientes:
' La cadena de selección
' los datos que traeremos de la base de datos.
Private seleccion As String = _
            "SELECT Nombre, Apellidos FROM Clientes"

Cargar los datos

Una vez que tenemos todas las variables definidas, (en realidad solo es necesario definir el DataTable a nivel de formulario), en el evento Load del formulario cargamos el DataTable con los datos:

Private Sub Form1_Load( _
            ByVal sender As Object, _
            ByVal e As EventArgs) _
            Handles MyBase.Load
 
    Me.txtApellidos.Text = ""
 
    da = New SqlDataAdapter(seleccion, conexion)
    dt = New DataTable
    da.Fill(dt)
 
    Me.datosClientes.DataSource = dt
 
    iniciando = False
End Sub

Fíjate en dos detalles del código anterior:
1- Asigno una cadena vacía a la caja de textos. Esto es por si tenía algo escrito.
2- Estoy usando una variable que he declarado a nivel de formulario para saber si estoy iniciando los datos o no. Al definirla le asigno un valor verdadero, y cuando se termina el código del formulario le asigno un valor falso.

Buscar los datos según lo escrito

Esto lo hago porque en el evento TextChanged de la caja de textos hago la comprobación de si los apellidos escritos están en la tabla, y como resulta que al iniciarse el formulario (en el constructor), si has escrito algo en el diseñador de formularios, eso que hayas escrito se va a asignar a la propiedad Text de la caja de textos, y cuando el código de asignación se ejecute, se va a producir el evento TextChanged, y si se produce ese evento antes de que todo esté "inicializado", pues... tendremos un error...Para que lo comprendas mejor, te muestro el código del evento TextChanged de la caja de textos, que es el que se encarga de hacer la búsqueda en los datos que tenemos en el objeto DataTable (dt):

Private Sub txtApellidos_TextChanged( _
            ByVal sender As Object, _
            ByVal e As EventArgs) _
            Handles txtApellidos.TextChanged
 
    If iniciando Then Exit Sub
 
    ' Buscar en el DataTable usando el método Select
    ' que es como un filtro WHERE en una cadena de selección. 
    ' El resultado se devuelve como un array de tipo DataRow
    Dim filas() As DataRow
 
    ' Si solo quieres mostrar los que empiecen por lo escrito.
    ' Al escribir "s" se buscarán los que empiecen por esa letra.
    filas = dt.Select("Apellidos LIKE '" & txtApellidos.Text & "%'")
 
    ' Borrar los elementos anteriores
    Me.listaApellidos.Items.Clear()
 
    ' Si hay datos, mostrar los apellidos
    If filas.Length > 0 Then
 
        ' Recorrer cada fila y mostrar los apellidos
        For Each dr As DataRow In filas
 
            Me.listaApellidos.Items.Add( _
                        dr("Apellidos").ToString & ", " & _
                        dr("Nombre").ToString)
 
        Next
    End If
End Sub 
 
Fíjate que lo primero que hago en ese evento es comprobar si el valor de la variable iniciando es verdadero, y en caso de que así sea, simplemente "me voy" y no hago nada de nada.
Como puedes imaginar (además de porque ya te lo he dicho), esa variable está definida de esta forma (con idea de que inicialmente tenga un valor verdadero):

' Para evitar re-entradas en el código
Private iniciando As Boolean = True 

Buscar en el campo Apellidos con las letras en cualquier posición

El "truco" de todo esto está en la forma de usar el método Select del objeto DataTable.
Ese método espera una expresión al estilo de la que usaríamos con WHERE en una consulta de SQL, por tanto, podemos usar lo mismo que en esa cláusula WHERE. En el código anterior, lo que le digo que haga es:
Buscar todas las filas en las que el campo Apellidos "empiece" con el texto indicado.
Que traducido al lenguaje que entiende el método Select es: Apellidos LIKE 'cadena'
Y como le hemos puesto un signo de porcentaje al final, le estamos diciendo que busque lo que haya en la caja de textos y si en el campo Apellidos hay más cosas después de lo indicado, pues que lo de por bueno.
Por tanto, si tenemos  varios datos de apellidos que empiezan por la letra eme y escribimos una "M", se mostrarán todos los que coincidan, que es lo que puedes ver en la figura 2.
Pero si en vez de usar el código mostrado antes, usas este otro:

filas = dt.Select("Apellidos LIKE '%" & txtApellidos.Text & "%'")

Como estamos usando los caracteres comodines antes y después de lo que se escriba, el resultado al escribir la letra "M" será el que ves en la figura 3, que si te fijas en la lista, hay más de los 2 datos que mostró el primer código que usé para la captura de la figura 2, ya que se encuentran todos los que tenga alguna letra M en los apellidos, además de que no se hace distinción de si están en mayúsculas o minúsculas.
Figura 3. Sin tener en cuenta donde está lo escrito
Figura 3. Sin tener en cuenta donde está lo escrito

Sincronizar el resultado con el DataGridView

Esto que te voy a explicar ahora en realidad no es necesario, pero como se lo que pasa, que después alguno vendrá diciendo:
¿Y como puedo hacer para que si pulso en el nombre de la lista me busque el que está en el DataGridView?
Así que, me adelanto a la pregunta y te digo como lo he hecho yo, que no quiere decir que sea la mejor ni la única forma de hacerlo, pero es lo que se que funciona, je, je.
Lo que viene a continuación es para que al pulsar en uno de los nombres de la lista, se seleccione el mismo elemento del DataGridView y se muestre, vamos que sea visible si hay más datos de los que se muestran.
Por ejemplo, si escribes la letra M, en la lista tendrás varios nombres (ver la figura 4), y cuando pulsas en uno de ellos, ese nombre se busca en el DataGridView y se selecciona, además de hacer que sea visible, como puedes ver en la figura 5.
Figura 4. El primer elemento del DataGridView es Guillermo
Figura 4. El primer elemento del DataGridView es Guillermo
Figura 5. Ahora el primero que se muestra es otro
Figura 5. Ahora el primero que se muestra es otro

Como los datos se muestran en la lista tienen los apellidos separados del nombre con una coma, vamos a tener en cuenta ese caso, de forma que haremos una búsqueda doble, por el apellido y por el nombre.
¿Dónde buscamos?
En cada "celda" de cada fila del control DataGridView (como puedes comprobar algo poco eficiente si tenemos muchísimos datos, pero... eso es lo que hay, je, je).
Una cosa que me ha pasado es que si lo que se busca no está... pues no está, vale, pero lo raro es que me daba un error de objeto no inicializado... y ahora que estoy escribiendo esto, estoy pensando que... sí, ¿que pasa?, algunas veces pienso después de hacer las cosas y de tirarme nosecuantas horas intentando resolver el fallo... A lo que iba, ese fallo seguramente sería por la última fila del DataGridView está vacía... y como para la búsqueda usé un bucle For Each en vez de uno de tipo For con una variable numérica, pues... lo que te muestro, seguro que se puede mejorar, pero... ya sabes... al que no le guste, ¡que lo haga mejor! je, je... 
Como te decía, si hay una coma en el texto seleccionado, se toma lo que haya antes como los apellidos y lo que haya después como el nombre, y se busca en el contenido del DataGridView.
Si no hay coma, se busca solo por los apellidos.
Fíjate también que hago una llamada al método ClearSelection del DataGridView para quitar la selección anterior que hubiera. Y para asegurarnos de que el elemento seleccionado es visible, asigno el índice a FirstDisplayedScrollingRowIndex, ese índice lo obtengo de la fila que coincide con la búsqueda.
Aquí tienes el código del evento SelectedIndexChanged del control ListBox, de forma que al pulsar en uno de los elementos de la lista se ejecute este código.

Private Sub listaApellidos_SelectedIndexChanged( _
            ByVal sender As Object, _
            ByVal e As EventArgs) _
            Handles listaApellidos.SelectedIndexChanged
 
    If iniciando Then Exit Sub
 
    ' Al hacer clic, mostrar el dato
    Me.txtApellidos.Text = Me.listaApellidos.SelectedItem.ToString
 
    ' Buscarlo en el DataGridView (de forma manual, no conozco otra forma...)
 
    ' Eliminar las selecciones anteriores
    Me.datosClientes.ClearSelection()
 
    ' Recorrer las filas para buscar el Apellido indicado
    For Each fila As DataGridViewRow In Me.datosClientes.Rows
        ' Si es el mismo apellido del textBox
 
        ' Curiosamente si no son los mismos datos
        ' se produce un error de que d.Cells(...) es Nothing
        ' En realidad de "curioso" no tiene nada,
        ' es que es la última fila, que está vacía...
        If fila.Cells("Apellidos").Value Is Nothing _
        OrElse fila.Cells Is Nothing Then
            Continue For
        End If
 
        ' Si se quiere tener en cuenta el nombre y los apellidos
        Dim i As Integer = Me.txtApellidos.Text.IndexOf(",")
 
        If i > -1 Then
            ' En este ejemplo, el formato es Apellidos, Nombre
            Dim nombre, apellidos As String
            apellidos = Me.txtApellidos.Text.Substring(0, i).TrimEnd()
            nombre = Me.txtApellidos.Text.Substring(i + 1).TrimStart()
 
            If nombre = fila.Cells("Nombre").Value.ToString _
            AndAlso apellidos = fila.Cells("Apellidos").Value.ToString Then
                ' Seleccionamos la fila
                Me.datosClientes.Rows(fila.Index).Selected = True
                ' nos aseguramos de que sea visible
                Me.datosClientes.FirstDisplayedScrollingRowIndex = fila.Index
                Exit For
            End If
        Else
            If Me.txtApellidos.Text = fila.Cells("Apellidos").Value.ToString Then
                ' Seleccionamos la fila
                Me.datosClientes.Rows(fila.Index).Selected = True
                ' nos aseguramos de que sea visible
                Me.datosClientes.FirstDisplayedScrollingRowIndex = fila.Index
                Exit For
            End If
        End If
    Next
End Sub

4 comentarios:

  1. muy buen ejemplo pero no me funciona el datagridview

    ResponderEliminar
  2. ¿Pór qué no poner la Fuente original? Creo que es de buenas costumbres también, además de ayudar, poner la fuente original de la información si no fuimos nosotros. Saludos JFM

    ResponderEliminar
  3. hola, tengo una duda estoy trabajando en c# la busqueda dinamica, pero tengo un problema, y como hacer la busqueda sin importar los acentos ya que en mi base de datos quiennes lo registraron lo escribieron indistintamente, asi por ejemplo López y Lopez pero solo me filtra el segundo
    de atenmano muchas gracias
    Carloriosr@hotmail.com

    ResponderEliminar