TMoveResize. Redimensionando controles en tiempo de ejecución. Como en el IDE, mire. (1)

« Las callecitas de Buenos Aires tienen ese qué sé yo. Viste? »
Astor Piazzola.

No es ninguna novedad que uno de los « qué sé yo » de las callecitas de Buenos Aires son sus bares y restaurantes. Los hay de todo tipo y color. Quien esto escribe sabe deambular por los mismos embarcándose en etílicas investigaciones como la que procedo a contarle: salvo aquellos bares perpetrados con una mentalidad norteamericana donde cada-cosa-está-en-su-lugar y son inamovibles hasta los aderezos, los demás (los bares comunes de Buenos Aires y de cualquier lugar) tienen mesas que se mueven de acuerdo a las necesidades. Esto resulta muy útil sobre todo cuando llega un grupo numeroso a festejar aniversarios de natalicios o cualquier nimiedad que pueda ser festejable. Las mesas se van armando de manera que se pueda recibir la mayor cantidad de clientes con el menor grado de incomodidad para ellos.
Se preguntará qué tiene que ver esto con la programación y con redimensionar controles en tiempo de ejecución.

¿Ha echado un vistazo al software utilizado en los restaurantes? No es mala costumbre andar espiando pantallas, se lo aseguro. Si cree que el software que ha escrito es de baja calidad, salga y mire software ajeno y en acción, verá usuarios que sufren.

Pero no deje que me vaya por las ramas, volvamos a los programas con los que nos cobran en los restaurantes. Algunos no han llegado a la programación visual y los que lo han hecho no han tenido la suerte de llegar a los 32 bits o, al menos, contar con la funcionalidad mínima indispensable.
No he tenido la suerte de hacer software para restaurantes, pero me imagino que el « mapa » de mesas puede ser representado por un gráfico en pantalla donde, cualquier operación con una mesa determinada, comenzaría con clic sobre la mesita en pantalla. Siempre es mejor tener una representación de la realidad en pantalla para que el operario no tenga que estar recordando cada mesa por su nombre/número ubicación. Para el caso, los programadores podríamos imaginar que podemos « armar » mesas en pantalla con unos TButton, al hacer clic en ellos se mostraría un menu con opciones relacionadas con la adición de dicha mesa. No parece muy difícil para un programador que ha dejado atrás la programación estructurada y sabe lo que es un Objeto.

Siguiendo con el tema, no hace mucho tuve la oportunidad de evaluar un software para lo que en Sudamérica llamamos « Call centers ». Se trata de esas empresas que emplean operadores telefónicos para realizar llamadas telefónicas una tras otra, casi sin respirar. Los operadores están ubicados en pequeños casilleros contiguos, muchos. El « jefe » utiliza el software para controlar dicho personal: escuchar sus conversaciones, controlar cuanto tiempo descansan, cuánto tardan en el baño, etc. en definitiva, lo que llaman « incrementar la productividad ». Pero, lo que quería contarle era que cada operador debe reflejarse en la pantalla del supervisor o « jefe » y, una vez más, nos encontramos con un  programador que ha concebido la vida a través de una cuadrícula fija e inamovible.

¿Acaso está prohibido tener casilleros en diagonal en los call centers? ¿Acaso no debería el software permitir « dar de alta » una nueva mesa en un restaurant y colocarla realmente donde va sin tener que ajustar a una cuadrícula? Y otra cosa: Imagine ahora 10 mesitas que están juntitas al fondo del salón se encuentra el antedicho grupo numeroso festejando un cumpleaños, lo habitual es que toda la deuda sea abonada por una sola persona: el agasajado, o el más borracho. Lo importante es que existe sólo una cuenta a abonar, la que pertenece a la mesa X. Es decir, es UNA sola mesa grande. ¿No debería verse así en pantalla? ¿No debería poder mover y redimensionar una mesita en pantalla como hacemos en el IDE de Delphi?

Me propuse averiguar cuán difícil era mover y redimensionar un control. Tenía en mente los famosos y simpáticos cuadraditos que el IDE de Delphi crea cuando « seleccionamos » un control y que utilizamos para arrastrar y soltar cuando necesitamos re-dimensionar lo seleccionado. Debe ser posible, me dije, al fin y al cabo Delphi está escrito en Delphi (recuerde que estamos hablando de Win32).

