Clase con progress_Info
[Informar del progreso en una clase con C#]

Fecha: 18 de noviembre de 2004
Autor: Antonio Cuesta García ACUESTA@terra.es

 

.

Introducción:

Bueno la idea de este articulo es debida a que por regla general todos los programas, utilidades, etc. realiza ciertos procesos de larga duración, y aunque nosotros pensemos (en mi caso particular), que el usuario no necesita una información visual (que pueda ir en contra de mas rapidez en dicho proceso), cuando somos nosotros quienes usamos un programa si que nos gusta saber si lo que esta haciendo tardara mas o menos, y en que punto se encuentra, cosa que queda muy bien reflejada con una barra de progreso.

No es la intención de este articulo mostrar el uso del control ProgressBar, sino la de implementar en nuestros procesos (pesados) un mecanismo para que puedan transmitir una información de su progreso de forma útil y sencilla tanto si es para nuestro uso como si es para el uso y disfrute de terceros.

Evolución de la idea inicial.

El resultado de las pruebas fueron tres posibles soluciones, ya que gracias a ellas se puede ver claramente una evolución en sus resultados, para recoger las tres en este artículo se crea un programa para Windows, compuesto por un solo formulario con tres botones para llamar a cada una de las soluciones propuestas y un ProgressBar.

Primera opción (primer botón).

Esta es la opción más sencilla, y realmente solo es una muestra del proceso elegido para demostrar la idea.

El proceso utilizado es muy sencillo, (ya que después cada desarrollador utilizar el suyo, por ejemplo: copiar archivos, actualizar bases de datos con nuevos registros, etc.), lo único que si es importante, es que antes de comenzar el proceso tengamos información sobre el numero total de archivos, registros, etc. que tenemos que procesar, ya que sin esta información el conocimiento del punto actual de ejecución de nuestro proceso no nos resultaría útil.

A continuación se muestra el proceso:

private void ProcesoTardon()
{
    int inicio=0;         // Valor de inicio
    int fin=200000;       // Valor de finalización

    progressBar1.Minimun=inicio;
    progressBar1.Maximun=fin;

    // El proceso en si mismo.

    for (int i=inicio;i<=fin;i++)
       this.progressBar1.Value=i;
}

Cono se puede ver es simplemente un bucle for, sin mayor importancia.

Ventajas de utilizar este método:
* El código a escribir es muy pequeño, buena idea para una solución rápida.

Desventajas de utilizar este método:
* La principal, es que no se puede reutilizar, es decir, si queremos que este proceso se ejecute desde otro formulario, tendríamos que rescribir todo el código, ya que depende del control ProgressBar progressBar1, que esta en dicho formulario; de hecho por ese motivo se ha creado dentro de la clase del formulario principal y se le declara como private.

Segunda opción (segundo botón).

Bueno, pues esta opción, también es muy simple, lo que se pretende es reutilizar nuestro código tardón, ya que como se indica en las desventajas anteriores si queremos ejecutar dicho proceso desde dos formularios distintos, tendríamos que copiar el código con los problemas que puede traer esto.

Para solucionarlo, se crea una clase que yo llamo ClaseTardona (una ocurrencia rápida), y que si nuestro proyecto es grande y lo desarrollamos en tres capas podría ir incluido en la Capa de Negocios, donde quedaría muy bien.

La clase resultante seria la siguiente:

public class ClaseTardona
{
    private int _inicio;         // Valor de inicio
    private int _final;          // Valor de finalización

    public ClaseTardona()
    {
        _inicio=0;
        _final=200000;
    }

    public void Procesar(ProgressBar PBar)
    {
        PBar.Minimun=_inicio;
        PBar.Maximun=_final;

        // El proceso en si mismo.

        for (int i=_inicio;i<=_final;i++)
            PBar.Value=i;
    }

    public int Inicio
    {
        get { return _inicio; }
    }

    public int Final
    {
        get { return _final; }
    }
}

Y la llamada desde nuestro button2:

private void button2_Click(object sender, System.EventArgs e)
{
    ClaseTardona c1=new ClaseTardona();
    c1.Procesar(this.progressBar1);
}

Como ya indicaba es una clase muy sencilla, y solo aporta dos propiedades de solo lectura, Inicio y Final, que realmente no se utilizan en el ejemplo, pero dan un carácter informativo a la clase.

En el constructor realizaríamos la asignación de los valores tanto inicial como final, que en este caso simplemente es una asignación numérica fija (para el ejemplo), pero si nuestra clase fuera la encargada de facturar albaranes, en el constructor obtendríamos el numero del albaranes que están pendientes de facturar, etc.

El método Procesar, (que es el único de la clase), es el que se encarga de realizar el trabajo, y además de informarnos del punto de avance donde se encuentra nuestro proceso, toma como argumento una instancia de un ProgressBar que utiliza para actualizar su valor, y que en nuestro caso es el progressBar1 de nuestro formulario, esto para mi personalmente es una chapuza, ya que en este punto tenemos algo que funciona y que es reutilizable, pero si en nuestro nuevo formulario no utilizamos un ProgressBar, no podremos facturar nuestros albaranes, (ni contar hasta 200000), etc. Esto lo podríamos solucionar simplemente sobrecargando el método Procesar sin argumentos, pero en este caso no tendríamos la información que ha propiciado nuestro estudio, además puede darse el caso de querer utilizar otra forma diferente para indicar al usuario el tiempo que queda de proceso y el punto donde se encuentra, por ejemplo: una barra de progreso creada por nosotros, o un simple control Label, etc., lo que limitara nuestra bonita clase.

Tercera opción (tercer botón).

Después de estas dos opciones que simplemente nos dan una idea del problema y de como solucionarlo de forma rápida (sobre todo la con la primera), comentare la idea inicial que yo tenia, y que no es otra que realizar una clase que realice el trabajo y que sea lo mas independiente posible, para que se pueda utilizar en diferentes partes de una solución, reutilizarla en otro proyecto, e incluso dejarla lista para que pueda ser utilizada correctamente por otros programadores, sin que imponga a los mismos limitaciones a la hora de crear la interfaz de usuario, pudiendo mostrar o no la información del progreso, y si se quiere mostrar que se pueda hacer como quiera cada uno y no por imposición con un control ProgressBar.

¨Como realizamos esto?, de forma muy sencilla, añadiendo a nuestra clase un evento que notificara los cambios de posición en el progreso de nuestro proceso, así el usuario de dicha clase podrá optar por controlar dicho evento como el quiera, o no controlarlo.

Para esto, primero creo una clase que hereda de System.EventArgs, que yo llamo PosicEventArgs, que tendrá una propiedad publica Posic, donde se guardara el valor correspondiente a la posición actual del progreso, y un constructor con este valor como argumento, quedando como sigue:

public class PosicEventArgs:EventArgs
{
    public int Posic;         // Valor de la posición inicial

    public PosicEventArgs(int Pos):base()
    {
        this.Posic=Pos;
    }
}

Después se declara el delegado publico CambioPosHandler, quedando de la siguiente manera:

public delegate void CambioPosHandler(objeto o,PosicEventArgs ev);

Y después de todo esto nuestra nueva clase que en este caso (para poder convivir con la anterior ClaseTardona), he llamado ClaseTardona2:

public class ClaseTardona2
{
    private int _inicio;         // Valor de inicio
    private int _final;          // Valor de finalización

    public event CambioPosHandler CambioPosic;

    public ClaseTardona2()
    {
        _inicio=0;
        _final=200000;
    }

    public void Procesar()
    {
        // El proceso en si mismo.

        for (int i=_inicio;i<=_final;i++)
            OnCambioPosic(new PosicEventArgs(i));
    }

    protected virtual void OnCambioPosic(PosicEventArgs e)
    {
        if (CambioPosic!=null)
            CambioPosic(this,e);
    }

    public int Inicio
    {
        get { return _inicio; }
    }

    public int Final
    {
        get { return _final; }
    }
}

El desarrollo de la clase es tan simple como la anterior, lo único que tenemos un método protegido OnCambioPosic, encargado de lanzar el evento CambioPosic, si es que esta instanciado, y este método es llamado desde el método Procesar cada vez que se modifica el estado del proceso.

Como remate una muestra del uso de nuestra clase en el formulario de prueba.

private void button_Click(object sender, System.EventArgs e)
{
    ClaseTardona c1=new ClaseTardona2();         // Creamos una instancia de ClaseTardona2

    // Actualizamos los valores de progressBar1
    this.progressBar1.Minimun = c1.Inicio;
    this.progressBar1.Maximun = c1.Final;

    // Añadimos un delegado al evento
    c1.CambioPosic+=new CambioPosHandler(c1_CambioPosic);
    c1.Procesar();                              // Ejecutamos el proceso
}
private void c1_CambioPosic(object o, PosicEventArgs ev)
{
    this.progressBar1.Value = ev.Posic;    // Refrescamos la información de progressBar1
}

Bueno con este articulo se ha intentado mostrar diferentes manera de realizar algo, se puede ver que con un poco de código añadido, y aprovechando técnicas como son los delegados y los eventos podemos crear nuestras clases dotándolas de una gran funcionalidad.

Me despido con un saludo a todos y esperando que este articulo sea útil a alguien.



ir al índice

Fichero con el código de ejemplo: ACUESTA_AppConProgreso.zip - Tamaño 9,13 KB