martes, 13 de octubre de 2015

Patrón de Diseño - Decorator


Introducción


Los patrones de diseño es algo que leí hace tiempo, he usado algunos en los proyectos en los que trabaje y en los que trabajo, pero una forma de asimilarlos, creo yo, es practicar con ellos con ejemplos y por eso este post. Con este post,intentaré implementar un patrón de diseño pero de forma que fuese como parte de algo real. Digo esto por que hay algunos libros en los que ponen los ejemplos con nombres de clases como "classA" "parrentClassA". Aunque puede ser explicativo, a mí se me queda mejor con nombres más concretos.

Y como hay mucha, mucha información, donde explican este tipo de patrón, dejo más que nada, la fuente original.

"Design Patterns: Elements of Reusable Object-Oriented Software"




Hace tiempo estuve trabajando en una empresa en la que por pantalla visualizaba viajes de buses, y pasando el cursor, mostraba informacón sobre el bus.

He tomado este caso para hacer un ejemplo del patrón Decorator.

Patrón Decorator


En este ejemplo, tenemos unos autobuses (Bus.cs) los cuales los conduce un conductor asociado al Bus(DriverInfo.cs). Y unos de los requisitos es que cuando pasa el cursor por el bus dibujado en pantalla, que se muestre la informacón asociada al conductor. En este caso, para complicar un poco más la cosa, se muestra dos informaciones del conductor:
- Información del nombre del conductor (Texto) (DecoratorInfoDriver.cs)
- Gráfica de las horas conducidas (Gráfico) (DecoratorGraphicsDriver.cs)

Aquí, abajo se muestra el diagrama de clases.


Comentarios


En el patrón de diseño decorator, se ve que los objetos que decoran el objeto, tiene el mismo interfaz o métodos que
el objeto que decora, para que de esta forma, los objetos clientes, no 'noten' la diferencia y traten a ambos como iguales.

Pero luego, como los objetos se les añade funcionalidad a través de añadir decorator, pero ¿y a la hora de quitarla?. A la hora de quitarla, sería, supongo, quitar los objetos decoradores, pero en este ejemplo, se quitan todos los decorados a la vez. La forma en que se quita un objeto un decorator es de la forma.

//borrar de la lista de _buses todos los objetos decorators (los que muestran la información del conductor)
private void removeInfoDriverInfo()
{
    for (int i = 0; i < _buses.Count; i++)
    {
        DecoratorInfo decoInfo = _buses[i] as DecoratorInfo;

        //Si hay en la lista algún objeto decorado..
        if (decoInfo != null)
        {
            //obtener el objeto que esta siendo decorado
            Bus bus = decoInfo.GetVehicule() as Bus;
            _buses.Remove(decoInfo); //borrar el objeto decorado de la lista de visualizaicon
            _buses.Add(bus); //añadir a la lista de visualiacion el objeto que era decorado
        }
    }
}

El método removeInfoDriverInfo, en runtime, va añadiendo y borrando objetos a la lista de visualización y de está forma, se añade o se borran las funcionalidades de cada objeto.

Luego el método "GetVehicule", retorna le objeto que esta siendo decorado. No estoy seguro, si me estoy ciñendo a la teoría, pero al acceder al objeto que esta siendo decorado, era necesario, para incluirlo dentro de la lista de visualización (List<ControlsUI.VehicleUI> _buses), y una forma de obtener el objeto decorado através de sus decoradores era con el método GetVehicule() (también se podría haber llamado GetDecoratedObject(), pero como estamos dentro del dominio del problema de los buses...)

public VehicleUI GetVehicule()
{
    if (_vehicle is DecoratorInfo)
    {
        DecoratorInfo decoratorInfo = (DecoratorInfo)_vehicle;
        return decoratorInfo.GetVehicule();
    }
    else
    {
        return _vehicle;
    }
}


Conclusión

La conclusión, una que por lo menos saco, es que si no lo hubise implentado, no me hubiese encontrado con el problema de como recuperar el objeto decorado, para quitar de esta forma, la funcionalidad añadida que le dan los objetos decoradores.

Habrá muchas formas, pero la que he escogido para simplificar, es que en a lista  List<ControlsUI.VehicleUI> _buses, estén todos los objetos, tanto Bus como  ecoratorGraphicsDriver y/o DecoratorInfoDriver, si se van borrando y añadiendo objetos a esta lista, a la par que quiera el usurio que un Bus tenga una funcionalidad añadida (ver info del conductor) o no.

Y creo que lo mismo ha sido un ejemplo, muy sencillo, pero ha sido una forma de verlo un poco más, y de no solo leerlo.

El código fuente se puede descargar aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com



miércoles, 9 de septiembre de 2015

Contar Olivos con Ortofoto: Parte II (Clustering con K-Means)


Introducción


El siguiente post, por su propio titulo lo indica, es la segunda parte del post 'Contar Olivos con Ortofoto: Parte I'.

En esta segunda parte, se trata de ver que métodos o de que forma se puede discriminar, que son olivos y que no son, para de esta forma tener el número de olivos de una ortofoto con el menor margen de error (+- 4 olivos creo que es un margen razonable¿?):

Nota: para quién quiera ver los olivos que tiene cada recinto, una herramienta que puede utilizar es el SIGPAC (Sistema de Información Geográfica de Identificación de Parcelas Agrícolas).



Redes Neuronales

Utilice redes neuronales con una capa oculta, pero no era capaz de aprender con un margen de error mínimo.

Como un olivo puede estar representado por diferentes tamaños en pixeles. Por ejemplo, 20x20, 15x15, 4x4,..etc..lo que se hacía, era pasar todas las imagenes a un tamaño fijo, 7x7. Con este tamaño, las muestras de olivos, y las muestras de no olivos, para que aprendiese la red neuronal, daba un error grande, y no aprendía bien. Era por que la diferencia entre las imagenes de los olivos y los que no eran olivos, era muy pequeña.

Es posible, con ortofotos de alta calidad, donde un olivo puede ser representado por tamaños de 50x50, se pueda usar una red neuronal.¿?. Ya que la diferencia ahora, entre una imagen de lo que es un olivo y lo que no, se diferencian más.

Entonces, ¿qué usar?


