Agenda telefónica desde XML en VB

Leer una agenda telefónica desde un archivo XML que actúa como origen de los datos, configurando columnas personalizadas de un control DataGrid, en Visual Studio 2017 con VB

En este ejercicio se diseña una sencilla agenda telefónica que lee los datos desde un archivo XML en lugar de hacerlo desde una base de datos. Las principales características del ejercicio son:

  1. Lectura desde un archivo XML, configurado como origen de los datos
  2. Mostrar los datos en un control DataGrid personalizado
  3. Mostrar registros utilizando filtros de búsqueda basados en sentencias SQL simples
  4. Creación de una clase propia de columna de DataGrid que sólo acepta números.

En primer lugar se repasa la funcionalidad del control DataGrid que es la base de esta aplicación. Es un control muy potente que facilita mucho la tarea al programador.

Control DataGrid

En ADO .NET existe un control específico para mostrar los datos de una o varias tablas contenidas en un DataSet. Se trata del DataGrid que permite crear, de manera automática, un enlace complejo con los datos ahorrando trabajo de escritura de código pues tiene implementadas muchas funciones útiles para mostrar y manipular campos de una o varias tablas. El control DataGrid de los formularios Windows Forms muestra datos en una serie de filas y columnas. En el caso más sencillo, la cuadrícula está enlazada a un origen de datos con una sola tabla que no tiene relaciones. En ese caso, los datos aparecen en filas y columnas simples, como en una hoja de cálculo. Si el control DataGrid está enlazado a datos con varias tablas relacionadas y está habilitado el desplazamiento en la cuadrícula, ésta mostrará controles de expansión en cada fila. Un control de expansión permite el desplazamiento desde una tabla primaria a una tabla secundaria. Al hacer clic en un nodo se muestra la tabla secundaria y al hacer clic en el botón Atrás se muestra la tabla primaria original. De este modo, la cuadrícula muestra las relaciones jerárquicas entre tablas.

El control DataGrid puede proporcionar una interfaz de usuario para un conjunto de datos, desplazamiento entre tablas relacionadas, dar formato enriquecido y posibilidades de edición. La presentación y la manipulación de datos son funciones independientes: el control gestiona la interfaz de usuario, mientras que las actualizaciones de datos las administra la arquitectura de enlace a datos de los formularios Windows Forms y los proveedores de datos de .NET Framework.

Cuando la cuadrícula está enlazada a un objeto DataSet (como en este ejercicio) se crean las columnas y las filas, se les da formato y se rellenan, todo ello sin intervención del programador. Para que el control DataGrid funcione debe estar enlazado a un origen de datos por medio de las propiedades DataSource y DataMember. Este enlace hace que el control DataGrid señale a una instancia de un objeto de origen de datos (como DataSet o DataTable). Mediante el control DataGrid se muestran los resultados de las acciones que se ejecutan en los datos. La mayoría de las acciones específicas para datos no se ejecutan por medio del control DataGrid , sino a través del origen de datos que puede ser:

  • DataSource: es el DataSet que contiene la tabla cuyos campos son mostrados por el DataGrid.
  • DataMember: es la tabla seleccionada dentro de las que componen el DataSet.

El DataGrid es un control complejo, potente y flexible cuya apariencia y comportamiento por defecto, si bien son válidos para la finalidad perseguida, son fácilmente modificables tanto en modo de diseño como por código. Algunas de las numerosas propiedades de un DataGrid que podemos modificar son: BackColor, Alignment, CaptionText , AlternatingBackColor, CaptionBackColor, CaptionForeColor, HeaderText, etc.

Si no asignamos ningún valor a la propiedad DataMember del DataGrid y el DataSet tuviera más de una tabla, el propio DataGrid, gracias a la funcionalidad del mecanismo de DataBinding, ofrece la posibilidad de seleccionar la tabla origen de datos mediante un nodo expandible.

El control DataGrid puede utilizarse para mostrar una única tabla o las relaciones jerárquicas entre un conjunto de tablas. Cuando el control DataGrid muestra una tabla y la propiedad AllowSorting está establecida en true, es posible ordenar los datos de nuevo haciendo clic en los encabezados de columna. El usuario puede también agregar filas y modificar celdas.

Aparte de los eventos comunes de control tales como MouseDown, Enter y Scroll, el control DataGrid admite eventos asociados con la edición y el desplazamiento dentro de la cuadrícula. La propiedad CurrentCell determina cuál es la celda seleccionada. Cuando el usuario se desplaza a una nueva celda se provoca el evento CurrentCellChanged. Cuando el usuario se desplaza a una nueva tabla (a través de relaciones primarias o secundarias) se produce el evento Navigate. El evento BackButtonClick se produce cuando el usuario hace clic en el botón Atrás mientras ve una tabla secundaria; el evento ShowParentDetailsButtonClick se produce cuando se hace clic en el icono para mostrar u ocultar filas primarias.

