viernes, 19 de septiembre de 2014

El control BackgroudWorker de Visual Studio


El control BackgroundWorker

Una de las dificultades en la programación en capas es la de mostrar elementos en la interfaz gráfica de la aplicación mientras se realiza un proceso que lleva mucho tiempo en la capa de datos. Por ejemplo la clásica aplicación que muestra una barra de progreso de la tarea que se está ejecutando dentro de una clase que obtiene una gran información de una base de datos o que procesa una gran cantidad de datos. Cuando colocaba toda la funcionalidad dentro del formulario no era problema, pero ¿Cómo hacerlo ahora que mi clase está separada del formulario?. Primero lo resolví con un evento dentro de la clase que procesa la gran cantidad de información. Este evento se dispararía cada vez que quisiera que la barra de progreso se actualizara, pero me quedaba el problema de poder cancelar el proceso antes de que terminara. Entonces pensé en hilos (threads). Es complicado trabajar con hilos de ejecución, pero afortunadamente encontré un control que cubre esta funcionalidad en visual studio y se llama BackgroundWorker.

El control BackgroudWorker permite ejecutar una operación en un subproceso (hilo) dedicado e independiente. La figura 1 muestra el ícono del control.

Figura 1 Icono del backgroundWorker

El control BackgroundWorker no es visible al usuario por lo que al agregarse se coloca en la barra debajo del formulario, donde se colocan otros controles como el timer por ejemplo.

Los eventos que maneja el control BackGroundWorker son DoWork donde se coloca el código del proceso que se va a ejecutar, el ProgressChanged que sirve para actualizar los controles de avance, por ejemplo un progressbar y el RunWorkCompleted que es el evento que indica el final del proceso.

Para ejecutar el subproceso se utiliza el método RunWorkerAsync del control, que puede recibir cualquier objeto como argumento. Es importante definir este objeto con todos los parámetros necesarios para el método que va a realizar el proceso dentro de DoWork.

El método CancelAsync sirve para cancelar el proceso, siempre y cuando la propiedad WorkerSupportCancellation del control BackgroundWorker este en true. El objeto que realiza el proceso debe revisar la propiedad CancellationPending del objeto BackgroundWorker y poner a true la propiedad Cancel del objeto de argumentos del evento para que se realice la cancelación.

El siguiente código muestra un ejemplo de como usar el control BackgroundWorker:

Primero defino la clase clsParamsProceso que me servirá para pasar todos los parámetros que requiero para realizar el proceso. A continuación escribo el código que llevará cada manejador de eventos del BackgroudWorker.

public class clsParamsProceso
    {
        private string _NombreArchivo;

        public string NombreArchivo
        {
            get { return _NombreArchivo; }
            set { _NombreArchivo = value; }
        }

        private int _Nivel1;

        public int Nivel1
        {
            get { return _Nivel1; }
            set { _Nivel1 = value; }
        }

        private int _Nivel2;

        public int Nivel2
        {
            get { return _Nivel2; }
            set { _Nivel2 = value; }
        }

        private int _Nivel3;

        public int Nivel3
        {
            get { return _Nivel3; }
            set { _Nivel3 = value; }
        }

        private int _Nivel4;

        public int Nivel4
        {
            get { return _Nivel4; }
            set { _Nivel4 = value; }
        }

        private string _CuentaBanco;

        public string CuentaBanco
        {
            get { return _CuentaBanco; }
            set { _CuentaBanco = value; }
        }

        private int _FilaInicio;

        public int FilaInicio
        {
            get { return _FilaInicio; }
            set { _FilaInicio = value; }
        }

        private string _ArchivoRangos;

        public string ArchivoRangos
        {
            get { return _ArchivoRangos; }
            set { _ArchivoRangos = value; }
        }
    }

        private void btnProcCatalog_Click(object sender, EventArgs e)
        {
            clsParamsProceso parametros = new clsParamsProceso();
                if (txtNomArchivoCat.Text != "")
                {
                    if (!bgwProgreso.IsBusy)
                    {
                        tssEstado.Text = "Convirtiendo Catálogo...";
                        parametros.NombreArchivo = opdlgPricnipal.FileName;
                        parametros.Nivel1 = Convert.ToInt32(txtLonNivel1.Text);
                        parametros.Nivel2 = Convert.ToInt32(txtLonNivel2.Text);
                        parametros.Nivel3 = Convert.ToInt32(txtLonNivel3.Text);
                        parametros.Nivel4 = Convert.ToInt32(txtLonNivel4.Text);
                        parametros.CuentaBanco = txtCuentaBanco.Text;
                        parametros.FilaInicio = Convert.ToInt32(txtInicio.Text);
                        bgwProgreso.RunWorkerAsync(parametros);
                        HabilitarControles(false);
                    }
                }
                else
                    MessageBox.Show("No a seleccionado ningún archivo a convertir""Error captura de datos", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        private void bgwProgreso_DoWork(object sender, DoWorkEventArgs e)
        {
            ctr.GenerarCatalogo(bgwProgreso, e);
            }

        private void bgwProgreso_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            prbPrincipal.Value = e.ProgressPercentage;
        }

        private void bgwProgreso_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            prbPrincipal.Value = 0;
            HabilitarControles(true);
            if (e.Error != null)
            {
                tssEstado.Text = "Error: " + e.Error.Message;
            }
            else if (e.Cancelled)
            {
               tssEstado.Text = "Conversión de catálogo Cancelada";
            }
            else
            {
                        tssEstado.Text = "Conversión de catálogo finalizada exitósamente";
                }
            }

