Cómo buscar registros en un control DataGridView
Por Enrique Martínez Montejo
[Microsoft Most Valuable Professional - Visual Basic]
Última revisión: 25/10/2009
 

Son frecuentes las consultas que se efectúan en los grupos y foros de Visual Basic .net sobre cómo buscar registros en un control DataGridView dependiendo de un criterio de búsqueda especificado.

Si el control DataGridView se encuentra enlazado a un objeto DataTable, es fácil seleccionar todos aquellos registros que cumplan con un criterio de selección, mediante una llamada al método Select del objeto DataTable. Pero más que una búsqueda, el método Select lo que hará será filtrar los registros existentes en el propio objeto DataTable para devolver, en una matriz de objetos DataRow, aquéllos que cumplan con el criterio de filtro establecido. Pero éstos registros serán devueltos por el orden de la clave principal existente en el objeto DataTable, o si ésta no existe, por el orden en el que se añadieron los registros al objeto DataTable, o si lo preferimos, por el orden de clasificación que se indique, pero en ningún momento tendremos la información correspondiente al índice que la fila tiene dentro del objeto DataGridView. Tenga en cuenta que el control DataGridView nos permite ordenar los datos haciendo clic en los encabezados de sus columnas.

Lo que se pretende es que, con independencia de que el control DataGridView se encuentre o no enlazado a un origen de datos, se puedan buscar los registros o filas mediante los valores de un campo o columna de los existentes actualmente en el control DataGridView. Y eso es precisamente lo que voy a explicar en éste artículo.

Añadir un método Find al control DataGridView

Si el control DataGridView  dispusiera de un método de búsqueda, no me encontraría en estos momentos escribiendo sobre ésta tema: me gusta jugar al ajedrez en mis ratos libres. Pero como no es así, me voy a centrar en el mecanismo existente en la versión 3.5 del marco de trabajo de .NET, para añadir nuevos métodos a las clases existentes, incluidas las del propio marco de trabajo de .NET.

Mediante lo que se conoce como métodos de extensión, podemos añadir nuevos métodos a las clases ya existentes, y los llamaríamos de igual manera que llamamos a cualquier otro método de una clase, es decir, como si fuera otro método de instancia de una clase o tipo de dato existente.

Únicamente podemos definir como métodos de extensión, a los procedimientos Sub y Function, por tanto, estarían fuera del concepto los procedimientos de propiedad y evento, así como las definiciones de campo que se incluyan dentro de una clase (variables con ámbito de visibilidad a nivel de la propia clase donde se definen).

Todos los métodos de extensión, necesariamente tienen que estar marcados con el atributo Extension, el cual se encuentra incluido dentro del espacio de nombres System.Runtime.CompilerServices, por tanto, al comienzo del módulo importaríamos dicho espacio de nombres:

Imports System.Runtime.CompilerServices

Otro requisito que debe cumplir cualquier método de extensión, es que su primer parámetro siempre tiene que estar definido con el mismo tipo de dato de la clase que se desea extender. Si vamos a añadir a la clase DataGridView un método de extensión llamado Find, su primer parámetro deberá estar definido como DataGridView. Pero si el método de extensión lo vamos a añadir a la clase DataTable, entonces su primer parámetro deberá estar definido del tipo DataTable. En definitiva, el primer parámetro del método de extensión define la clase a la cual se va añadir dicho método.

En Visual Basic .NET, los métodos de extensión necesariamente deben declararse dentro de un módulo con un ámbito de visibilidad Public; no pueden existir dentro de la definición de una clase. En C# sí se encuentran dentro de una clase, al igual que cualquier otro procedimiento, pero tanto el método de extensión como la propia clase, tienen que estar definidos como estáticos mediante el modificador static.

Por último, decir que los métodos de extensión sólo están disponibles si nuestra aplicación utiliza la versión 3.5 del marco de trabajo de .NET, es decir, Visual Basic 2008 o cualquier otra versión posterior compatible con Visual Basic 2008.