Clustering k-means

Para una mejor explicación, ver Clustering k-means.  Y a mi modo de ver, este algoritmo lo que hace es que dado un número determinado a priori de clusters, el algoritmo, va mirando si los valores de un elemento, un objeto, está más cerca de un cluster o de otro. Y encuentra un cluster que este más cerca, asigna ese objeto a ese cluster. Creo que no está muy bien explicado. En el caso concreto que nos atañe.

Se han identificado por ejemplo 100 posibles olivos de diferentes tamaños (5x5, 20x20,..etc). Ahora, el algoritmo, inicializa aleatoriamente, los clusters a los que pertenece cada objeto. Es decir, va diciendo..."objeto 1 tu perteneces aleatoriamente al cluster 0, tu el objeto 2, al cluster 3,...hasta 6 clusters".

Luego se calcula el centroide de cada cluster. El centroido sera el tamaño medio de los objetos que lo componen. 
Y luego ya por ultimo, entramos en el 'while'. Que será, mientras haya cambio de un objeto de un cluster a otro, se hace: se va por cada objeto, y se mide la distancia (distancia de Euler) de ese objeto a todos los cluster, si se encuentra que el cluster más cercano encontrado, es distinto al que pertenece el objeto, eureka!, el objeto no pertenece a ese cluster y se pasa al otro.

Al final del algoritmo, en este ejemplo, tenemos 6 clusters, y cada uno con objetos similares entre sí.

Aquí, se supone que como los olivos serán casi todos del mismo tamaño, estarán casi todos en un mismo cluster, y los objetos que no son olivos, en otro clusters distinto.

Veamos un pantallazo.



Ene el pantallazo se ven rectangulos semitransparentes. Esto son cluster que se han determinado que no entran a formar parte del computo de la suma de los olivos. Y como se van son rectangulos que no tienen nada que ver con un olivo.

Pero, aquí está el problema que veo, que hay objetos en un cluster, que son olivos y otros que no son. Por ejemplo, el Cluster 5, son olivos, pero luego hay etiquetas 5 en el tejado de una casa.

¿como solucionar esto?

Un solución rápida, sería que el usuario, definiera una región de la ortofoto, donde se le diga, donde debe contar, y los objetos que estén fuera de esta región (la marcada por el trazado rojo), no entrar en el conteo de los olivos. 

El trazado rojo, está dibujado con el Paint, y no ha sido implementado en el programa, pero no sería un ejercicio complicado el crear una región y aplicar clipping.

Conclusión

No ha sido muy elegante la solución, por el hecho de tener que dibujar una región, pero si por lo menos para una primera versión, si contaría los olivos correctamente. Ahorrando los costes de llevar personal para que los cuente (tiempo, viajes, dietas,....).

El programa en release está aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com





martes, 1 de septiembre de 2015

Un producto software, varios clientes. O como customizar.

Introducción


Hace un tiempo, unos años, trabaje en una empresa donde tenían un producto para varios clientes, Y cada cliente, requería un nuevo requisito, o quitar alguno, esto generaba código del estilo:

#if __CLIENT_A__

// habilitar caracteristica del cliente A


#endif


Este tipo de código a lo largo, de miles de lineas de código, mezclado con las demás funcionalidades de los demás clientes, el código es más difícil de seguir que una demostración matemática (al menos para mí...)

  Hace un tiempo, me tope con este problema, pero no quería extender la complejidad de los #if a lo largo de todas las clases del programa. Debía de encapsular esta complejidad en una clase.

Pero antes, veamos un ejemplo práctico.

Ejemplo práctico


Imaginemos, que tenemos un software que tiene las siguientes funcionalidades:

- Exportar sus datos a un fichero Txt (__TXT__EXPORT__ )
- Exportar sus datos a un fichero Excel (__EXCEL_EXPORT__)
- Visualización 2d (__2D_VIEW__ )
- Visualización 3d (__3D_VIEW__ )
- Exportación del modelo 3d generado a un fichero (__EXPORT_3D_MODEL__ ). Esta funcionalidad solo se activo si está activa la visualicación 2d y 3d.

La función drawOptionsInstallOldWay dibuja en pantalla las opciones instaladas de la forma #if. Y la función drawOptionsInstallNewWay las dibuja de otra forma que ya veremos.

El aspecto de la aplicación sería con las siguientes opciones activadas:
__TXT__EXPORT__, __2D_VIEW__,__3D_VIEW__





La parte izquierda se dibuja con el método drawOptionsInstallOldWay y la parte derecha (azul) con el método drawOptionsInstallNewWay .

