sábado, 31 de mayo de 2008

Controles de datos

En la mayoría de aplicaciones destinadas al ámbito empresarial siempre se tiene que trabajar, de una manera u otra, con conjuntos más o menos grandes de datos. Para ayudar a los desarrolladores en estas situaciones, .NET ofrece una serie de controles de datos que permiten mostrar y manipular la información con la que se quiere trabajar. De estos controles el más conocido y el mas utilizado sin duda es el datagrid (o rejilla de datos). En la versión 2005 de Visual Studio este control pasó a llamarse DataGridView, y además venia acompañado por otros controles que pueden facilitar mucho la labor de mostrar datos por pantalla. En este post-ejemplo veremos los siguientes:
  • BindingSource: que permite entre, otras cosas, simplificar el enlace a los datos entre los controles de un formulario (por ejemplo el DataGrid, pero también cualquier otro control) y los propios datos, encargandose de la notificación de los cambios, filtros, etc...
  • BindingNavigator: que permite al usuario navegar e interactuar con los datos enlazados a un formulario proporcionando para ello una interfaz gráfica.

Lo que haremos será ver un sencillo ejemplo de cómo utilizar estos 3 controles combinados para mostrar y manipular un conjunto de datos.

Una vez añadidos los controles al formulario tenemos que indicar la relaciones entre ellos. En el evento Load del formulario escribimos lo siguiente:

private void FormMain_Load(object sender, EventArgs e) { myDataGridView.DataSource = myBindingSource; myBindingNavigator.BindingSource = myBindingSource; }


Con estas dos instrucciones estamos indicando que el BindingSource será tanto la fuente de datos del DataGrid como del BindingNavigator (el BindingNavigator "navegará" sobre los datos que haya en el BindingSource y el DataGridView mostrarà los datos que haya en el BindingSource).

Ahora solamente falta enlazar los datos propiamente dichos con el BindingSource. En principio el objeto BindingSource acepta como fuente de datos cualquier objeto que implemente alguna de las siguientes interfaces: IBindingList, IListSource o ITypedList.
En el ejemplo veremos que el conjunto de datos vendrá definido en un DataTable ya que una manera muy comun de obtener datos de una base de datos. Para hacerlo basta indicar lo siguiente:

// el método GetDataFromDB simula una llamada a una BBDD devolviendo un DataTable.
// El código se ejecutará al pulsar un botón creado a tal efecto.
private void btMostrar_Click(object sender, EventArgs e)
{
myBindingSource.DataSource = getDataFromDB();
}

En el momento de pulsar el botón los datos devueltos se añaden al BindingSource y por consiguiente son visibles desde el DataGrid. Además de mostrar los datos en el DataGrid, el objeto BindingSource permite por realizar acciones tales como filtrar y ordenar. Un ejemplo para realizar la ordenación seria el siguiente:

myBindingSource.Sort = "ID DESC";

La propiedad Sort de BindingSource espera una cadena en la que se le indica la columna del bindingSource por la que se quiere ordenar, y si se quiere hacer de manera ascendente o descendente. En nuestro ejemplo se ordena por la columna ID de manera descendente.

Por su parte a propiedad de Filtrado permite filtrar según los valores de alguno de los campos del conjunto de datos, por ejemplo:

myBindingSource.Filter = cbFiltro.Items[cbFiltro.SelectedIndex].ToString() + " LIKE '" + tbxFiltro.Text + "%'";

La propiedad Filter, espera una cadena con una sintaxis similar al lenguaje SQL, en la cual se le indica la columna por la que se quiere filtrar y el tipo de comparación que se desea. En el ejemplo se usa un combobox para indicar la columna y se hace una comparación LIKE, que indica que se buscaran todas aquellas coincidencias que empiecen igual que el texto buscado (ej: Nombre Like 'M%' devolverá todos los nombres que empiecen por M)

La interacción con BindingSource permitiria movernos por los diferentes ítems de los datos y realizar acciones cómo eliminar y/o insertar nuevos elementos.

Por ejemplo pulsando el botón de borrar se eliminaría el elemento seleccionado (del DataGrid, no del origen de datos!!) y saltaría el evento bindingNavigatorDeleteItem_Click. Una de las pegas de este evento (y del control bindingsource también) es que no proporciona información sobre cual es el item que se acaba de borrar. Si se quiere tener esta informacion se debería utilizar algún evento de borrado del propio DataGrid.