Finalmente, dentro de mi clase control, el metodo GenerarCatalogo quedaría de la siguiente forma:
public int GenerarCatalogo(BackgroundWorker trabajador,
                            DoWorkEventArgs e)
{
    int Avance = 0;
    for (int i = 0; i < 10000; i++)
    {
        //Lineas de codigo del proceso....
        Avance = (int)(i * 100.0 / 10000);
        trabajador.ReportProgress(Avance);
        if (trabajador.CancellationPending)
        {
            e.Cancel = true;
            break;
        }
    }
}

Cada ciclo calculo el avance y se lo envío al método ReportProgress del objeto BackgroundWorker

Espero que este control les sea de utilidad y comiencen a experimentar con hilos de procesos, un tema bastante interesante, sobre todo en la ejecución en paralelo.

viernes, 6 de junio de 2014

Respuesta al Reto 5 Clases Abstractas


Las clases abstractas son clases que sirven para definir las propiedades y el comportamiento de sus clases derivadas. Establecen el límite conceptual del objeto identificado en el diseño de la solución al problema a resolver. Es conocida como la super clase y no se puede instanciar un objeto de ella. La idea es heredar clases instanciables a partir de esta clase. Nos puede servir para especificar un comportamiento mínimo de una clase, para que en un grupo de programadores los que requieran este tipo deriven sus clases a partir de ella. Las clases abstractas existen en los lenguajes mas populares como C#, Java y PHP.

Es complicado diseñar los elementos que van a componer la solución al problema que se pretende resolver con un programa si no se tiene un amplio conocimiento del dominio de ese problema. Asi que proponer en base a solo al nombre de una clase, no es posible. La idea de este reto es motivar a los que deseen resolverlo a imaginarse y a pensar que es lo que representa el concepto de un producto y de un cliente en un sistema de punto de venta.


En el caso del producto podemos pensar en una lata de frijoles en una tienda de abarrotes, un par de zapatos en una zapatería o un juego de herramientas en una ferretería. Aqui lo importante es determinar por medio de una clase los límites de esa abstracción. En lo que a mi experiencia se refiere, pensar en un artículo o un producto, me hace pensar en todas las posibilidades que cada uno de mis clientes me ha planteado: que tenga descripción, que tenga una clave fácil de recordar por los empleados, que tenga un código de barras, que se sepa a cuanto se compro, es decir su costo, a cuánto se va a vender, su porcentaje de iva. Características asociadas con el inventario como la cantidad que se tiene en la tienda (existencias), el límite de esas existencias para pedir mas (cantidad en stock o punto de reorden), que tipo de costeo se va a manejar (costo promedio, primeras entradas, primeras salidas, identificado), cuál va a ser su ubicación en la tienda, su unidad de medida. Otras quizás mas específicas como a que categoría pertenece, si el producto se vende suelto o por paquete o si tiene una gama de colores. En realidad las posibilidades son enormes.