Ahora, veamos cual de los dos métodos es más difícil de leer y de mantener a la larga.

 private void drawOptionsInstallOldWay(Graphics g)
        {
            
            float y = 40.0f;
#if __TXT__EXPORT__ 
                g.DrawString("Txt Export", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __EXCEL_EXPORT__
                g.DrawString("Excel Export", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __2D_VIEW__
                g.DrawString("2d View", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __3D_VIEW__
                g.DrawString("3d View", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif

#if __3D_VIEW__ && __2D_VIEW__ // __EXPORT_3D_MODEL__
                g.DrawString("Export 3d Model", SystemFonts.DefaultFont, Brushes.Black, 10.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
#endif


        }


 private void drawOptionsInstallNewWay(Graphics g)
        {
            float y = 40.0f;

if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_TXT__REQUERIMENT))
            {
                g.DrawString("Txt Export", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }
if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT))
            {
                g.DrawString("Excel Export", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if(RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_2D_REQUERIMENT))
            {
                g.DrawString("2d View", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if (RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_3D_REQUERIMENT))
            {
                g.DrawString("3d View", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }

            if (RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GENERATE_3D_MODEL))
            {
                g.DrawString("Export 3d Model", SystemFonts.DefaultFont, Brushes.Blue, 200.0f, y);
                y += SystemFonts.DefaultFont.Height + 2.0f;
            }
        }

Vemos que el método drawOptionsInstallNewWay no hace uso de #if, sino de llamadas a un objeto de tipo RequerimentsActivator, el cual, le responde si una funcionalidad o requisito está activo o no.
Con esto se consigue que la complejidad de los #if, está centralidad, encapsulada en la clase RequerimentActivator.

La clase RequerimentActivator


Esta clase sigue el patrón de diseño singleton. De esta forma, se podrá acceder desde cualquier parte del programa, pero, yo creo que las partes donde se debería de acceder, es desde el interfaz, pero no llamarla desde las clases del problema de la aplicación.
Es decir, hacer uso de esta clase, RequerimentActivator, solo desde el MainForm.cs o clases que deriven de la clase Form. Así, las clases del dominio del problema no estarán acopladas a esta clase.


Esta clase tiene un enumerado llamado OptionalRequeriments donde se enumeraran todas las opciones que se pueden activar y desactivar.

public enum OptionalRequeriments { EXPORT_EXCEL__REQUERIMENT = 0, EXPORT_TXT__REQUERIMENT = 1, GRAPHICS_2D_REQUERIMENT = 2,GRAPHICS_3D_REQUERIMENT = 3, GENERATE_3D_MODEL=4 };
            int _MaxOptionalRequeriments = 5; //numero de opciones


Y el método iniRequeriments inicializa el array booleano _requeriments a true o a false dependiendo si un requisito se activo o no. Pero, ¿cómo?

Haciendo uso, ahora sí, de los #if.


#if __EXCEL_EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] = true;
#endif
#if __TXT__EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_TXT__REQUERIMENT] = true;
#endif

Sí por ejemplo el simbolo __EXCEL_EXPORT__ no está definido, entonces 
 _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] estaría a falso.

La función completa sería:

   private void iniRequeriments()
            {
                _requeriments = new bool[_MaxOptionalRequeriments];
            
                for (int i = 0; i < _MaxOptionalRequeriments; i++)
                {
                    _requeriments[i] = false;

                }

#if __EXCEL_EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT] = true;
#endif
#if __TXT__EXPORT__
                _requeriments[(int)OptionalRequeriments.EXPORT_TXT__REQUERIMENT] = true;
#endif
#if __2D_VIEW__
                _requeriments[(int)OptionalRequeriments.GRAPHICS_2D_REQUERIMENT] = true;
#endif

#if __3D_VIEW__
                _requeriments[(int)OptionalRequeriments.GRAPHICS_3D_REQUERIMENT] = true;
#endif

#if __2D_VIEW__ && __3D_VIEW__
                _requeriments[(int)OptionalRequeriments.GENERATE_3D_MODEL] = true;
#endif

            }

En negrita he puesto el requisito 'generar modelo 3d a fichero'. Como este requisito depende de dos requisitos, no se define un simbolo de tipo __GENERATE_MODEL_3D__.

La gracia de esto, es que se puede establecer una jerarquía de requisitos. Requisitos que se activen o no, dependiendo de otros requisitos!!

Además, de luego, la facilidad para habilitar los menús, dependiendo de las funcionalidades activas. Como se ve abajo.

private void updateMenuFromRequerimentsNewWay()
        {
            txtToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_TXT__REQUERIMENT);

            excelToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.EXPORT_EXCEL__REQUERIMENT);

            View2dToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_2D_REQUERIMENT);

            View3dToolStripMenuItem1.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GRAPHICS_3D_REQUERIMENT);

            generateModel3DToolStripMenuItem.Enabled = RequerimentsActivator.GetInstance().IsRequerimentInstalled(Versions.RequerimentsActivator.OptionalRequeriments.GENERATE_3D_MODEL);
            
        }

Conclusión

Creo que está forma de activar o desactivar los requisitos de un software, hace más mantenible a la larga el código fuente, ya que, encapsula la complejidad de los #if . Además, esto permite, aumentar la complejidad, pudiendo crear jerarquías de requisitos (activar un requisito cuando varios requisitos están activos, o pueden obedecer a una lógica booleana).


El código fuente se puede encontrar aquí.

Félix Romo
felix.romo.sanchezseco@gmail.com

lunes, 27 de julio de 2015

Contar Olivos con Ortofoto: Parte I (Algoritmo Secuencial de Etiquetado)

Contar Olivos con Ortofoto: Parte I (Algoritmo Secuencial de Etiquetado)


Introducción


Hace muchos años, trabaje en Tragsatec, donde trabajaba manteniendo el GIS Dinamap. Había una parte del programa que me fascinaba. A partir de una ortofoto de un campo de olivos, Dinamap, contaba los olivos. De esta forma, se podría saber los olivos de un terreno y la subvención correspondiente. De esta forma, se evitaba que una persona, se trasladase al lugar y contarlos.

Siempre me pregunte que tipo de algoritmo se usaba. ¿Como los contaba? Y siempre me quedo esa espinita. Y creo que hoy, poco a poco, me lo voy sacando.

Pero, es lo mismo que contar estrella en una foto, solo que aquí, el fondo es negro y los elementos a contar blancos o más claros.


Mi implementación - Algoritmo Secuencial de Etiquetado


Primeramente, no he implementado todo el proceso, pero si una parte. Los pasos que yo veo para contar olivos, o estrellas...es el siguiente.

1-  Extraer todos los elementos de la imagen sean o no olivos. (Algoritmo Secuencial de Etiquetado)
2. - Determinar si el elemento extraído es un olivo.

En este post se ha implementado la primera parte, extraer todos los elementos de la imagen. Para este proceso se el algoritmo de tipo Algoritmo Secuencial de Etiquetado.



Implementación Algoritmo Secuencial de Etiquetado.

Imagen de test.



Dado una imagen en una escala de grises, se transforma esta imagen una matrix nxm donde n es la anchura de la imagen y m la altura de la imagen. Así, obtenemos una imagen donde los ceros, será el fondo de la imagen y los 1s, definiran los elementos contendidos en la imagen.



Aquí se ve como los 1s forman tres figuras, tres circulos. Ahora, con el algoritmo de etiquetado secuencial, se etiqueta cada figura, dando lugar a la extracción de la información de cada figura, lo que ocupa en ancho y algo en la imagen y su etiqueta asociada. Asi, el numero de figuras en la imagen, nos daría el numero de olivos aproximados.l



Aunque, claro, las figuras etiquetadas, pueden ser otra cosa que no sean olivos, como casas, o zonas osucaras de carreteras, y entonces, habría que pasar las matrices que forman las figuras, por otra fase, que detecte que figuras son olivos.