Filtrar registros del DataGrid

En el formulario de esta aplicación se han creado botones de comando como letras del alfabeto para filtrar los registros seleccionando solamente aquellos que empiezan por la letra elegida (equivale a las páginas por letras de las agendas telefónicas). El mecanismo de filtrado es sencillo: se utiliza una sentencia SQL de selección que incluye el operador LIKE junto a la letra del botón pulsado (cada botón tiene su propiedad Text asignada a una letra del alfabeto), como si ejecutásemos (por ejemplo, al pulsar el botón con la letra A):

SELECT * FROM AgendaTb WHERE Nombre LIKE A%.

El código que se ejecuta al pulsar en cualquiera de los botones con las letras es éste:

'filtro SQL: registros que empiecen por el texto escrito en el TextBox'
Dim vista As New DataView With {
    .Table = datos.Tables("Agendatb"),
    .RowFilter = "Nombre LIKE '" + Me.TxFiltro.Text + "%'",
    .Sort = "Nombre"
}
'nuevo origen del DataDrid: la vista personalizada'
Me.Grid.DataSource = vista
'recargar el DataGrid'
Me.Grid.Update()
Dim n As Integer
n = vista.Count
Me.Label2.Text = "Número de registros: " + CStr(n)

Además de ello se ha creado otro modo de filtrado escribiendo texto en una caja de texto por si se desea buscar según patrones de más de una letra.

DataGridTextBoxColumn personalizada (clase propia)

Para que las cajas de texto de los teléfonos acepten solamente números y no letras, se ha creado una clase
DataGridTextboxColumnNumbers
derivada (herencia) de la clase
DataGridTextboxColumn
(perteneciente a la clase DataGrid).

En la clase derivada se define un procedimiento HandleKeyPress que, en las columnas destinadas a los números de teléfono, sólo deja pasar la tecla pulsada si se trata de un número. Para disponer de este procedimiento usamos una instrucción AddHandler en el método constructor de la clase. La instrucción AddHandler asocia un evento a un manipulador de eventos. Aquí asocia la pulsación de una tecla en el TextBox con el manipulador de su evento KeyPress y refiere al procedimiento (HandleKeyPress):

'Espacio de nombre propio de la aplicación, al pertenecer al mismo espacio de nombres las 2 clases del ejercicio (Datagrid y DataGridDigitsTextBoxColumn)hay herencia entre ellas y podemos usar esta clase DataGridDigitsTextBoxColumn dentro de la otra clase DataGrid'
Namespace DataGridNumbersOnly
 
'Clase DataGridTextBoxColumnNumbers que definiremos en este archivo, hereda de DataGridTextBoxColumn'
Public Class DataGridTextBoxColumnNumbers
Inherits DataGridTextBoxColumn
'AddHandler asocia un evento a un manipulador de eventos, aquí asocia la pulsación de una tecla en el TextBox con el manipulador de su evento KeyPress y refiere al procedimiento HandleKeyPress'
AddHandler Me.TextBox.KeyPress, New System.Windows.Forms.KeyPressEventHandler(AddressOf HandleKeyPress)
 
'PROCEDIMIENTO CON SENTENCIA CASE... ELSE'
'Pocedimiento específico de configuración del cuadro de texto en cuanto a teclas pulsadas'
Private Sub HandleKeyPress(ByVal sender As Object, ByVal e As KeyPressEventArgs)
Select Case e.KeyChar ' según el valor de la tecla pulsada
	' pasan valores si la tecla pulsada es un número de 0 a 9
	Case "1"c, "2"c, "3"c, "4"c, "5"c, "6"c, "7"c, "8"c, "9"c, "0"c
		e.Handled = False
'si la propiedad Handled del objeto e se pone a True indica que la tecla ha sido "manejada" por el evento, equivale a indicar que la tecla no ha sido pulsada, pero si la propiedad Handled del objeto e se pone a False indica que la tecla todavía no ha sido "manejada" por el evento y se envía'
	Case Else ' en los demás casos, no se pasa la pulsación'
		e.Handled = True
End Select
End Sub


Personalización de las columnas del DataGrid

Los mecanismos de herencia nos permiten disponer de la clase DataGridTextboxColumnNumbers en la clase principal del programa. Al personalizar el Datagrid, creamos una columna del tipo DataGridTextboxColumn que es proporcionada por la clase DataGrid, pero esta no filtra las pulsaciones de teclas y se aplica a la columna Nombre en la que podemos necesitar escribir tanto letras como números), creamos otras 2 columnas del tipo DataGridTextboxColumnNumbers (la clase creada por nosotros mismos) que se corresponde con las 2 columnas de los teléfonos, donde solamente deja escribir números).
De esta manera tan sencilla conseguimos el efecto buscado y podemos comprobar fácilmente cómo las cajas de texto de Teléfono1 y de Teléfono2 no aceptan letras, en cambio el campo Nombre acepta letras y números indistintamente.

