Mostrando entradas con la etiqueta WinForms. Mostrar todas las entradas
Mostrando entradas con la etiqueta WinForms. Mostrar todas las entradas

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.

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


miércoles, 5 de noviembre de 2014


La API de tu interfaz (UserControls)


Introducción


Cuando uno desarrolla una aplicación de escritorio, piensa que el interfaz será estable, que una vez que se implemente, no se modificará y si se modifica son algunos detalles. Si se sigue este principio durante el desarrollo del UI, tendremos un problema. Ya que al cabo de una año o dos, por razones comerciales, de un cliente especial, de un nuevo jefe, se modifique gran parte del UI.

Un ejemplo, supongamos que estamos desarrollando un software para la gestión del video club, y en una parte del interfaz tenemos un ListView con el titulo de la pelicula, director, etc. Esta ListView se rellena desde una base de datos. Creo que el típico error aquí, es hacer visible la ListView con el resto de la aplicación. Es decir, seguir estos pasos:

- Leer información de las peliculas de la base de datos
- Añadir información a la ListView.



Pero vamos a complicar un poco la cosa. Ahora, una vez que se la información principal de la película, queremos que en otra parte de la pantalla, se muestre el argumento de la película.
Con lo cual, lo más normal es capturar el evento de selección de la ListView para actualizar y visualizar el argumento de la película seleccionada.



private void ON_lvFilms_SelectedIndexChanged(object sender, EventArgs e)
        {
            showPlotFromFilm();
        }



Así, el método showPlotFromFilm accederá al control listview, para obtener el titulo de la película y asi buscar en la base de datos o en memoria y mostrar el argumento. Aquí, hemos introducido un acoplamiento de miedo.

El problema viene cuando al cabo de un mes o un año, por el motivo que sea, hay que cambiar la listview por un treeview, ya que se ha decidido ver las películas por géneros y el treeview es por el momento el que más se ajusta.

Entonces, si hay que cambiarlo, habría que mirar todo el programa, desde donde es visible el listview para ahora engancharlo a un treeview. Podría llevar mucho tiempo, más luego los posibles bugs por un cambio que afecta tanto.

¿Cómo disminuir este riesgo?

Crear la API del UI para tu Aplicación


Para disminuir este riesgo, una opción (la verdad que otra de momento no alcanzo a verla) es crear un control de interfaz de usuario que su responsabilidad sea la de visualizar películas.

Al igual que existen los controles para visualizar texto (TextBox) o un fichero .jpg (PictureBox), nos creamos un control para representar un objeto del dominio del problema, en este caso, una conjunto de película (no escribo 'lista' para no sugerir que en la implementación que sea una lista).

El control que visualizará la lista de películas será FilmControl.cs (deriva de UserControl)

Y tendrá una propiedad:

public List<Kernel.Film> Films

Que de hay el control rellenará la lista de las peliculas en la lista que tiene interna, pero si en el futuro se cambia a un arbol, no afectaría externamente, es decir, fuera del ámbito del control.

Y luego tendría el evento:


[Description("Se selecciono una pelicula en el control.")]
public event selectFilmHandle ONselectFilm;




Que se dispara cuando el usuario seleccione una película en la listview. Si en el futuro fuese una arbol, la implementación por dentro, detectaría la selección en el arbol y lanzará despues el evento ONselectFilm.



Esta ha sido una forma algo simple de intentar explicar la buena practica de crear partes del interfaz de usuario que manejen directamente conceptos a nivel del dominio del problema y no de implementación.

En este ejemplo se ve por ejemplo en ONselectFilm, donde es un evento que se lanza cuando se selecciona una pelicula, pero por el contrario si fuese ON__lvFilms_SelectedIndexChanged, es un evento que se lanza cuando se selecciona un elemento de la lista, y lista es ya un elemento de la implementación, y sin embargo en ONselectFilm, no se hace referencia a ningún elemento de implementación del interfaz, sino que solamente se ha selecionado, ya sea por un ListView o un TreeView o cualquier otro UI más extraño. Así, hemos reducido el acoplamiento entre partes del programa enormemente.

Para descargar el código fuente de videoClub aquí.

Conclusión


Como experiencia, esta forma de trabajar me ha ahorrado bastantes horas de trabajo, ya que se intento crear una api UI para los objetos del dominio del problema (en la carpeta Kernel del ejemplo), de esta forma si una parte del interfaz se quiere llevar a otro sitio del interfaz, o recolocar la interfaz, será más rápido y menos propenso a errores (que los habrá claro).

Félix Romo

felix.romo.sanchezseco@gmail.com