-Determinar si el elemento extraído es un olivo.


Aquí se puede usar redes neuronales la cuales, habrán sido entrenadas para reconocer patrones de imágenes de olivos. Como una imagen de un olivo puede estar en 20x15 pixeles, o en 15x17 pixeles, se realiza una transformación de la imagen a una conocida, por ejemplo a 5x5, Y asi, entrenaremos la red neuronal con imágenes de olivos de 5x5 pixelesy también de no olivos con esta resolución. Y también importante, entrenar a esta red neuronal contra el fraude, por si pusiesen en el campo olivos de cartón para que en la ortofoto se cuente como un olivo. Pero esta parte, ya se realizará en otro post.



CounterOlives


CounterOlives es el programa de testeo para probar la primera fase de esta implantación de contar olivos. 










En este link esta un fichero comprimido con el ejecutable y algunas ortofotos en formato .bmp.


En el siguiente ejemplo, se ve como cuenta como olivos, figuras que no son olivos. Aquí, es donde un segundo algoritmo (redes neuronales por ejemplo) debería de clasificar cada figura y asi contar verdaderamente los olivos que hay en la imagen.





Conclusión

Con unas pocas modificaciones, se podría contar el número de piscinas en un municipio, el numero de estrellas, número de personas en una concentración...lo mismo he pecado de optimista con 'unas pocas modificaciones'..pero se podría intentar.

Noticias relacionadas:

"Google Earth es utilizada para encontrar piscinas ilegales": 
http://www.marketingdirecto.com/actualidad/digital/google-earth-es-utilizada-para-encontrar-piscinas-ilegales/



miércoles, 15 de julio de 2015

Redes Bayesianas : Simulación Accidentes petroleros

Introducción


Hace ya un tiempo, en la máquina del café de mi trabajo, charlando, se comento los escenarios de no-masa, es decir, escenarios donde hay muy pocos datos para hacer uso de métodos estadísticos. Por ejemplo, los accidentes de barcos, aviones, cohetes,...

También, quería meterme con este tema de las redes bayesianas, ya que en su día, me metí con los Naives Bayes classifier (clasificador bayesiano ingenuo ¿?)

Entonces, ví que con redes bayesianas se podría simular escenarios de no masa, y escogí el escenario de los accidentes de los petroleros.

Red Bayesiana Accidentes Petroleros

No soy ningun experto en las causas que pueden provocarlos, ni mucho menos la probabilidad de cada uno de ellos, pero aquí está la red que use.




Aquí supuse que el Fuego (Fire) depende de si la tripulación no tiene experiencia, es junior y si el casco es de tipo casco simple.
Después, en daños estructurales (Structural Damage) supuse que si un barca sufre fuertes olas, mayor probabilidad de que sufrir daños, y las fuertes olas (strong swell) dependen de donde este el barco, el que mar u océano esté.

Las tablas de probabilidad para los nodos dependientes fueron:




Simulación


Lanzamos 1000 simulaciones, con una probabilidad baja de tener tripulacion junior y que el barco esté en el océano atlántico y con baja probabilidad que el barco tenga un casco simple.


Esto nos da que el fuego y daños estructurales están a la par, entre 30-40 accidentes.




Cambiamos la probabilidad de la tripulación junior a más alta, esto nos dará más incendios.




Ha subido hasta del orden de 500 incendios.

Aquí se ve, que cambiando las probabilidades, los resultados de la simulación varia. Aquí es donde los expertos en el dominio, en este caso los accidentes de petroleros, deben de crear el modelo con las variables y dependencias adecuadas, y las probabilidades más o menos correctas. Aunque a medida que se van conociendo datos, este modelo se actualidad, tanto con variables nuevas como con actualizar las probabilidades.

Conclusión


Aunque este método podría ayudar en este tipo de problemas, no sé si en la comunidad actuarial, al no ser un método estadístico, sería muy aceptado o tendrían sus reticencias. Pero, es aquí donde esta su fortaleza, la información estadística la guarda en las probabilidades dadas por el experto o conjunto de expertos.

Por otra parte, también este tipo de escenarios, se podría resolver con lógica difusa. Como por ejemplo, en un programa que desarrolle que usaba lógica difusa para frenar los coches (como lo hace, salvando las distancias claro, el metro de Japón).



Si te interesa el código fuente, envíame un correo.

Félix Romo
felix.romo.sanchezseco@gmail.com


miércoles, 17 de junio de 2015

Threading: Actualizando Winforms

Introducción

Es muy posible que estando en proyecto de gráficos y al inicio de él, las prioridades serán que el gráfico se visualice correctamente en la ventana, con la información correcta, que se pueda manipular el objeto que se saca por pantalla, y dejando otras funcionalidades para la siguiente iteración.

Para ilustrar este post, imaginemos un proyecto software de tipo SIG (Sistema de Información Geográfica) donde hay muchas entidades geográficas a visualizar,
tantas, que se almacenan en una base de datos, y solo se pintan las entidades que corresponden con el área que se visualiza en pantalla.

Bien, en un principio...era la nada...luego el big bag..ok..al tema. Se visualizarían las entidades de una región en pantalla, pero luego el usuario, va moviendo esa región, o se aleja, esto generará que haya más entidades de mapa a visualizar, como no están en memoria, se piden a la base de datos,
la base de datos las retorna, y una vez retornadas se invalidará (Invalidate()) la ventana para que se repinte el mapa con la nueva información.

Threading

Entonces, llega el cliente o jefe..en fin un stakeholder del proyecto y dice:
"¿Y no se puede dibujar las entidades a la vez que las va leyendo de la base de datos?". Y en ese momento, empieza la cabeza a meterse en el código y ver que modificaciones habría que hacer, cual sería el nivel del impacto, y el resultado al cabo de un segundo es.."uff..no es inmediato..".

Solución


En el pequeño ejemplo que me he inventado, parece muy simple, pero cuando estas en un proyecto real, las cosas no son tan simples, pero hay que intentar reducir la complejidad al mínimo posible.

