Buscar este blog

domingo, 18 de abril de 2010

Threads en Windows.Forms usando el componente BackgroudWorker

Usar threads en Windows.Forms se simplifica mucho usando el componente BackgroundWorker que nos permite llevar a cabo una tarea en el fondo sin preocuparnos de tareas como la administración del thread o el acceso a controles desde un thread diferente al del form.
Les presento este pequeño tutorial que nos presenta una tarea muy sencilla que llevaremos a cabo en un trhread.

Primero que nada crearemos el formulario añadiendo los siguientes componentes:

Un NumericUpdown llamado hasta
un botón llamado inicio
un botón llamado fin
un BackgroundWorker llamado thread, con las propiedades SupportCancellation y ReportProgress en verdadero
una ProgressBar llamado progreso
una etiqueta llamada status

El formulario se vera como en la imagen:



Lo primero que hacemos es añadir el código para comenzar la tarea como no deseamos que el usuario pueda cambiar los parámetros del mismo mientras se ejecuta deshabilitaremos los controles adecuados, también noten que pasamos un parámetro al proceso esto es necesario ya que en el thread no podemos accesar los controles del formulario o tendremos un error:

private void inicio_Click(object sender, EventArgs e) {
            inicio.Enabled = false;
            fin.Enabled = true;
            hasta.Enabled = false;
            thread.RunWorkerAsync(hasta.Value);
        }

Podemos cancelar el proceso en cualquier momento usando el método CancelAsync del BackwroundWorker:

private void fin_Click(object sender, EventArgs e) {
            thread.CancelAsync();
        }

El proceso simplemente desde cero hasta el número del NumericDown que recuperamos desde el argumento que pasamos al proceso, nos dentendremos un segundo entre cada número.
Para cancelar el proceso debemos verificar constantemente la propiedad CancelationPending cuando esta sea verdadera detendremos la cuenta.
Para reportar el progreso usamos el método ReportProgress que acepta un porcentaje entero y un argumento de tipo object en el que podemos incluir otra información relevante al progreso:

private void thread_DoWork(object sender, DoWorkEventArgs e) {
            decimal total = (decimal)e.Argument;
            for(decimal i = 0; i < total; i++) {
                if(thread.CancellationPending)
                    break;
                System.Threading.Thread.Sleep(1000);
                int porcentaje = (int)((i / total) * 100);
                thread.ReportProgress(porcentaje, porcentaje.ToString() + "%");
            }
        }
Para reportar el progreso usamos el evento ProgressChanged, noten el uso del argumento pasado anteriormente:

private void thread_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            progreso.Value = e.ProgressPercentage;
            status.Text = (string)e.UserState;
        }

Cuando se termina el proceso podemos rehabilitar los controles y agregar directamente una notificación:

private void thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            inicio.Enabled = true;
            fin.Enabled = false;
            hasta.Enabled = true;
            status.Text += " Terminado";
        }

Finalmente igual que deshabilitamos los controles durante el proceso nos aseguramos que no se cierre la ventana durante el mismo:

private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            if(thread.IsBusy) {
                MessageBox.Show("Deten el proceso antes de salir");
                e.Cancel = true;
            }
        }

A continuación esta el código completo:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace ProbadorW {
    public partial class Form1 : Form {
        public Form1() {
            InitializeComponent();
        }

        private void inicio_Click(object sender, EventArgs e) {
            inicio.Enabled = false;
            fin.Enabled = true;
            hasta.Enabled = false;
            thread.RunWorkerAsync(hasta.Value);
        }
        private void fin_Click(object sender, EventArgs e) {
            thread.CancelAsync();
        }
        private void thread_DoWork(object sender, DoWorkEventArgs e) {
            decimal total = (decimal)e.Argument;
            for(decimal i = 0; i < total; i++) {
                if(thread.CancellationPending)
                    break;
                System.Threading.Thread.Sleep(1000);
                int porcentaje = (int)((i / total) * 100);
                thread.ReportProgress(porcentaje, porcentaje.ToString() + "%");
            }
        }
        private void thread_ProgressChanged(object sender, ProgressChangedEventArgs e) {
            progreso.Value = e.ProgressPercentage;
            status.Text = (string)e.UserState;
        }
        private void thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
            inicio.Enabled = true;
            fin.Enabled = false;
            hasta.Enabled = true;
            status.Text += " Terminado";
        }
        private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
            if(thread.IsBusy) {
                MessageBox.Show("Deten el proceso antes de salir");
                e.Cancel = true;
            }
        }
    }
}

Formateando codigo para Internet

Aquí les dejo la dirección de la página que uso para formatear el código que presento en este blog tiene un resultado muy vistoso y soporta varios lenguajes:

http://www.manoli.net/csharpformat/

Tamaño de la pantalla con Windows.Forms