Dicho lo anterior, ya es hora que pasemos a definir nuestro método Find para el control DataGridView. Por tanto, lo primero que hay que hacer es añadir un nuevo módulo a nuestro proyecto, al cual, bien podremos nombrarlo como DataGridViewExtensions, pensando si el día de mañana deseamos añadirle más métodos de extensión al control DataGridView.

Option Compare Text

Imports System.Runtime.CompilerServices

Public Module DataGridViewExtensions

#Region "Find"
    ''' <summary>
    ''' La función devolverá una lista de objetos DataGridViewRow
    ''' que satisfagan el criterio de búsqueda especificado.
    ''' </summary>
    ''' <param name="dgv">Control DataGridView.</param>
    ''' <param name="fieldName">Nombre de la columna por donde se desea buscar.</param>
    ''' <param name="criterio">Valor con el criterio de búsqueda deseado.</param>
    ''' <returns></returns>
    ''' <remarks></remarks>
    <Extension()> _
    Public Function Find( _
        ByVal dgv As DataGridView, _
        ByVal fieldName As String, _
        ByVal criterio As String) As List(Of DataGridViewRow)

        ' Comprobamos los valores de los parámetros.
        '

        If (fieldName = String.Empty) OrElse _
            (criterio = String.Empty) Then Return Nothing

        ' Para que funcione adecuadamente el operador Like, hay que establecer
        ' Option Compare Text a nivel del módulo donde aparezca la función,
        ' o a nivel del proyecto.
        '

        Try
            Dim query As IEnumerable(Of DataGridViewRow) = _
                From item As DataGridViewRow In dgv.Rows.Cast(Of DataGridViewRow)() _
                Where ((item.Cells(fieldName).Value IsNot DBNull.Value) AndAlso _
                      (CStr(item.Cells(fieldName).Value) Like criterio)) _
                Select item

            ' Devolvemos la consulta LINQ ejecutada.
            Return query.ToList()

        Catch ex As Exception
            Return Nothing

        End Try

    End Function

    ''' <summary>
    ''' Hace que el control DataGridView se desplace a la posición indicada
    ''' dentro del conjunto de filas que satisfacen el criterio de búsqueda
    ''' especificado en el método Find.
    ''' </summary>
    ''' <param name="dgv">Control DataGridView.</param>
    ''' <param name="position">Índice de la posición a la que desea desplazarse.</param>
    ''' <param name="listRows">Lista de objetos DataGridViewRow.</param>
    ''' <remarks></remarks>
    <Extension()> _
    Public Sub Find(ByVal dgv As DataGridView, ByVal position As Int32, _
                    ByVal
listRows As List(Of DataGridViewRow))

        If (listRows Is Nothing) Then _
            Throw New InvalidOperationException( _
                "Operación no válida. " & _
                "Establezca primero los criterios de búsqueda " & _
                "mediante el método Find."
)

        ' Seleccionamos el elemento correspondiente a la
        ' posición actual especificada.
        '

        Dim row As DataGridViewRow = listRows.ElementAtOrDefault(position)

        If ((row IsNot Nothing) AndAlso (row.Index <> -1)) Then
            ' Establecemos la celda actual si el índice de la fila
            ' no es igual a -1.
            '

            dgv.CurrentCell = dgv.Rows(row.Index).Cells(0)
            dgv.FirstDisplayedCell = dgv.CurrentCell

        End If

    End Sub

#End Region

End Module

Como habrá podido observar, he sobrecargado el método Find con dos versiones: la primera para buscar por la columna y el criterio especificado, y la segunda para desplazarnos a la posición deseada dentro del conjunto de filas encontradas. Fíjese que el primer parámetro de ambos procedimiento están definidos del tipo DataGridView, ya que es necesario para hacer que el método sea extensible de la clase DataGridView, de ésta manera podremos llamar al método Find como llamaríamos a cualquier otro método del mismo:

DataGridView1.Find( ... )

En cuanto a la consulta LINQ que utilizo, la explicación de la misma está fuera del alcance de éste breve artículo, porque tendría que dedicarle un artículo completo para que el lector, más o menos se haga una ligera idea de lo que hace exactamente la consulta. Si está interasado en LINQ, busque en la ayuda de Visual Studio LINQ to Objects.