Aquí se ha intentado reducir la complejidad o ocultarla en la clase BackgroundPainter, que su responsabilidad sería la de leer de la base de datos, y pintar las entidades del mapa leídas, además de añadirlas al mapa. Aunque el leer de la base de datos estarías en la clase EntitiesDDBB y el dibujar en las clases hijas de MapEntity. Es decir que BackgroundPainter sería una clase controlador.


Se ha derivado de la clase BackgroundWorker, para de estar forma hacer la concurrencia más fácil, ya que cuando se termina de pintar todas, se lanzará
el evento RunWorkerCompleted, donde se invalidará la ventana y se repintará con la nueva información.


Se sobreescribe el método OnDoWork para que en este método se haga el trabajo de obtener las entidades de base de datos, añadir al mapa y dibujarlas.

En la aplicación, el botón "Get entities from DDBB", simula que el usuario a cambiado de región o alguna circunstancia que haga que se tenga que cargar nuevas entidades de base de datos. 
Se puede ver mientras se pinta las entidades, como se puede interactuar con el UI:


El código fuente se puede descargar aquí.

Conclusión


Habrá mucha soluciones, pero esta me ha parecido curiosa por el hecho de crear una clase que derive de BackgroundWorker y así, crear un worker en background donde ocultar
la complejidad de la aplicación y no llevar esta complejidad en MainForm.cs o en otra clase existente. He incluso para complicarlos más, esta clase BackgroundPainter podría recuperar
la textura del terreno en background con otro thread.

jueves, 26 de marzo de 2015

Testear El Interfaz Usuario


Testear El Interfaz Usuario


Introducción


El Visual Studio ofrece un framework muy bueno para testear las clases del dominio del problema. Y todas o casi todas las clases que no tienen relación con el interfaz de usuario.

La cosa cambia, cuando se quiere testear el interfaz de usuario, ya que, el software también habría que testearlo en su capa de interfaz. Una modificación en la capa de negocio, puede afectar a como se presentan los datos en el interfaz.

En su día, empece a investigar como crear test de interfaz de usuario con el visual studio 2010 y cuando ya tenía unos cuantos definidos, al día siguiente al correrlos, me fallaban los test y no había realizado ningún cambio, y yo veía que tenían que pasar, ya que lo estaba viendo en el interfaz. Daba excepciones al leer de una celda de un grid, al realizar un click en un menú..y claro..no pasaba el test.

Por ejemplo, si tenemos una aplicación que realiza un calculo, además de los test que comprueba que el algoritmo de calculo es correcto y arroja los resultados esperados, estos resultados se verán luego en pantalla de alguna forma, en forma de grid, gráfico,..y es aquí donde interviene los test de intefaz de usuario.

Aunque en un principio pueda parecer un gasto de tiempo, estos test te aseguran una alta calidad, y sobre todo, te evitan el bug más tonto ante una o varias modificaciones.

Estos test de interfaz de usuario, incluso tiene una variante, que es, cuando tienes un software que lee ficheros en un determinado formato propio, y este formato va variando con el tiempo para ajustarse a los nuevos requisitos de usuario, y la aplicación debe leer cualquier versión del formato, se puede crear un test para leer todos los ficheros de entrada que hay en un directorio y comprobar que todos se cargan bien, y se visualizan correctamente en la interfaz de usuario. Esto me ha pasado en un proyecto y se detectaron más de 50 ficheros antiguos que no se cargaban bien. Ahora, cualquier modificación en el formato, se pasa un directorio donde están todas las versiones de los ficheros y comprueba que se cargan en la aplicación más de 100 ficheros de diferentes versiones. De esta forma, automáticamente se comprueba la carga de todas las versiones de ficheros existentes con el consiguiente ahorro de tiempo y mejora en la calidad del producto.

Mini Framework Test

He cogido un pequeño proyecto que desarrolle para crearme mi vocabulario en ingles y así, no anotarme en un cuaderno, dos o tres veces la misma palabra, que era el problema que tenía. Además, de poder exportar a un fichero cada Topic e imprimirlo o meterlo en el móvil para repasar, pero bueno, este es otro tema.

Para testear myVocabulary, añadí el proyecto 'testerUI', este proyecto es una aplicación de consola, que tiene una referencia a myVocabulary para que pueda crear la ventana principal, el MainForm, y así poder testear el interfaz.



La chicha está en la clase 'TesterAgent'. Esta clase, a través de reflexión que proporciona .Net, recoge todas las clases que se han definido en el proyecto 'testerUI' con el atributo [TestClassUI]. Y una vez que tiene la lista de todos los tipos, crea cada instancia y va llamando a cada método público, que es al final, la ejecución de un test de interfaz de usuario.



Cada método público recibe como parámetro un objeto de tipo MainForm, para poder llamar a sus métodos y simular la interacción del usuario. Aquí, estos métodos públicos, deben llamar al objeto MainForm con el Invoke, ya que como el test se está ejecutando en un Thread que no es el thread de interfaz de usuario.

Ejemplo:


/// <summary>
        /// testNumberTopicsAndWords_OnFile
        ///
        /// Testea la carga del fichero
        /// </summary>
        /// <param name="mainForm"></param>
        public void testNumberTopicsAndWords_OnFile(MainForm mainForm)
        {
            int numberOftopics =0;
            int numberOfWordsOnTopics = 0;
            string solutionPath = TestDirectory.GetSolutionPath();
            string fullTestFileName = solutionPath + @"\testerUI\TestFiles\myVocabularyTest.xml";
            mainForm.Invoke((MethodInvoker)delegate
            {
                mainForm.openVocabularyXML(fullTestFileName);
                numberOftopics  = mainForm.GetNumberOfTopicsInUI();
                mainForm.SelectTopicUI("Adverbs");
                numberOfWordsOnTopics = mainForm.GetNumberOfWordsOnRightView();
            }
            );
            if (numberOftopics != 1)
            {
                String sError = "Numero de Topics incorrecto en el xml";
                throw new TestException(sError);
            }
            if (numberOfWordsOnTopics != 3)
            {
                String sError = "Numero incorrecto de palabras";
                throw new TestException(sError);
            }
        }


En el test se ve como se llama a openVocabularyXML, a SelectTopicUI, dentro del Invoke, y es que estos métodos, interinamente llaman a controles del interfaz de usuario. Y por eso necesitan ser llamados desde Invoke.

