Ago 29 2008

Como filtrar las filas de un DataTable usando un RowFilter (VB.Net) {

Tag: VB.Net

Más de una vez me he encontrado con un formulario en el cual muestro datos con un DataGridView, pero los datos a mostrar son demasiados, entonces es bueno agregar algún filtro para minimizar la cantidad de registros mostrados.

Para hacer esto hay 2 opciones, la más lenta sería recargar los datos desde la base aplicando filtros y la más rápida, ya que funciona en memoria es filtrar los datos que ya estamos mostrando en pantalla. Para realizar esto, en este ejemplo utilizaremos un DataTable con datos de alumnos el cual filtraremos dinámicamente.

Para empezar con este ejemplo necesitaremos un Form que tenga un CheckBox para indicar si se debe aplicar el filtro o no, un ComboBox para seleccionar el campo a filtrar, un TextBox para ingresar el valor del filtro y un DataGrid para mostrar los datos.

Lo primero que haremos será llenar el ComboBox con el nombre de las columnas de nuestro DataTable:

Private Sub LlenarComboColumnas()
	If TodosAlumnos IsNot Nothing Then ' TodosAlumnos es el DataTable
		For Each c As DataColumn In TodosAlumnos.Columns
			Me.cmbCampo.Items.Add(c.ColumnName)
		Next
	End If
End Sub

Luego agregaremos un método que se encargará de manejar 3 eventos: CheckBox.CheckedChanged, ComboBox.SelectedIndexChanged y TextBox.TextChanged. En éste método se optará por aplicar el filtro o mostrar todos los registros nuevamente:

Private Sub AplicarFiltro(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles chkFiltro.CheckedChanged, cmbCampo.SelectedIndexChanged, txtValor.TextChanged
	cmbCampo.Enabled = chkFiltro.Checked ' Habilitar o Deshabilitar el ComboBox y TextBox
	txtValor.Enabled = chkFiltro.Checked
 
	If chkFiltro.Checked Then
		Filtrar()
	Else
		Me.cmbCampo.SelectedIndex = 0 ' Resetear el filtro
		Me.txtValor.Text = String.Empty
		AlumnosFiltrados = TodosAlumnos.Copy() ' Mostrar todos los datos nuevamente
		Me.DataGridView1.DataSource = AlumnosFiltrados.DefaultView
	End If
End Sub

Ahora nos resta filtrar, lo que haremos en este caso, ya que el ejemplo es simple, será chequear si el tipo de datos correspondiente a la columna seleccionada es String usaremos como condición LIKE y agregaremos un comodín (*) al final del valor, si en cambio es Integer usaremos = y chequearemos que el valor ingresado sea numérico.

Private Sub Filtrar()
	Try
		AlumnosFiltrados = TodosAlumnos.Copy()
 
		If chkFiltro.Checked AndAlso Me.cmbCampo.SelectedIndex >= 0 AndAlso Not String.IsNullOrEmpty(Me.txtValor.Text) Then
			Dim columna As String = Me.cmbCampo.SelectedItem.ToString()
			Dim condicion As String = "="
			Dim valor As String = txtValor.Text
			' Si no se obtienen todos los datos no se aplica el filtro
			If AlumnosFiltrados.Columns(columna).DataType Is GetType(String) Then
				condicion = "LIKE"
				valor = String.Format("'{0}*'", valor) ' Comodín al final para obtener los valores que empiezan con 'valor'
			ElseIf AlumnosFiltrados.Columns(columna).DataType Is GetType(Integer) Then
				If Not IsNumeric(valor) Then ' Chequear que sea numérico
					Throw New ArgumentException("El valor ingresado no es correcto. Debe ingresar un valor numérico.")
				End If
			End If
 
			Me.AlumnosFiltrados.DefaultView.RowFilter = String.Format("{0} {1} {2}", columna, condicion, valor)
		End If
 
		Me.DataGridView1.DataSource = Me.AlumnosFiltrados.DefaultView ' Mostramos los datos filtrados
	Catch ex As Exception
		MostrarExcepcion(ex)
	End Try
End Sub

Para probar este ejemplo puedes descargar una solución de Visual Studio 2008 (37.03 KB).

}


Ago 14 2008

Como filtrar una lista de objetos con LINQ y ver las propiedades por Reflection (VB.Net) {

Tag: LINQ, VB.Net

LINQ es un proyecto de Microsoft que agrega a los lenguajes del .Net Framework la capacidad de utilizar consultas de sintáxis parecida a SQL. Inicialmente sólo se itegró a C# y VB.Net.

En el sitio de Microsoft hay una traducción de la página del proyecto LINQ donde se puede leer más al respecto.

En nuestro ejemplo utilizaremos LINQ To Objects, que es el término que se utiliza para definir la utilización de LINQ con cualquier colección que implemente las interfaces IEnumerable o IEnumerable(T).

En nuestro caso hemos definido una clase Objeto que tiene las propiedades ID : Integer, Tipo : String, Tamaño : Decimal y Habilitado : Boolean. Para utilizar LINQ hemos creado una colección del tipo List(Of Objeto) la cual cargamos con varias instancias de nuestra clase Objeto.

La idea es crear un filtro dinámico, para dar la posibilidad al usuario de seleccionar el valor para 2 propiedades y crear un filtro con OR o AND, o sea Propiedad1 = a OR/AND Propiedad2 = b.

Nuestra intención es poder reutilizar el código, entonces no podemos crear 2 ComboBox y cargar las propiedades a mano, por lo que recurriremos al namespace System.Reflection para extraer las propiedades de nuestra clase Objeto.

Según Microsoft: El espacio de nombres System.Reflection contiene clases e interfaces que proporcionan una vista administrada de los campos, los métodos y los tipos cargados, con la posibilidad de crear e invocar tipos dinámicamente.

Private Sub CargarPropiedades()
	Dim propiedades1 As New List(Of String)
	Dim propiedades2 As New List(Of String)
 
	' Recorremos todos los miembros de la clase
	For Each propiedad As System.Reflection.MemberInfo In System.Reflection.Assembly.GetExecutingAssembly.GetType("Objeto").GetMembers()
		' Filtramos para seleccionar solamente las propiedades
		If propiedad.MemberType = System.Reflection.MemberTypes.Property Then
			propiedades1.Add(propiedad.Name)
			propiedades2.Add(propiedad.Name)
		End If
	Next
 
	Me.cmbField1.DataSource = propiedades1 'Asignamos el DataSource a nuestros ComboBox
	Me.cmbField2.DataSource = propiedades2
End Sub

Con ese código tan simple ya tenemos 2 combos cargados con las propiedades de nuestra clase Objeto. En este caso ob tuvimos el tipo Objeto del mismo ensamblado, (Assembly), en el que estamos trabajando, pero pudimos haberlo buscado en otro Assembly.
Cabe aclarar que se utilizan 2 listas, una para cada combo porque si seteamos la misma lista como DataSource para los 2 ComboBox, al cambiar el valor seleccionado en uno, se cambia también en el otro.

Sólo resta armar nuestra sentencia LINQ para filtrar la coleeción de Objeto. Para ello tenemos que tomar: 1) la primera propiedad seleccionada por el usuario, 2) el valor que ingresó para esa propiedad, 3) la condición que seleccionó (AND/OR), 4) la segunda propiedad seleccionada, 5) el valor ingresado para la segunda propiedad.