'Cuadro de diálogo para elegir el archivo XML'
dlgFile = New OpenFileDialog With {
	.Filter = "Archivo de datos XML(*.xml)|*.xml",
	.Title = "Selecciona el origen XML"
}

'Si el diálogo devuelve OK'
If dlgFile.ShowDialog() = DialogResult.OK Then
	'Nombre del archivo elegido, con su ruta completa, equivale al archivo XML'
	ruta = dlgFile.FileName

	'DataSet: contiene una copia de los datos, en esquema XML, independiente del proveedor, con sus elementos tablas y relaciones. Es el verdadero almacén de datos desconectados'
	datos = New DataSet

	'Rellenar el DataSet desde el archivo XML'
	datos.ReadXml(ruta)

	'configurar varias opciones del Datagrid por código'
	Me.Grid.CaptionText = "Listado de teléfonos"
	Me.Grid.CaptionBackColor = Color.Black
	Me.Grid.CaptionForeColor = Color.Yellow
	Me.Grid.GridLineColor = Color.Navy
	Me.Grid.GridLineStyle = DataGridLineStyle.Solid
	'Me.Grid.CaptionFont = New Font("Verdana", 10, FontStyle.Bold)'
	'Me.Grid.Font = New Font("Courier", 10, FontStyle.Regular)'

	'crear un objeto para estilos en el Datagrid'
	Dim estilo As New DataGridTableStyle With {
	    .MappingName = "Agendatb",
	    .BackColor = Color.White,
	    .AlternatingBackColor = Color.LightGray
	}

	'Crear objetos del tipo DataGridTextBoxColumn para cada columna de la tabla del Datagrid'
	Dim columna As New DataGridTextBoxColumn

	'Configurar cada columna del DataGrid'

	'Esta es la primera columna'
	columna = New DataGridTextBoxColumn
	columna.TextBox.MaxLength = 50
	columna.Alignment = HorizontalAlignment.Left
	columna.HeaderText = "Nombre del contacto"
	'columna del Dataset enlazada con esta columna del Datagrid'
	columna.MappingName = "Nombre"
	columna.Width = 330
	'texto que se muestra cuando la columna tiene valor null'
	columna.NullText = ""
	'añadir la columna a los estilos del Datagrid'
	estilo.GridColumnStyles.Add(columna)

	'Esta es la segunda columna (que acepta sólo números), repetimos el proceso de la primera columna pero en vez de ser DataGridTextBoxColumn será DataGridDigitsTextBoxColumn, que hemos definido en la clase DataGridDigitsTextBoxColumn del archivo DataGridDigitsTextBoxColumn.vb'
	columna = New DataGridTextBoxColumnNumbers
	columna.TextBox.MaxLength = 9
	columna.Alignment = HorizontalAlignment.Left
	columna.HeaderText = "Teléfono 1"
	'columna del Dataset enlazada con esta columna del Datagrid'
	columna.MappingName = "Telefono1"
	columna.Width = 100
	'texto que se muestra cuando la columna tiene valor null'
	columna.NullText = ""
	'añadir la columna a los estilos del Datagrid'
	estilo.GridColumnStyles.Add(columna)

	'Esta es la tercera columna (que acepta sólo números), se crea igual que la segunda columna'
	columna = New DataGridTextBoxColumnNumbers
	columna.TextBox.MaxLength = 9
	columna.Alignment = HorizontalAlignment.Left
	columna.HeaderText = "Teléfono 2"
	'columna del Dataset enlazada con esta columna del Datagrid'
	columna.MappingName = "Telefono2"
	columna.Width = 100
	'texto que se muestra cuando la columna tiene valor null'
	columna.NullText = ""
	'añadir la columna a los estilos del Datagrid'
	estilo.GridColumnStyles.Add(columna)

	'Añadir el estilo personalizado a la colección de estilos de tablas del Datagrid'
	Me.Grid.TableStyles.Add(estilo)

	'asignar Dataset al Datagrid'
	Me.Grid.DataSource = Me.datos.Tables("AgendaTb")
	'abrir la agenda por la letra A (registros ordenados)'
	'Me.TxFiltro.Text = "a".ToUpper'
	'filtro SQL: registros que empiecen por el texto escrito en el TextBox'
	Dim vista As New DataView With {
	    .Table = datos.Tables("Agendatb"),
	    .RowFilter = "Nombre LIKE '" + Me.TxFiltro.Text + "%'",
	    .Sort = "Nombre"
	}
	'nuevo origen del DataDrid: la vista personalizada'
	Me.Grid.DataSource = vista

	'recargar el DataGrid'
	Me.Grid.Update()
End If


Puedes descargar el proyecto completo desde aquí.

Nota: el ajuste automático de escala del formulario que muestra la ventana de la aplicación se puede configurar de varias maneras, lo tienes en las propiedades del formulario como AutoScaleMode, la opción con la que obtengo el mejor resultado es con Dpi.