Lo que yo propongo es agrupar los atributos más básicos en la clase abstracta y después en las clases concretas ir generando clases mas complejas, que vayan incluyendo las características adicionales mas específicas.

Mi propuesta se muestra en la siguiente figura:



Observen el esterotipo de la clase encerrada entre <<>>. Solo se tiene una clase abstracta y las demás son clases concretas (clases que si se pueden instanciar).

A ver que les parece, espero sus opiniones y críticas. La idea de poner las clases concretas es para mostrarles donde pongo las demás propiedades que no están definidas en la clase abstracta. En mi diseño intenté poner los atributos generales mínimos que definen a la clase productos y posteriormente fuí agregando características mas complejas, como el caso de la unidad de medida, que yo lo planteo como una clase y la categoría a la que pertenece el producto.

Con respecto a la clase abstracta de clientes, en un sistema de punto de venta, el cliente se define como la persona que compra en la tienda. Esta persona puede solicitar una factura, por lo que es importante tener sus datos fiscales, asi como los datos de su dirección. Ademas, un cliente puede ser una empresa, por lo que la persona que llega a comprar puede que sea la persona que hace el contacto.

Es por ello que mi propuesta de clase abstracta y sus clases concretas, es la siguiente:



Nuevamente espero sus comentarios.



Conclusión


En el diseño de clases es necesario contar con una vista jerárquica de interacción de clases que permita ir definiendo objetos de lo general a lo particular. Esto facilita la creación de nuevas clases y el reuso de las existentes en una forma ordenada. Anteriormente creaba clases con todos los atributos y derivar de ellas era un problema, pues existían atributos que no utilizaba. Las clases abstractas definen el inicio de la jerarquía de un grupo de objetos, que puede irse incrementando, dependiendo de las necesidades del sistema. Es pensar en una superclase que sirva como base para que los programadores de la misma empresa deriven sus clases de ella y no tengan que conocer el dominio completo del problema, pues en ella esta contenida la funcionalidad básica.

viernes, 18 de abril de 2014

Configuración de Ambiente de Desarrollo en Android

A pesar de que existe mucha documentación para desarrollo de aplicaciones para android, nada como la experiencia que te da hacerlo tu mismo. Te topas con varios detalles que se supone no te debes encontrar. Primero instalo el SDK de android. Yo esperaba que dentro de las herramientas estuviera un IDE, pero no es asi. La direccion URL de donde lo descargo es: http://developer.android.com/sdk/index.html Una vez que instale el SDK bajo la version mas reciente de Eclipse.
 Lo descargue de la ruta: http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops/R-3.7-201106131736/eclipse-SDK-3.7-win32-x86_64.zip pues mi computadora tiene Windows 7 de 64 bits.
Para Windows de 32 bits esta: http://www.eclipse.org/downloads/download.php?file=/eclipse/downloads/drops/R-3.7-201106131736/eclipse-SDK-3.7-win32.zip
 Finalmente descargo el Plugin ADT como me indica la pagina del sdk de android:
 1. inicio eclipse y entonces selecciono Help > Install New Software....
 2. doy clic en Add, en la esquina superior derecha.
3. En el Add Repository dialog que aparece pongo "ADT Plugin" en Name y la siguiente URL en Location: https://dl-ssl.google.com/android/eclipse/
 4. doy clic OK
 5. En el dialogo the Available Software, seleccione el checkbox Developer Tools y doy clic en Next.
 6. doy clic Next.
 7. acepto las licencia, entonces doy clic en Finish.
 8. Reinicio eclipse. En el SDK Manager tengo que descargar una plataforma, pues no se instala ninguna. Descargo la de la version 2.2 que es la que tiene mi celular.
 NOTA: El SDK Manager debe cargarse como administrador, pues como la carpeta temp esta dentro de la carpeta de instalacion del SDK y este esta en archivos de programa, marca error si no se abrio como administrador. Para abrir el SDK manager como administrador, se ubica en el icono de la aplicacion, dan clic derecho y seleccionan abrir como administrador. es importante bajar los ejemplos de la plataforma, para aprender de ellos. Finalmente en el AVD Manager hay que crear un nuevo device, para que se cargue el emulador al ejecutar un ejemplo. Se le da new, se agregan los parametros, como de que plataforma, el tamaño de la SD Card y el tamaño de la pantalla. Les recomiendo una SD Card de 1 a 2 GB, pues mas grande se tarda en crearlo.
 Para probar tu configuracion crea un nuevo proyecto del tipo android sample project y selecciona api demos, para que veas una serie de ejemplos que te pueden servir como ejemplo. No quiero terminar sin comentarles que deben tener conocimientos basicos de java para crear una aplicacion para android. Ademas saber manejar objetos y si tienen un poco de experiencia en applets o aplicaciones de windows, les puede ayudar a comprender el esquema.

