Agenda XML VB.NET

Agenda telefónica desde un archivo XML en Visual Basic .NET

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

  1. Lectura y escritura de un archivo XML utilizando clases sencillas de usar
  2. Mostrar los datos en un control DataGrid personalizado
  3. Buscar registros utilizando filtros de búsqueda basados en sentencias SQL
  4. Creación de una clase propia de columna de DataGrid que sólo aceptan números, no letras.

Se repasará la funcionalidad del control DataGrid que es la base de esta aplicación.

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, así como 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 automáticamente.

Para que funcione el control DataGrid, 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.

  • 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, AlternatingBackColor, CaptionText, CaptionBackColor, CaptionForeColor, Alignment, 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 correspondientes a las letras del alfabeto para filtrar los registros seleccionando solamente aquellos que empiezan por la letra elegida (equivale a las páginas 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 com 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 distintas letras es éste:

Private Sub btFiltrar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btFiltrar.Click

'aplicar un filtro a la tabla AgendaTb del DataSet mediante un DataView
'DataView permite personalizar las vistas de las tablas
'Si el archivo XML SÍ está abierto
If abierto = True Then

Try
If Me.txFiltro.Text.Length > 0 Then
Dim vista As New DataView
vista.Table = datos.Tables("AgendaTb")
'filtro SQL: registros que empiecen por el texto escrito en el TextBox
vista.RowFilter = "Nombre LIKE '" + Me.txFiltro.Text + "%'"
vista.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
'Dim aviso4 As String = CStr(a) + " Registros recuperados "
'MessageBox.Show(aviso4, "Filtrado", MessageBoxButtons.OK)
Me.Label2.Text = "Número de registros: " + CStr(n)
End If
Catch pollo As Exception
MessageBox.Show(pollo.Message.ToUpper, "Aviso al usuario", MessageBoxButtons.OK)
End Try

'Si el archivo XML NO está abierto
'Else
' Dim aviso4 As String = "Primero has de cargar el archivo XML"
' MessageBox.Show(aviso4.ToUpper, "Aviso al usuario")
End If

End Sub

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 que 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 descrito más arriba (HandleKeyPress):

' FORMULARIO SECUNDARIO: DataGridTextBoxColumnNumbers.vb
' --------------------------------------
Option Strict Off
Option Explicit On

Espacios de nombres necesarios
Imports Microsoft.VisualBasic
Imports System
Imports System.ComponentModel
Imports System.Windows.Forms
'Espacio de nombre propio de la aplicación, al pertenecer a este 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
'Método constructor de la clase
Public Sub New()
'MyBase se refiere a la clase madre DataGridTextBoxColumn, de la que hereda
MyBase.New()
'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
AddHandler Me.TextBox.KeyPress, New System.Windows.Forms.KeyPressEventHandler(AddressOf HandleKeyPress)
End Sub

'PROCEDIMIENTO CON SENTENCIA IF
'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)
'- ignorar si no es un número
'- ignorar si hay más de 9 caracteres
'If (Not (System.Char.IsDigit(e.KeyChar))) Then
'poner e.Handled a False equivale a decir que la tecla no ha sido manejada por el evento
'y, por tanto, la pulsación se envía, pero si e.Handled es True, equivale a decir que la tecla
'ya ha sido manejada por el evento (como si la tecla no hubiese sido pulsada, por tanto,
'ya no se envía esa pulsación).
'e.Handled = True
'End If
'If (Me.TextBox.Text.Length >= 10) AndAlso (Me.TextBox.SelectionLength = 0) Then
'e.Handled = True
'End If
'End Sub

'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 indico
' 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 indico 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

End Class
End Namespace

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
(la clase proporcionada por la clase DataGrid, no filtra las pulsaciones de teclas, corresponde a la columna Nombre, en la que podemos necesitar esribir tanto letras como números) y otras 2 columnas del tipo
DataGridTextboxColumnNumbers (la clase creada por nosotros mismos, corresponde a las 2 columnas de los teléfonos, solamente deja escribir números):

Private Sub btCargar_XLM(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btCargar.Click
'Si el archivo XML SÍ está abierto, simplemente informar

If abierto = True Then
Dim aviso As String = "El archivo XML ya está cargado en el DataSet"
MessageBox.Show(aviso.ToUpper, "Aviso al usuario", MessageBoxButtons.OK)
End If

'Si el archivo XML NO está abierto
If abierto = False Then
Try
'Cuadro de diálogo para elegir el archivo XML
dlgFile = New OpenFileDialog
dlgFile.Filter = "Archivo de datos XML(*.xml)|*.xml"
dlgFile.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)
'si abrimos un archivo XML que no es el apropiado salir sin hacer nada
'podemos saber si es el XML correcto comprobando si contiene la tabla AgendaTb
'en la 2ª posición (Tables(1)), la primera posición en el XML es dataroot (Tables(0))
'MsgBox(Me.datos.Tables(1).TableName.ToString)
If Not Me.datos.Tables(1).TableName = "AgendaTb" Then
Exit Sub
End If

'valores por defecto de los campos de la fila de nueva inserción
'para que no aparezca el texto "(null)" al pasar el cursor a esa fila,
'también se puede configurar con la propiedada NullText de las
'DataGridTextBoxColumn que se crean para cada columna del DataGrid
'Dim i As Integer
'For i = 0 To (Me.datos.Tables("AgendaTb").Columns.Count - 1)
' Me.datos.Tables("AgendaTb").Columns(i).DefaultValue = ""
'Next

'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
estilo.MappingName = "AgendaTb"
estilo.BackColor = Color.White
estilo.AlternatingBackColor = Color.LightGray

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

'Configurar cada columna

'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)

'CREACION DEL ESTILO PARA LAS COLUMNAS ESPECIALES
'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
Dim vista As New DataView
vista.Table = datos.Tables("AgendaTb")
'filtro SQL: registros que empiecen por el texto escrito en el TextBox
vista.RowFilter = "Nombre LIKE '" + Me.txFiltro.Text + "%'"
vista.Sort = "Nombre"
'nuevo origen del DataDrid: la vista personalizada
Me.Grid.DataSource = vista

'contar el número de registros que presenta el DataGrid
Dim n As Integer
n = vista.Count
Label2.Text = "Número de registros: " + CStr(n)

'recargar el DataGrid
Me.Grid.Update()

'Habilitar los botones Actualizar y Quitar filtro
'crear los Tooltips para esos controles
Me.btActualizar.Enabled = True
Me.ToolTip1.SetToolTip(Me.btActualizar, "Modificar la base de datos con los cambios realizados en el DataSet")
Me.btQuitar.Enabled = True
Me.ToolTip1.SetToolTip(Me.btQuitar, "Mostrar de nuevo todos los registros")

'Habilitar la escritura en el Texbox
Me.txFiltro.ReadOnly = False

'Para saber que el archivo XML SÍ está abierto
abierto = True
End If

Catch pollo As Exception
MessageBox.Show(pollo.Message.ToUpper, "Aviso al usuario", MessageBoxButtons.OK)
End Try
End If
End Sub

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.

Anuncios

2 pensamientos en “Agenda XML VB.NET

  1. Pepín ¿tú también por aquí? Gracias, se agradece el comentario.

Los comentarios están cerrados.