Salí a navegar por los mares de la WEB y me encontré con un excelente artículo del croata Zarko Gajic, que echó un poco de luz sobre mi oscura materia gris. No voy a traducir textual la idea de Zarko, si desea leer el artículo original puede encontrar la primer parte en:

http://delphi.about.com/library/weekly/aa102505a.htm

Si no desea leer el artículo, le cuento más o menos lo poco que entendí. Se trata de abordar un tema de programación un tanto complejo y por ello la decisión de Zarko de hacer tres pasos/artículos es muy acertada.

Primer artículo.

Pensemos en la operación de arrastrar un control « soltado » sobre el IDE de Delphi: hacemos clic sobre el él y lo arrastramos hasta soltarlo sobre su nueva ubicación. Allí, los cuadraditos negros que rodean al control seleccionado son sólo decorativos. Lo importante es que el control pueda ser arrastrado.

Ahora pensemos en el IDE de Delphi y en cada uno de los 8 cuadraditos que rodean al control seleccionado. Cada vez que ponemos el cursor del maldito roedor sobre dichos cuadraditos la apariencia del mismo cambia y nos « dice » en qué sentido podemos cambiar las dimensiones del control. ¿Qué son esos cuadraditos? Pues controles de Windows, deben interactuar con el ratón, recibir el clic inicial y obviamente, responder al arrastre. Lo habitual con esos cuadraditos es hacer clic, arrastrar y soltar sólo uno de ellos a la vez. Es decir, el cuadradito debe tener el mismo comportamiento que el control seleccionado: debe ser arrastrable.
Lo que Zarko nos muestra en este primer artículo es cómo debería comportarse el control a mover, sea éste nuestro control « víctima » o cualquiera de sus 8 cuadraditos . Cómo podemos con pocas líneas de código implementar el arrastre en dichos controles. Para ello, el control y sus futuros « cuadraditos » (que son derivados de TControl), deben tener asignados tres manejadores, los correspondientes a los siguientes eventos.

OnMouseDown (« toma » el control)
OnMouseMove (mueve el control « tomado »)
OnMouseUp (« suelta » el control en su nueva ubicación)

Recuerde: cada cuadradito tiene asignado tres manejadores. Acompañando el artículo Zarko nos provee de un proyecto de ejemplo bastante explicativo donde ha soltado varios componentes a los que en tiempo de ejecución les asignará dichos manejadores. Es decir, dará a esos controles en tiempo de ejecución el comportamiento de un componente dentro del IDE: ser « movible » o « arrastrable » con el ratón. (El proceso de « redimensionar » vendrá de la mano de la misma funcionalidad: la posibilidad de mover el cuadradito).

Todos los controles que deben ser « movibles » pueden compartir esos manejadores. Lo que hace el pequeño proyecto de ejemplo es asignar dinámicamente los mismos tres manejadores a unos 3 controles. Luego, dichos controles pueden moverse con sólo presionar el botón izquierdo del ratón (OnMouseDown) y arrastrar (OnMouseMove). Su nuevo domicilio será exactamente allí donde se suelte el botón (OnMouseUp).
Recuerde que el manejador de un evento puede ser compartido por más de un control, dentro del manejador se utilizará Sender para determinar cuál de los controles es el que está enviando o disparando el evento. Le aconsejo echar un vistazo al código fuente del artículo de Zarko.

El evento OnMouseDown pone a True un flag o bandera llamado inReposition que indica que « estamos arrastrando ».
El evento OnMouseMove, que sólo actúa si el flag inReposition es True. De lo contrario el control quedaría pegado al ratón para siempre y no es lo que buscamos. Lo que hace es posicionar el control justo debajo del miserable roedor: allí donde vaya el ratón irá el control, siempre que inReposition valga True, siempre que « estemos arrastrando ».
Cuando soltamos el ratón se dispara el evento OnMouseUp, y en él ponemos inReposition en false. El ratón podrá seguir moviéndose pero como dentro de OnMouseMove lo primero que se evalúa es inReposition, el control ya no será afectado, ha sido « soltado ».

Con lo visto en este primer artículo, ya sabemos como crear controles « arrastrables ». Repasemos:

1) Para mover un control cualquiera en tiempo de ejecución debemos adicionarle esta funcionalidad: los tres manejadores.
2) Para ajustar sus dimensiones a la manera de las IDE´s tradicionales, debemos crear los famosos cuadraditos (controles) y darles exactamente la misma funcionalidad: ser arrastrables.

Para lo siguiente conviene que tenga la jerarquía de los « VCL controls » en mente.

Continuará.