Utilizar el método Find

Una vez que tenemos implementado nuestro método de búsqueda en el control DataGridView, va siendo hora de utilizarlo. Para ello, en su formulario de inicio inserte los siguientes controles:

Deje los nombres por defecto de los controles, y acomode los mismos para que se asemejen a la disposición que tienen en la siguiente imagen:

 

En el ejemplo voy a utilizar la base de datos Northwind de SQL Server que reside en la instancia local de Microsoft SQL Server. Si no dispone de la mencionada base de datos, o desea utilizar otro motor de base de datos, busque en el ejemplo el procedimiento GetData, y adapte la conexión y la consulta SQL de selección a sus necesidades.

Imports System.Data.SqlClient

Public Class Form1

    Private m_bs As BindingSource
    Private m_listRows As List(Of DataGridViewRow)

    Private Sub Form1_Load( _
        ByVal sender As Object, _
        ByVal e As System.EventArgs) Handles MyBase.Load

        ' Creamos una instancia del objeto BindingSource.
        '

        m_bs = New BindingSource

        ' Instalamos el controlador de evento PositionChanged.
        '

        AddHandler m_bs.PositionChanged, AddressOf PositionChanged

        ' Asociamos el control BindingNavigator
        ' con el control BindingSource.
        '

        BindingNavigator1.BindingSource = m_bs

        ' Hacemos invisibles los siguientes controles
        ' del control BindingNavigator.
        '

        BindingNavigatorAddNewItem.Visible = False
        BindingNavigatorDeleteItem.Visible = False

        ' Establecemos el valor de la propiedad Text de
        ' los siguientes controles.
        '

        Label1.Text = "&Columna de búsqueda"
        Label2.Text = "Criterio de búsqued&a"
        Button1.Text = "&Buscar"
        Me.Text = "Cómo buscar registros en un control DataGridView"

        ' Obtenemos un objeto DataTable con los datos de la
        ' tabla Customers de la base de datos Northwind.
        '

        Dim dt As DataTable = GetData()

        If (dt Is Nothing) Then Return

        ' Enlazamos el control DataGridView.
        '

        DataGridView1.DataSource = dt

        ' Rellenamos el control ComboBox con los nombres
        ' de las columnas del objeto DataGridView, que en
        ' éste caso, se corresponderán con los nombres de
        ' las columnas del objeto DataTable.
        '

        For Each col As DataGridViewColumn In DataGridView1.Columns
            ComboBox1.Items.Add(col.Name)
        Next

        ' Seleccionamos el primer elemento e indicamos
        ' el estilo que tendrá el control ComboBox.
        '

        ComboBox1.SelectedIndex = 0
        ComboBox1.DropDownStyle = ComboBoxStyle.DropDownList

    End Sub

    Private Sub Button1_Click( _
        ByVal sender As Object, _
        ByVal e As EventArgs) Handles Button1.Click

        Dim fieldName As String = ComboBox1.Text
        Dim criterio As String = TextBox1.Text

        ' Recuperamos las filas que cumplan con el criterio especificado.
        '

        m_listRows = DataGridView1.Find(fieldName, criterio)

        ' Asignamos el origen de datos del objeto BindingSource.
        '

        m_bs.DataSource = m_listRows

    End Sub

    Private Sub PositionChanged(ByVal sender As Object, ByVal e As EventArgs)

        ' Utilizamos la segunda sobrecarga del método Find,
        ' para posicionarnos en el registro que coincida
        ' con el valor de la propiedad Position del control
        ' BindingSource.
        '

        DataGridView1.Find(m_bs.Position, m_listRows)

    End Sub

    Private Function GetData() As DataTable

        Using cnn As New SqlConnection( _
            "Data Source=(local);" & _
            "Initial Catalog=Northwind;" & _
            "Integrated Security=SSPI;"
)

            Try
                Dim sql As String = _
                "SELECT CustomerID, CompanyName, ContactName, " & _
                "City, PostalCode, Country FROM Customers"

                Dim da As New SqlDataAdapter(sql, cnn)

                Dim dt As New DataTable("Customers")

                da.Fill(dt)

                Return dt

            Catch ex As Exception
                MessageBox.Show(ex.Message)
                Return Nothing

            End Try

        End Using

    End Function