Luego, se obtienen los datos que se quieren testear, en este caso, el número de Topics que se cargo en el árbol del interfaz de usuario, y el numero de palabra que hay en el listview de la parte derecha del interfaz de usuario.

Corremos el proyecto 'testerUI', se ejecutaran los test, y nos mostrará lo siguiente.



En la pantalla, está el resultado del ultimo test, que comprobaba que al seleccionar la palabra whereabouts en el listview, se mostraba en la ventana de detalle, el significado correcto de la palabra, donde, - paradero,...

TestingProject


Este proyecto es un proyecto normal de test que genera el visual estudio que normalmente se usa, por lo menos de momento yo, para testear la capa de negocio. Pero, hay algunas veces, dependiendo del tipo de test y de los tipos de controles a testear en la interfaz de usuario que se puede testear desde este tipo de proyectos.

Por ejemplo, vemos en TestingProject una clase de test que se llama LoadFileTest, que contiene dos test:

testNumberTopicsAndWords_OnFile
testTextOfMeaningOfaWord

Bien, el test testNumberTopicsAndWords_OnFile, funciona bien, pasa el test, pero el otro test testTextOfMeaningOfaWord, no funciona por que no se da la cascada de eventos que se tiene que dar para pasar el test. Y esto es por que el interfaz de usuario no es visible. En estos casos, estos test que necesitan una secuencia de eventos igual que como si estuviera visible al interfaz de usuario, se pasaría el test al proyecto 'testerUI'.






Conclusión


Dejo aquí el código fuente de todo, ya que será más auto explicativo que dos páginas escritas. 

El crear test de interfaz de usuario para cubrir todo el interfaz llevaría mucho tiempo, pero creo que si se crean test para cubrir partes del interfaz de usuario donde se cree que puede ser la principal fuente de bugs, o que debe ser la parte mejor testeada, puede ser de gran utilidad. Por ejemplo, si estamos desarrollando una aplicación que visualiza una onda en una gráfica, se generaría una onda conocida, y luego comprobar que la gráfica tiene los puntos en las coordenadas esperadas.

También este tipo de test, permite detectar cierres inesperados de la aplicación rápidamente, sobre todo, si el programa permite la carga de fichero, y isa dada una lista de ficheros, cárgalos y comprobar que no...estalla...el programa al cargarlos.


Félix Romo
felix.romo.sanchezseco@gmail.com







viernes, 30 de enero de 2015

Desarrollo Software Futuro (+5 años)


Desarrollo Software Futuro (+5 años)

Introducción


En este post, me voy a poner la gorra de Nostradamus, y mi bola de cristal..virtual claro, pensar un poco como será el desarrollo de software en el futuro. ¿Y para qué? el futuro vendrá cuando vendrá y cuando venga ya no será futuro sino presente. Pero creo que si levantamos la vista y miramos a años vista sobre el futuro desarrollo, lo mismo encontramos algo que nos interese entrar en ese campo y nos llene más, o algo más practico, estar mejor posicionado para encontrar trabajo en el futuro.

Nos montamos en el Delorean, fijamos año 2020, y en el año 2020, si el de arriba quiere, veremos cuan errados estábamos, aunque lo mismo alguna cosa ya existe.

2020

- Electrodomesticos:

Ya la tv y el telefono son smart. Y por inercia, la lavadora, frigorífico y lavavajillas también serán smart. Es decir, llevaran androi. Tienen funciones ya muy avanzadas, como por ejemplo, un modelo de lavavajillas que ví que media la suciedad del agua que pasaba por abajo para ajustar los tiempos, y ahorrar agua.
Pero de momento, estos programas vienen de fábrica. Pero lo mismo, en el 2020, tendrán un puerto USB, una pantalla táctil, y poder instalar el programa de lavadora que te has programado o te has descargado. Aunque según escribo esto, veo que ya hay una que va por ese camino. Lo que pasa que el software es el de la casa,..claro.

Esto me lleva a pensar, que un salto de gigante que al igual que hubo una explosión de ofertas de desarrolladores para smart phones, el día que exista otro dispositivo distinto al smart phone y que contenga un sistema operativo, habrá otra explosión de ofertas de empleo para eso tipo.

¿Y que dispositivos podrían ser? Drones

- Drones


Los drones cuando se ven por la tv, parece que son juguetes para mayores, pero no. Solo basta echar un vistazo a lo siguiente:

- Global Hawk: aquí si que se puede ver su tamaño en comparativa.
https://www.youtube.com/watch?v=IiGnaqouUfA

- Drone para la infanteria: con este casi si que pudiera parecer un juguete..
https://www.youtube.com/watch?v=X0xI4EbKUXs

Estudiantes de la EETAC y la ETSEIAT construyen un 'drone' para salvar rinocerontes y elefantes de la caza furtiva en África

http://www.upc.edu/saladepremsa/al-dia/mes-noticies/estudiantes-de-la-upc-construyen-un-drone-para-salvar-rinocerontes-y-elefantes-de-la-caza-furtiva-en-africa

- Drones construyendo colaborativamente:
https://www.youtube.com/watch?v=xvN9Ri1GmuY



Y eso es solo un ejemplo, pero se puede aplicar muchas cosas. Por ejemplo:
- Vigilar los bosques españoles (y del Mundo vale..). Unos drones con camaras termicas, detectores de humo, y esos drones colaborando entre sí.

¿Qué los bosques son muy amplios? se le puede meter un mini motor a reacción

- Ayudar en buscar desaparecidos en montañas. Otra vez, drones colaborativos.

- Ayudar en vigilancia de costas.

¿Pero como podría afectar en el desarrollo de sofware?

El día que estos aparatos tengan un sistema operativo como Androi para los smart phones, habrá empresas que buscarán programadores para desarrollar aplicaciones para drones. Un ejemplo, hace tiempo trabaje en un GIS, donde este GIS, con las fotos aéreas recibidas de los campos de los olivos, contaba automáticamente los olivos. Pero estas fotos eran realizadas desde aviones, claro, pero..¿y un dron que se dedique ha hacer ortofotos de una región? más barato!.