Lo que haremos será armar la sentencia de la siguiente manera:

  • Seleccionar todos los objetos con PropiedadSeleccionada1 = ValorIngresado1
  • AND Si el usuario seleccionó ‘AND’ PropiedadSeleccionada2 = ValorIngresado2, sino True
  • OR Si el usuario seleccionó ‘OR’ PropiedadSeleccionada2 = ValorIngresado2 sino False.

Entonces la sentencia, en pseudo-código se vería de una se las siguientes maneras:

  • Seleccionar todos los objetos con Propiedad1 = Valor1 AND Propiedad2 = Valor2 OR False
  • Seleccionar todos los objetos con Propiedad1 = Valor2 AND True OR Propiedad2 = Valor2

De esa manera con una misma sentencia se cubren los 2 casos, veamos el código:

Private Sub btnSelect_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSelect.Click
	If ValidarEntrada() Then ' Chequeaar que el usuario haya ingresado todos los valores necesarios
		Try
			' LINQ To Objects
			Dim resSelect = From obj In mObjetos _
				Select obj _
				Where obj.GetType().GetProperty(Me.cmbField1.SelectedItem.ToString()).GetValue(obj, Nothing) = Me.txtCond1.Text _
				And (IIf(cmbJoin.SelectedItem = "AND", obj.GetType().GetProperty(Me.cmbField2.SelectedItem.ToString()).GetValue(obj, Nothing) = Me.txtCond2.Text, True)) _
				Or (IIf(cmbJoin.SelectedItem = "OR", obj.GetType().GetProperty(Me.cmbField2.SelectedItem.ToString()).GetValue(obj, Nothing) = Me.txtCond2.Text, False))
 
			Me.lstSelect.Items.Clear() ' Limpiar la lista antes de mostrar el resultado de la consulta
			For Each item In resSelect
				Me.lstSelect.Items.Add(item)
			Next
		Catch ex As Exception
			If MessageBox.Show(String.Format("Error: {0}{1}Desea ver más información acerca de este error?", ex.Message.ToString(), Environment.NewLine), "Error", MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk) = Windows.Forms.DialogResult.Yes Then
				MessageBox.Show(ex.ToString(), "Detalle del error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation)
			End If
		End Try
	End If
End Sub

Descargar el ejemplo de LINQ y reflection (42.24 KB)

}


Página 1 de 11