End Class

¡Bueno! Ya está en condiciones de ejecutar el ejemplo. Para buscar los clientes existentes en la ciudad de Rio de Janeiro, elegiríamos la columna City y como criterio de búsqueda, escribiríamos en el control TextBox el nombre de Rio de Janeiro, haciendo acto seguido clic en el control Button para comenzar la búsqueda. Si todo ha ido bien, se habilitará el control BindingNavigator donde se mostrará el número 3, que nos indicará los tres clientes existentes en la ciudad de Rio de Janeiro. Pulse las flechas del control BindingNavigator para desplazarse por los registros encontrados.

Ahora, si deseanos conocer aquellos clientes cuyo nombre de contacto comience por la letra H, seleccionaríamos la columna ContactName y como criterio de búsqueda especificaríamos H*, donde el asterisco actuaría de comodín. Una vez pulsado el botón de búsqueda, aparecerán siete clientes cuyo nombre de contacto comienza por la letra H. Da igual que especifique el criterio de búsqueda con mayúsculas o minúsculas, porque para eso hemos utilizado la opción Compare Text en el módulo DataGridViewExtensions.

Hago especial hincapié en que deberá especificar el nombre de la columna del control DataGridView, es decir, el valor de la propiedad Name de un objeto DataGridViewColumn, que puede ser distinto del nombre del campo del objeto DataTable al que se encuentre asociada la columna. Si una columa del control DataGridView se llama DataGridViewTextBoxColumn5, éste será el nombre que deberá pasar al método Find del control DataGridView implementado.

¿Qué sucedería si no se ha encontrado ningún cliente coincidente con el criterio de búsqueda especificado? Pues absolutamente nada; el método Find devolvería el valor 0 y se le asignaría el nuevo origen de datos al control BindingSource, con lo que automáticamente se deshabilitará el control BindingNavigator, ya que el valor de la propiedad Position del control BindingSource sería -1, con lo que no habría ningún elemento con dicho índice.

Insisto en que la única utilidad que tienen los objetos BindingSource y BindingNavigator en el ejemplo indicado, es permitir desplazarse por los registros encontrados, para que el ejemplo sea más explicativo. Si solamente desea conocer el número de registros encontrados, basta con recorrer la colección de objetos DataGridViewRow devueltos por el método Find, tal y como se muestra en el siguiente ejemplo:

    Private Sub Button1_Click( _
        ByVal sender As Object, _
        ByVal e As EventArgs) Handles Button1.Click

        ' Lo primero de todo, establecemos a Nothing
        ' el origen de datos del control BindingSource.
        '

        m_bs.DataSource = Nothing

        Dim fieldName As String = ComboBox1.Text
        Dim criterio As String = TextBox1.Text

        ' Recuperamos las filas que cumplan con el criterio especificado.
        '

        Dim rows As List(Of DataGridViewRow) = _
            DataGridView1.Find(fieldName, criterio)

        ' Obtenemos el valor de la columna ContactName y el
        ' índice de la fila donde se encuentra el cliente.
        '

        For Each row As DataGridViewRow In rows
            MessageBox.Show(CStr(row.Cells("ContactName").Value), CStr(row.Index))
        Next

    End Sub

 

Otros enlaces de interés:

Índice de la colección de ejemplos de las clases del marco de trabajo de .NET

 


Enrique Martínez Montejo - 2009

NOTA: La información contenida en este artículo, así como el código fuente incluido en el mismo, se proporciona COMO ESTÁ, sin garantías de ninguna clase, y no otorga derecho alguno. Usted asume cualquier riesgo al poner en práctica, utilizar o ejecutar lo explicado, recomendado o sugerido en el presente artículo.

NOTE: The information contained in this article and source code included therein, is provided AS IS without warranty of any kind, and confers no rights. You assume any risk to implement, use or run it explained, recommended or suggested in this article.