domingo, 6 de octubre de 2013

Respuesta al reto 3

Reto 3.

Ya me pique, pero me han salido en el trabajo dia a dia estas situaciones que sirven para probar mi ingenio al resolverlos. Espero que a ustedes tambien los motive, sobre todo a los nuevos programadores. Hacer una funcion que dado el numero de columna de una matriz, me regrese el nombre de la columna en Excel. Usar una funcion que se llame ASC que recibe como parametro un numero y regresa una cadena con el caracter del codigo ascii. Esto es para que al llamar a ASC(65) me regrese la letra A como cadena. Como ustedes saben las columnas en excel se referencian con letras, comenzando con la letra A, hasta la Z y de alli sigue la combinacion AA. Yo se que pueden usar funciones o codigos de internet, pero traten de resolverlo solo con el manejo de cadenas, usando la funcion basica ASC. Eso lo hara mas divertido. Aqui esta mi propuesta al reto 3. Esta en C#:

private string RegresaColumna(int Columna)
{
    Stack<string> Pila1 = new Stack<string>();
    string Regresa = "";
    int Residuo;
    int Resultado;
    Resultado = Columna;
    do
    {
        Residuo = Resultado % 26;
        if (Residuo == 0)
            Residuo = 26;
        Resultado = Resultado / 26;
        Pila1.Push(Char.ConvertFromUtf32(Residuo + 64));
    } while (Resultado > 26);
    if (Resultado > 0)
        Pila1.Push(Char.ConvertFromUtf32(Resultado + 64));
    foreach (string cad in Pila1)
    {
        Regresa += cad;
    }
    return Regresa;
}
Como lo menciona el Master Ariel Lopez, la idea es utilizar el operador % (módulo) para obtener el residuo de la división. El ciclo comienza a dividir entre 26 (que es el total de letras a generar) hasta que el resultado sea menor a 26. El problema es que el primer residuo que me regresa es la letra mas significativa (la que debe estar mas a la izquierda), es por ello que utilizo una pila para ir almacenandola, para despues sacarlas e irlas agregando a la cadena resultante. La función Char.ConvertFromUtf32 me sirve para convertir el valor numerico ASCII a una cadena. Al residuo le sumo 64, para que me de la letra deseada (el valor Ascii de A es 65). Finalmente lo que atinadamente comento en su solucion Ariel, si el residuo me da cero, tengo que ponerle el maximo valor, si no, no sale la letra Z. Esto solo se da en la ultima letra. Comenten y si encuentran algun error mejor.

viernes, 6 de septiembre de 2013

Respuesta al reto 1

Se tiene una base de datos con las tablas: Ventas, Clientes, DetalleVentas y SeriesDetalleVentas que contienen los datos de las ventas de equipos de computo y accesorios. La tabla SeriesDetalleVentas contiene los numero de serie de los productos vendidos.

Una consulta para obtener las ventas con sus clientes, detalles y series de ls detalles seria el siguiente:

SELECT Ventas.idVenta, FolioVenta, FechaVenta, Ventas.idCliente, Clientes.NombreCompleto, 
DetalleVentas.idDetalleVenta, Productos.idProducto,
Productos.DescripcionProd, DetalleVentas.Cantidad, DetalleVentas.Precio,
Serie
FROM Ventas
INNER JOIN Clientes
ON Ventas.idCliente = Clientes.idCliente
INNER JOIN DetalleVentas
ON Ventas.idVenta = DetalleVentas.idVenta
INNER JOIN Productos
ON DetalleVentas.idProducto = Productos.idProducto
INNER JOIN SeriesDetalleVenta
ON DetalleVentas.idDetalleVenta = SeriesDetalleVenta.idDetalleVenta
ORDER BY Ventas.idVenta, DetalleVentas.idDetalleVenta