Por lo que he visto, hay de momento:
- AR.Drone: su sdk para manejar el drone está en linux y windows. Su alcanze está limitado al alcanze de la wifi, pero para experimentar puede estar muy bien.


- Airware: parece que apostando por crear un S.O. con todos los dispositivos que se puedan conectar a su S.O. (en vez de teclados, cámaras, sensores,...) y ofreciendo una API para desarrollar aplicaciones a las necesidades del cliente.

Creo que está dando sus primeros pasos, y poco a poco se están viendo las posibilidades de los drones. Pero cuando sea relativamente fácil, programar sobre el S.O. que lleva el dron y probar. Habrá más proyectos con drones. Supongo que tendrán que ofrecer de alguna forma el S.O. gratuito  para que más desarrollen la investigen, prueben y programen..adquieran experiencia sobre ella, y luego, dejar pagar a la empresas por ello.



Realidad Virtual

Allá por los años 90s (me encanto la película "El cortador de césped", estaba en auge la realidad virtual , que luego cayo casi en el olvido, pero de nuevo se empieza a mirar a ella. Yo creo que por que se ha reducido el coste de los dispositivos.

Ya en otro blog que tengo, escribí (allá por el año 2005) varios post sobre como la realidad virtual podría ser usada para terapias de reducción de la ansiedad y del miedo. Y al reducir el coste de los dispositivos, y poder usarse en un ordenador personal, se podría instalar en cualquier consulta.
Otras aplicaciones como ayudar a hablar en público.

Para quién ya quiera picar algo:

Virtual Reality in the .Net Framework, Part 1:

Conclusión

Pero..¿que habrá en infojobs en el 2020? 

- Habrá más ofertas de desarrolladores para androi, ya que no serán solo los smartphones y las tables quién use este sistema operativo, sino que habrá otros dispositivos que lo usen.

- Habrá más desarrolladores que trabajen como freelances. Creo que las causas pueden ser evitar los atascos, trabajar desde casa, ser uno su propio jefe, ...etc, etc,..y lo mismo tiene éxito por que los clientes al contratar la persona, saben que esa persona va a desarrollar el trabajo. Una de las pegas que veo es que el cliente tema que al cabo del tiempo, no tendrá soporte, pero ahí están los contratos...

- Habrá más profesores de informática, o mejor dicho, profesionales que se pasen a la enseñanza, ya que demandará profesores especializados en diferencias campos del desarrollo de software.

happy coding...

Félix Romo
felix.romo.sanchezseco@gmail.com


sábado, 17 de enero de 2015

Analisis de Rendimiento



Analisis de Rendimiento



Introducción


Empiezas un proyecto y empiezas a aplicar todo lo que has aprendido en la universidad y en los libros, blogs, consejos que has ido leyendo e intentar aplicar toda la ingeniera del software al nuevo desarrollo. Te creas tus diagramas de clases, secuencia, estás un minutos o más pensando que nombre dar a una clase, y las relaciones entre ellas.

Un mes despues..

Han surgido nuevos requisitos que estaban ocultos, otros se han derivado en otros requisitos y los jefes quieren ver resultados.  ¿Solucion? tiras lineas de código a casco porro, y a usar el copy&paste más que nunca (esto ya debería ser una señal de aviso..que algo no va bien). Sacas una versión, y los testeadores y usuarios: "esto parece que va más lento.."

Efectivamente, se ha dedicado el tiempo a tirar lineas de código y a sacar funcionalidad, sin reparar en el rendimiento de la aplicación.

Creo que durante todas las fases del desarrollo hay que parar de tirar lineas de código y mirar como va el rendimiento de la aplicación. El Visual Studio ofrece mucha información, que la verdad, uno casi se pierde entre tanta información. Y con este post, intentaré que me sirva para tener una especie de protocolo para cuando quiere hacer dicho analisis seguir esos pasos y hacerlo de la manera más efectiva. También, entender un poco más el tema del rendimiento.

Para ver el tema de rendimiento, me he creado un pequeño programa que genera 1000 primitivas, y según está generando las primitivas de la lotería, va dibujando la frecuencia de cada numero en la ventana. Y final de la simulación, se ve una casi distribución uniforme.



Así uno puede ver si tienes mucho histórico sobre las primitivas, la probabilidad se parece y si una tiene poco histórico, casi no tienes patrones para hacer cálculos y jugar a la Primitiva. En fin, en su día incorpore a un programa a la posición del Sol y de la Luna por los efectos gravitacionales sobre las bolas del bombo, pero nada...las variables a tomar en cuenta sin inmensas..pero bueno, nos entretenemos pensando en como...pero bueno, vamos al grano...



Variables


Hay muchas páginas que hablan sobre las variables, a si que, no voy enumerarlas, solo voy a comentar lo que me parecen y así, entenderlas mejor.

El método  public List<Primitive> Generate(int numberOfPrimis, onPrimitiveGenerated handle) es el método donde empieza a ejecutar todo. Y este método tarda alrededor de 7.062 milisegundos.

Lanzamos el profiler en modo "intrumentalización".

Paso 1:
Ponemos Current view a "Funtions"



Paso 2:
Ordenamos los resultados por la la columna "Funtion Name". Aquí algunos dirán por alguna columna donde están los resultados de los tiempos, pero como me quiero centrar solo en los tiempos de las funciones que he creado, lo ordeno por los nombres (seguro que hay una manera de filtrar, pero aún la desconozco).




Paso 3:

Buscamos la función que tenga el tiempo "Elapsed Inclusive Time", y este tiempo es el de la función
primiLotto.Kernel.PrimitivesGenerator.Generate(int32,class primiLotto.Kernel.onPrimitiveGenerated)

y sus tiempos en el profiler es de:

6.991,38(Elapsed Time Inclusive)

que, casualmente es muy parecido a con el otro tiempo calculado por el Stopwatch de 7062mseg.

Entonces, ya tenemos una correlacion. El "Elapsed Time Inclusive" mide el tiempo dentro de una función y también el tiempo de las funciones a las que llama. Es decir, el tiempo total, desde que se inicia hasta que termina.

De aquí, vamos a ver si derivamos el significado de las demás variables.

"Elapsed Exclusive Time" en el ejemplo para el método Generate es de 0.59 mseg. ¿como es posible?? esta variable lo que mide es el tiempo que es ha ejecutado el método, sin incluir las funciones hijas. Es como si cuando se llama a una función hija (en este caso por ejemplo al método generatePrimi ) es como si se parase el cronometro y a la vuelta de la función, se pone de nuevo el cronometro.

Entonces, el método que tenga mayor "Elapsed Exclusive Time" es la que me va a decir, que función esta siendo un cuello de botella.

Si ahora nos fijamos en que función tiene mayor ""Elapsed Exclusive Time" , esta es:

Y la función es drawProbability con un tiempo de 23.77mseg. Pero ojo!, hay si tiempo "Elapsed Inclusive Time", es de 1042.92, con lo cual, hay hay 'algo' en la función, que la hace ir más lenta.



Pero vamos a ver que nos dice el Visual Studio, generando el el árbol con las llamadas más costosas.






Nos dice el VS que el cuello de botella está en efectivamente en drawProbability . Y las llamadas a la API del GDI+ es lo que le está haciendo ir más lento. Mirando el código se ve que en cada iteración del bucle, se crea Pen y Brush que se puede crear al principio, ahorrando la creación de estos objetos 49 veces.

Optimizando


Creando los objetos Pen y Brush fuera del bucle en el método drawProbability , en tiempo "Elapsed Time Inclusive" es de 6564 msg.

7062 - 6564 = 498 msg ahora más rápido para 1000 simulaciones.

Truquito

Una de las cosas que se podría hacer para detectar a tiempo que una función está tardando mucho es por poner lo siguiente

public void DoSameThink()
{

    Stopwatch stopwatch = new Stopwatch();

            stopwatch.Start();

  //codigo de la función
//...............................
//...............................

    stopwatch.Stop();

Debug.Assert(stopwatch.ElapsedMilliseconds < 1000, "El método DoSameThink ha superado 1 segundo");

}

ejemplo:



Conclusión

No he contado nada que este escrito por otros blogs, libros, pero a mí me ha servido para asimiliar mejor el tema del rendimiento, de como analizar y ver los cuellos de botella. Si tu también estabas en ese caso y te ha ayudado, estupendo!!!

Como se ve, hay muchas variables que da el visual studio que aquí no se han nombrado, pero ya con estas dos variables que hemos nombrado, podemos mejorar algo el rendimiento de nuestro código.

El código fuente por está aquí por alguien quiere bajarlo y trastear con ello modificandolo.

En próximas, a ver si me adentro más en este campo.

Félix Romo
felix.romo.sanchezseco@gmail.com




martes, 6 de enero de 2015

Como construir un control Acordeon (Accordion control)

Como construir un control Acordeon (Accordion control c#)

Introducción


A lo largo del desarrollo de un proyecto, van surgiendo nuevos requisitos, o normalmente, son ampliaciones de los requisitos existentes. También por esto, es muy conveniente, antes de escribir código, contar hasta diez, es decir, diseñar, hasta que el diseño sea estable y pueda soportar los requisitos presentes y los posibles futuros (también hay que diseñar para estos). Aunque claro, muchos jefes, verán este trabajo como no productivo por que no hay lineas de código escritas, pero puede ahorrar días e incluso meses, a lo largo de la vida del código. Pero en fin, esto es otra historia...

Algo que me paso en un proyecto. Había que mostrar unos resultados de datos en una ventana provenientes de unos cálculos, podrían ser un resultado o muchos, y luego también añadir más resultados o borrar resultados que no interesaban. Aquí estime que uno de los controles que podrían encajar era de tipo acordeón.

Estupendo, ya tengo la idea que tendrá la vista, la diseño a portaminas y folio, y aquí que vemos que .Net no tiene un control de acordeón. Entonces, investigando por la red, para ver otros desarrolladores lo hacían. Me encontré con varios ejemplos, pero quería desarrollar el mío propio y así, ya tendríamos el control total sobre este control por si la vista necesitaba algo adicional que no tuviesen los demás controles.

Al lío..

Una de las fuentes en las que me base para construir el mío fue este:

http://sourceforge.net/projects/accordion/

Y empecé a investigar como estaba diseñado, una vez que capte la idea general, empeze a desarrollar el mío propio.


Las clases principales son:
Accordion: es la clase que representa el control..claro..

CheckBox: es una clase de .Net que aquí se usa como control que cuando se hace click, se oculta o aparece el control que hay debajo de este.
Control2: es una clase que une los objetos CheckBox y FLP.
FLP: es un clase que representa un FlowLayoutPanel. Mejor dicho deriva. Y es quién controla, cuando aparece un control o se oculta.

A partir de esta idea, empeze a desarrolar mi acordeón....


Mi Accordion Control



ownAccordion: clase que representa al control acordeón.
TitleControl: clase que representa el control que se va a quedar como cabezera de los controles que se añadan. Aquí se usa un usercontrol y asi poder añadir botones. En este caso, '+' y '-' para asi, añadir más controles al propio acordeón o borrarlos directamente.
myFLP: es la clase que deriva de un FlowLayoutPanel, y controla el añadir o borrar los objetos Control que se añaden al acordeón. En este ejemplo, los objetos ClientDataControl

Sé que le falta mucho a este control para que tenga una buena apariencia, pero creo que la base para seguir desarrollando. Aquí, por ejemplo, en un equipo de desarrollo, este control lo desarrollaría un programador senior, analista/programador, ingeniero software o el mismo jefe de proyecto (los cuales lo desarrollarían no acoplado el proyecto actual, sino de forma que se pueda usar en otros proyectos), y luego pasarlo otro desarrollado para que lo vaya ampliando, y además, aprendiendo. Una forma de ser mentor también.

¿Como queda?



La parte izquierda es de http://sourceforge.net/projects/accordion/

El código fuente se puede descargar de aquí.


Conclusión


Aparte de leer libros, revistas, investigar códificando, probando,..el leer código que hay por la red, intentar desarrollar los mismo, es otra forma de aprender. Y además, te habrá la mente a hacer las cosas de otra manera y lo mismo, más fácil.

Félix Romo
felix.romo.sanchezseco@gmail.com