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