Aqui lo interesante es que la ventas pueden tener solo un cliente, varios detalles y los detalles pueden tener varios numeros de serie.

Un ejemplo del resultado seria el siguiente:


El codigo para llenar todos los objetos de la consulta anterior en c# seria:

        public static void RecuperaVentas()
        {
            clsVentas Venta = new clsVentas();
            clsProductosMovs Producto;
            SqlDataReader dr;

            // Lo importante es definir un id anterior, para que en el momento que cambie, se cree
            // un nuevo objeto y se agregue el anterior a la coleccion
            int idVenta, idVentaAnterior = -1;
            int idDetalleVenta, idDetalleVentaAnterior = -1;

            while (dr.Read())
            {
                idVenta = Convert.ToInt32(dr["idVenta"]);

                // Si la venta es diferente a la anterior, se crea el objeto venta 
                // y se agrega a la coleccion
                if (idVenta != idVentaAnterior)
                {
                    Venta = new clsVentas();
                    Venta.idVenta = Convert.ToInt32(dr["idVenta"]);
                    Venta.FechaVenta = Convert.ToDateTime(dr["FechaVenta"]);
                    Venta.FolioVenta = Convert.ToInt32(dr["FolioVenta"]);
                    Venta.Cliente.idCliente = Convert.ToInt32(dr["idCliente"]);
                    Venta.Cliente.NombreCompleto = dr["NombreCompleto"].ToString();
                    Ventas.Agregar(Venta);
                }

                // Si el detalle de la venta es diferente a la anterior, se crea el objeto
                // detalle y se agrega a la coleccion
                if (idDetalleVenta != idDetalleVentaAnterior)
                {
                    Producto = new clsProductosMovs();
                    Producto.idDetalleVenta = Convert.ToInt32(dr["CantDetVenta"]);
                    Producto.Cantidad = Convert.ToInt32(dr["CantDetVenta"]);
                    Producto.idProducto = Convert.ToInt32(dr["idProducto"]);
                    Producto.Precio = Convert.ToSingle(dr["Precio"]);
                    Producto.DescripcionProd = dr["DescripcionProd"].ToString();
                    Venta.Detalle.Agregar(Producto);
                }

                // Finalmente se agrega la serie
                Producto.Series.Agregar(dr["Serie"].ToString();

                // Se actualizan los indices anteriores para el siguiente ciclo
                idVentaAnterior = idVenta;
                idDetalleVentaAnterior = idDetalleVenta;
            }
        }

Espero que tenga algun error para que comenten este codigo

jueves, 29 de agosto de 2013

Reto 1
Una de las principales tareas en cualquier programa es la de llenar una serie de objetos con los datos obtenidos de una matriz de resultados de una consulta a una base de datos. Por ejemplo, traer una venta con los datos de la venta, del cliente y de su detalle incluyendo los productos. Hacer una función en cualquier lenguaje, que obtenga los datos de una consulta y llene los objetos necesarios en un solo ciclo. No necesariamente una venta, puede ser una compra, un grupo con su maestro y alumnos, propongan. Igualmente ustedes propondrán los datos que tendrá cada objeto. Entre mas reutilizable sea mejor.

Si es pequeño el codigo, ponganlo aqui. Si no mandamelo a mi correo marclinux@yahoo.com. No me interesa mucho la consulta, en tu funcion puedes pasar como parametro un objeto del lenguaje que ya esta lleno, un dataset, un resultset o un array. Los campos tampoco son importantes, puede ser uno o dos datos por cada objeto. Lo importante es el ciclo que llena tres o mas objetos.

Tambien es importante que en la matriz que regresa la consulta se tenga un encabezado con mas de un detalle. Si es una venta, mas de un producto, si es un grupo, mas de un alumno

domingo, 31 de marzo de 2013

Inicio del blog de expea

Hola, por fin un mayor espacio para escribir. Es dificil explicar las cosas que me encuentro en internet en espacios tan pequeños como el twitter y el facebook.

Espero poder expresarme en este espacio sobre las cosas que me gustan, como lenguajes de programación, herramientas de desarrollo, metodologías de desarrollo de software y procesos de desarrollo.

Atte.

Ing. Marcos Hernandez