Medir el tamaño de la pantalla con .Net es muy sencillo.
Para esto tenemos la clase Screen que nos provee de métodos para encontrar las dimensiones ya sea el tamaño total o del área de trabajo que hay que notar que no incluye el área ocupada por la barra de tareas, aquí esta el ejemplo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;

namespace ProbadorConsola {
    class Program {
        static void Main(string[] args) {
            Rectangle wArea = Screen.GetWorkingArea(new Point(1, 1));
            Rectangle bounds = Screen.GetBounds(new Point(1, 1));
            Console.WriteLine("Area de trabajo "
                + "Superior: {0}, Izquierda: {1}, Alto: {2}, Abajo: {3}, "
                + "Ancho: {4} Derecha {5}",
                wArea.Top, wArea.Left, wArea.Height, wArea.Bottom, wArea.Width
                , wArea.Right);
            Console.WriteLine("Total "
                + "Superior: {0}, Izquierda: {1}, Alto: {2}, Abajo: {3}, "
                + "Ancho: {4} Derecha {5}",
                bounds.Top, bounds.Left, bounds.Height, bounds.Bottom,
                bounds.Width, bounds.Right);
            Console.ReadLine();
        }
    }
}

La salida es esta:
Area de trabajo Superior: 0, Izquierda: 249, Alto: 900, Abajo: 900, Ancho: 2631 Derecha 2880
Total Superior: 0, Izquierda: 0, Alto: 900, Abajo: 900, Ancho: 2880 Derecha 2880

miércoles, 31 de marzo de 2010

Parseando html con HtmlAgilityPack

Con la librería HtmlAgilityPack para .Net es muy sencillo cargar archivos de html y manejar sus objetos programáticamente. Es cierto que también se puede hacer con la clase HtmlDocument de las librerías base de .Net, pero esta clase tiene como ventaja que no necesita una ventana para funcionar por lo que puede usarse en una dll o un ejecutable de consola, además puedes bajar solo lo que necesitas y no el documento con todas sus imágenes y scripts.
Para usarla declaramos una variable de la clase y usamos el método LoadHtml para cargar el documento, el argumento debe ser una cadena conteniendo el html, de momento no se pueden cargar documentos directamente:

HtmlAgilityPack.HtmlDocument doc
                = new HtmlAgilityPack.HtmlDocument();
            doc.LoadHtml(htmlstring);

Una vez que tenemos cargado el html podemos usar sus objetos programáticamente, para encontrar el nodo base del documento (que podria ser html), tenemos la propiedad DocumentNode:

doc.DocumentNode

Para buscar un nodo especifico a partir de otro nodo tenemos el método SelectNodes que toma como argumento una cadena con una expresión de XPath, por ejemplo para encontrar una tabla con ancho de 100%:

doc.DocumentNode.SelectNodes("//table[@width='100%']")[0]

Nótese que tomamos el primer elemento, ya que el método obtiene una colección con todos los elementos que cumplen la condición, alternativamente el método SelectSingleNode que obtiene solo el primer elemento.
Para obtener un elemento en particular de un nodo también podemos usar la colección ChildNodes del nodo la cual contiene todos los elementos directos del nodo:


doc.DocumentNode.SelectNodes("//table[@width='100%']")[0].ChildNodes[1]

En conclusión HtmlAgilityPack es una herramienta muy robusta y útil para lidiar con html, acepta html con errores y nos permite manejarlo de manera similar a Xml.

domingo, 21 de marzo de 2010

Propiedades automaticas

Una característica muy útil de C# es que nos permite declarar propiedades de clase casi sin escribir código simplemente escribimos el tipo de datos y el nombre de la propiedad y listo, si queremos que la  propiedad sea de solo lectura o solo escritura solo se pone como privado el accesor apropiado.
Es muy útil para declarar clases que solo almacenan datos.
Aquí esta el ejemplo:

public class Ejemplo {
        public string Prop1 {
            get;
            set;
        }
        public string Prop2 {
            get;
            private set;
        }
    }

martes, 9 de marzo de 2010

Accesando a un renglon o columna de un TableLayoutPanel

Es muy sencillo acceder a una columna o renglón específicos de un TableLayoutPanel simplemente se accede a ellos por la propiedad RowStyles o ColumnStyles, ambas son colecciones de columnas o renglones.

Por ejemplo para ocultar un renglón:

table.RowStyles[0].Height = 0;

lunes, 8 de marzo de 2010

Obteniendo el tamaño más apropiado para un Label

Los controles label de Windows.Forms tienen un método muy para determinar el tamaño que necesita el control para dibujarse, el método es GetPreferredSize, el método nos devuelve un objeto Size con el ancho y alto adecuados para el control.

Aquí les dejo el ejemplo:

label1.GetPreferredSize(new Size(0, 0));