Los controles de datos, además de lo que hemos mostrado en esta escueta introducción, permiten una alta personalización y flexibilidad, haciendo la tarea de trabajar con datos en nuestras aplicaciones mucho más sencilla y potente.

Los que quieran profundizar más en ellos pueden visitar los siguientes enlaces:

BindingSource
BindingNavigator

Código fuente del ejemplo














sábado, 10 de mayo de 2008

Monitorización de directorios

.Net Framework tiene una clase especialmente útil, y además muy sencilla de usar, para la tarea de monitorizar los cambios producidos en los ficheros de un directorio. La clase en concreto es FileSystemWatcher y se encuentra situada en el namespace System.IO.

En este post vamos a ver un pequeño ejemplo de cómo utilizarla. Para ello he implementado una sencilla aplicación de consola en la que iremos viendo cómo se utilizan los diferentes métodos y propiedades de la clase.

Lo primero que tenemos que hacer es crear la instáncia de nuestro objeto FileSystemWatcher:

watcher = new System.IO.FileSystemWatcher(); //el objeto ya ha sido declarado previamente

La primera propiedad que necesitamos establecer para nuestro objeto es Path. Obviamente indica el directorio que se quiere monitorizar:

watcher.Path = directory; //directory contiene el directorio a monitorozar, por ejemplo "C:/"


Con la propiedad IncludeSubdirectories indicamos si se quiere monitorizar o no tambien los subdirectorios del directorio indicado.
watcher.IncludeSubdirectories = true;

En este punto tenemos un directorio que está siendo monitorizado, el siguiente paso es indicar que tipo de modificaciones queremos que se nos notifiquen. Para ello tenemos varios eventos a los cuales hay que suscribirse:

watcher.Deleted += new System.IO.FileSystemEventHandler(watcher_Deleted); watcher.Renamed += new System.IO.RenamedEventHandler(watcher_Renamed); watcher.Created += new FileSystemEventHandler(watcher_Created); watcher.Changed += new System.IO.FileSystemEventHandler(watcher_Changed);

Estos eventos serán lanzados por nuestro objeto cada vez que un ficchero sea borrado, renombrado ,creado o modificado respectivamente. En el método que captura el evento debemos realizar el tratamiento oportuno para cada uno de los casos.
La clase FileSystemWatcher tiene una propiedad que permite especificar para que tipo de cambios queremos que el evento Changed sea lanzado:

watcher.NotifyFilter = System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName | System.IO.NotifyFilters.LastWrite | System.IO.NotifyFilters.Size;

En este ejemplo saltaria el evento cada vez que cambiara el nombre del archivo, el nombre del directorio, la ultima fecha de modificación del fichero o su tamaño.

Si la propiedad se deja en blanco, cualquier modificación hecha en el fichero provoca que el evento sea lanzado.

Aparte de los eventos de notificación existe otro evento que es lanzado cuando se produce algún tipo de error en la monitorización:

watcher.Error += new System.IO.ErrorEventHandler(watcher_Error);

En la función que captura el evento se puede acceder a la excepción que ha provocado el error.

Si ahora mismo ejecutasemos una aplicación con el código que hemos ido viendo (añadiendo las funciones de captura de los eventos y el tratamiento de estos por supuesto), comprobariamos que no se notifica ningún tipo de cambio por mas que borremos , creeemos o modifiquemos archivos. Esto es así porque nos falta establecer el valor de una propiedad, la propiedad EnableRaisingEvents. Si establecemos el valor de esta propiedad a cierto veremos como nuestra clase watcher lanza un evneto cada vez que se modifica algun fichero del directorio. De hecho esta propiedad puede utilizarse como interruptor para iniciar o detener la monitorización del directorio. Estableciendo la propiedad y ejecutando nuestra aplicación veremos cómo efectivamente funciona correctamente:

watcher.EnableRaisingEvents = true;


Con esto tenemos suficiente para monitorizar cualquier directorio o arbol de directorios. Aunque hay otras propiedades de la clase que no hemos mencionado y que pueden ser de utilidad:

  • Filter : Permite establecer un filtro para los ficheros que se quieren monitorizar. Por ejemplo -> "*.bmp" únicamente monitorizaríamos archivos con la extensión bmp.
  • InternalBufferSize : Permite establecer el valor del buffer del objeto FileSystemWatcher.
Combinando todas estas propiedades y eventos podemos realizar casi cualquier tipo de monitorización de directorios, editándonos por ejemplo el tan utilizado método de utilizar timers para ir comprobando si llegan archivos a un directorio determinado, etc...

Descargar código fuente del ejemplo