MetaCard y Revolution: Herramientas de autor multiplataforma para multimedia | ||
---|---|---|
Anterior | Capítulo 3. Programación en MetaTalk y Transcript | Siguiente |
Ahora que estamos acostumbrados a utilizar el evento mouseUp, ¿se imagina haciendo un bucle esperando a que pulsen un determinado botón? No sólo la cantidad de código necesaria sería un problema, sino también que nada más (bueno, casi) se ejecutaría en MetaCard hasta que acabase el código asociado a ese comportamiento. Veamos como estas situaciones nos llevarán ha explorar con más detenimiento el significado de la programación orientada a objetos que se utiliza en las herramientas que nos ocupan.
Por otra parte, quiero plantear la situación que se da a la hora de ejecutar un código como respuesta a recibir un determinado evento. Con esto se plantea una alternativa a la orden send que hemos venido utilizando mayoritariamente: esta es la orden call.
Es posible utilizar un bucle en el modo usual de la programación procedural para mantener valores actualizados de una variable o la posición de un objeto. Pero en ese caso, el motor de MetaCard estará continuamente ocupado realizando esa tarea. Es posible y recomendable, en determinados casos, reescribir un bucle como el código necesario para tratar esa situación y la generación de un mensaje que, un tiempo después o cuando se produzca el evento físico correspondiente, desencadene de nuevo tal secuencia de instrucciones.
Figura 3-2. Aspecto de la pila en la que se desarrolla la problemática de sustituir los bucles por envío de enventos.
Veamos el planteamiento de un situación para ir introduciendo los cambios, es un poco forzada (existen otra soluciones, claro) pero es para ilustrar el tema. En principio vamos a mover un par de objetos (gráficos por la pantalla): "objeto1" de color rosado y "objeto2" de color verdoso que se muestra en Figura 3-2. La aproximación más naif es la de mover los objetos de su posición de origen a la final, lo que se puede hacer con la orden move que utiliza el botón "inciAleatori":
# # iniciAleatori # on mouseUp move graphic "objecte1" to\ random(the width of this card),random(the height of this card) move graphic "objecte2" to\ random(the width of this card),random(the height of this card) end mouseUp
Pero, claro: hasta que no acaba una orden, no puede empezar la siguiente. ¿Solución? Hacer pequeños movimientos y alternar entre los objetos, de manera que parezca que se mueven al tiempo. Establezcamos una posición fija de referencia para comparar y hagamos un bucle que mueva esos dos objetos a la hora. El botón "Inici" envía los objetos a una posición prefijada y el botón "bucleRepeat" implementa la solución expuesta:
# # Inici # on mouseUp move graphic "objecte1" to 86,78 move graphic "objecte2" to 586,78 end mouseUp # # bucleRepeat # on mouseUp repeat with i = 1 to 25 move graphic "objecte1" to\ (the first item of the loc of graphic "objecte1" + 20), \ the second item of the loc of graphic "objecte1" move graphic "objecte2" to\ (the first item of the loc of graphic "objecte2" - 20), \ the second item of the loc of graphic "objecte2" end repeat end mouseUp
Aunque hemos dividido la trayectoria en 25 "pasos", todavía se aprecia la alternancia en el moviento: no hay sensación se simultaneidad. El caso se puede agravar si el número de objetos fuese mayor y mientras, piense que nada más en la aplicación se podrá estar ejecutando, puesto que el código de un manejador es un bloque que se ejecuta de un tirón.
En la experimentación del anterior código, habrá podido apreciar los "saltitos" que dan los objetos. Imagine que el número de objetos fuese mayor: habría más tiempo transcurrido entre cada "paso" de un objeto y, por tanto, sería más evidente.? Vamos a cambiar de estrategia: vamos a dejar que sean los propios objetos quienes se muevan, deshaciendo el camino y de una forma más fluida.
# # bucleRepeat2 # on mouseUp send "menejat" to graphic "objecte1" send "menejat" to graphic "objecte2" end mouseUp # # objecte1 # local nMoiments on menejat if nMoiments = 25 then put 0 into nMoiments else move me rel -20,0 add 1 to nMoiments send "menejat" to me in 1 millisecond end if end menejat # # objecte2 # local nMoiments on menejat if nMoiments = 25 then put 0 into nMoiments else move me rel 20,0 add 1 to nMoiments send "menejat" to me in 1 millisecond end if end menejat
La programación orientada a eventos tiene sus ventajas, como casi todo. Pero si está acostumbrado a trabajar con lenguajes imperativos está acostumbrado a hacer las cosas de una manera. Aquí revisamos el enfoque de trabajo en base a la sucesión de eventos para realizar acciones, de modo que si estas han de ser interrumpidas esto sea posible y suceda de una manera "natural". Empezamos con un caso donde no parece que haya necesidad de discutir que las cosas suceden por eventos. Después pasaremos a uno que escribiremos (bueno, ya lo hemos hecho en un tutorial) "lineal" y que después reescribiremos como una sucesión eventos que pueden llegar en cualquier momento.
Un caso, eminentemente sencillo, es el de disponer un reloj (puede ser bastante sencillo como el de la Figura 3-3) que mantenga la hora actualizada, este nos llevará a programar la sucesión de eventos y el tratamiento correspondiente a los mismos. Lo que queremos ver aquí es que es muy fácil actuar al mismo que aquel, para por ejemplo pedir que se detenga en cualquier momento.
Para construir esta aplicación se han dispuesto tres objetos y se ha decorado un poco el tipo de letra que muestra el campo de texto, cosa que dejo a su experimentación. El botón de la izquierda es el encargado de poner en marcha el reloj y mantenerlo actualizado.
# # Martxza! # global timerID on mouseUp send "actualitza" to me in 1 second end mouseUp on actualitza put the long time into fld "display" send "actualitza" to me in 1 second put the result into timerID end actualitza # # Para! # global timerID on mouseUp cancel timerID end mouseUp
Hasta aquí todo bien. Pues ahora démosle una vuelta más de tuerca: ¿recuerda que la la sección de nombre Tutorial sobre presentaciones en Capítulo 1 movía un campo de texto sobre el camino que había dibujado previamente con un gráfico? La pregunta es retórica: si no lo recuerda, revíselo. ¡Qué digo, vamos a improvisarlo sobre la marcha!
Creemos una pila y dispongamos sobre esta un gráfico (que yo llamo "caminet") con la herramienta Graphic tool (polygon) de la "Menu Bar" como el que muestra la figura Figura 3-4a. Sobre esta también situaremos, por ejemplo como en la Figura 3-4b, un par de botones: uno ("elProta", que podemos decorar con alguno de los iconos de MetaCard y quitar el borde y el nombre del botón para que sea más divertido), que pretendemos mover por la ventana y otro ("Parat!") que pretendemos que pueda detener la acción de aquel. Para demostrar si "elProta" llega al final, mostraremos una caja de diálogo con alguna frase al respecto (Figura 3-4c), mientras que haremos algo similar (Figura 3-4d) al final del código del botón encargado de detener el proceso. Así todo el mismo será muy visual. Todo puede empezar cuando se pulsa con el ratón sobre la tarjeta y acabará cuando el objeto de interés llegue al final del camino o cuando sea interrumpido, en cada caso el mensaje lo aclará.
En el tutorial mencionado el objeto se mueve con un código como el siguiente, adaptado al caso actual y en el que hemos incluido la orden stop que hemos visto en la ayuda del move:
# # Tarjeta # local puntActual, elCami on mouseUp put the points of graphic "caminet" into elCami set the loc of btn "elProta" to the first line of elCami repeat with puntActual = 2 to the number of lines of elCami move btn "elProta" to line puntActual of elCami end repeat answer information "Soc bo: he aplegat!" end mouseUp # # Botón "Parat!" # on mouseUp stop moving btn "elProta" answer information "Soc millor encara: l'he parat!" end mouseUpPruébelo y ya me cuenta ...
Figura 3-4. Ejemplo de bucle programado con eventos: la primera fila constituye la disposición de los objetos (el punto de partida) y la segunda los dos posibles resultados que queremos obtener.
Pues bien, si no encuentra solución al problema anterior, le planteo una totalmente generalizable a otros casos donde la orden stop, ni ninguna otra, pudieran hacer nada por detener el proceso. La idea básica es crear eventos para iniciar las condiciones del bucles, hacer las acciones correspondientes a cada iteración y terminar. En el código siguiente aparecen respectivamente como "mouseUp", "pegaUnPas" y "hasAplegat". Observe que entre ellas se pasan el turno con la orden send con un pequeño retraso, ahí se permite que se pueda interrumpir la cadena. El primero prepara los valores inciales de las variables y da paso a la primer iteración del "bucle". El evento (bucle) principal se reenvia a si mismo el mensaje hasta que se da la condición de terminación. Entonces se ejecutará la única instrucción que había después del bucle.
# # Tarjeta # global idMensatge global puntActual, elCami on mouseUp put the points of graphic "caminet" into elCami put 1 into puntActual set the loc of btn "elProta" to the first line of elCami send "pegaUnPas" to me in 10 milliseconds end mouseUp on pegaUnPas if (puntActual = the number of lines of elCami) then send "hasAplegat" to btn "elProta" in 10 milliseconds else add 1 to puntActual move btn "elProta" to line puntActual of elCami send "pegaUnPas" to me in 500 milliseconds put the result into idMensatge end if end pegaUnPas # # Botón "elProta" # on hasAplegat answer information "Soc bo: he aplegat!" end hasAplegat # # Botón "Parat!" # global idMensatge on mouseUp cancel idMensatge answer information "Soc millor encara: l'he parat!" end mouseUp
Nota: Pruebe a ver si puede bajar el tiempo empleado como retraso en el ejemplo anterior.
Hemos visto ya la generación (envío) de mensajes mediante la utilización de la orden send, vamos ahora a revisarla y compararla con una alternativa: call. Ya lo habrá podido leer en la metaclase "Messages and the Message Hierarchy", pero por completitud del tema de este punto, lo volvemos a traer a discusión. La Figura 3-5 muestra dos pilas (una subpila y una pila principal para ser más exactos) que realizan bucles y muestran el contenido de variables y la diferencia (utilidad) de las instrucciones mencionadas.
Figura 3-5. Instrucciones send y call: entorno de pruebas compuesto por una pila principal (izquierda) y una subpila (derecha).
La subpila (la de la derecha en la Figura 3-5 y a la que yo llamo "aux") aparece en cuanto abrimos la principal por que hemos situado en el código de la pila:
on openCard go stack "aux" end openCard
Vamos a ver tres escenarios posibles con ayuda de este ejemplo. Siguiendo los trazos gruesos de la pila principal (la de la izquierda en la Figura 3-5 se observará que existen tres agrupaciones de controles separadas por aquellos trazos. y donde se han implementado cuatro variaciones de un mismo bucle:
La primera sería el típico bucle repeat en el que se ha introducido la orden wait para que tenga la máquina ocupada y también al usuario esperando, puesto que no puede ser interrumpida:
# Botón repeat-wait-repeat pas1 on mouseUp repeat with i = 1 to 10 put i wait 100 milliseconds end repeat wait 102 seconds repeat with i = 11 to 20 put i wait 100 milliseconds end repeat end mouseUp
Lo peor de esta versión es que durante 102 segundos no se puede hacer otra cosa que esperar: no es posible romper esta secuencia de código aunque se aperciba de que existe alguna condición que lo motive.
La segunda versión demuestra que es totalmente funcional repartir el código entre dos objetos y utilizar la orden send para generar un evento que haga que se ejecute el código situado en otro objeto. Terminada la ejecución de aquel, el control reltorna a la instrucción siguiente a la que envió el mensaje. Con lo que el resultado final es el mismo que en la versión anterior. Lo vemos en el código siguiente:
# Botón pas2-1 on mouseUp repeat with i = 1 to 10 put i wait 100 milliseconds end repeat send "mouseUp" to button "pas2-2" repeat with i = 11 to 20 put i wait 100 milliseconds end repeat end mouseUp # Botón pas2-2 on mouseUp wait 2 seconds end mouseUp
Y así podemos seguir anidando llamadas. Iincluso desde el segundo botón de vuelta hacia el primero como demuestra la segunda fila de este segundo bloque en la que se ha añadido una instrucción al segundo botón, en este caso llamado "pas2-4"
# Botón pas2-3 on mouseUp repeat with i = 1 to 10 put i wait 100 milliseconds end repeat send "mouseUp" to button "pas2-4" repeat with i = 11 to 20 put i wait 100 milliseconds end repeat end mouseUp on acabarConter answer warning "I si no l'acabe , simpatic?!" end acabarConter # Botón pas2-4 on mouseUp wait 2 seconds send "acabarConter" to button "pas2-3" end mouseUp
Vamos a dar un salto repartiendo los controles, de manera que simule una situación en la que tuviesen que trabajar en conjunto y que esten físicamente situados en dos pilas diferentes. Llegados a este punto, nuestro sencillo ejercicio se convierte en una sencilla situación de comunicación entre pilas. Ahora los controles ejecutan el mismo código pero los valores de espera los obtienen de los campos de texto; con lo que el usuario podría, en tiempo de ejecución, modificarlos y con ellos el comportamiento de la aplicación.
Para ello se ha creado un campo de texto valor que contiene el número de milisegundos con que se simula la actividad de cada pasada del bucle y lo hemos replicado en ambas pilas. Para hacer más divertido el código hemos hecho que los objetos se oculten cuando reciban el evento y reparezcan al final. Así se pueden tomar referencias para contar tiempos y el código ha variado mínimamente:
# Botón pas3-1 local delay on mouseUp hide me put field "valor" into delay repeat with i = 1 to 10 wait delay milliseconds end repeat put "(3-1) delay" && delay send "mouseUp" to button "pas3-2" of stack "aux" repeat with i = 11 to 20 wait delay milliseconds end repeat show me end mouseUp
Y para acabar de complicarlo, habrá observado que el segundo botón a quien llama ahora está en la subpila. ¿Ve la diferencia de la ejecución aunque el código de este boón sea el mismo de lo anteriores pas2-2 y pas2-4? Efectivamente, utiliza el valor de un campo de texto que se llama "valor", pero como sólo se utiliza el nombre corto del mismo, se asume que es el que está en la tarjeta activa de la pila activa. Y, ejecutando el código de pas3-2 es la única tarjeta de la subpila.
# Botón pas3-2 local delay on mouseUp hide me put field "valor" into delay wait delay seconds put "(3-2) delay" && delay show me end mouseUp
Con lo que si tienen valores diferentes ambos campos de texto "valor" ... Lo cual no es tan difícil que suceda, a veces utilizamos el mismo nombre para dos controles diferentes y no solemos especificar el nombre completo de un control, en este caso tenemos un button "pas3-2" of card 1 of stack "send-call" y un button "pas3-2" of card 1 of stack "aux". Por supuesto la referencia a la tarjeta se podría haber omitido, puesto que en nuestro caso sólo hay una en cada pila.
Eso que, en principio, es una ventaja deja de serlo si tenemos muchos controles en esa situación y hemos de ir continuamente referenciándolos por su nombre completo. No sólo por el trabajo de escribirlos, sino por que de no hacerlo prodríamos estar trabajando con los datos equivocados y el código sería correcto. Para solucionarlo, podemos hacer uso de la orden call que generará el evento sobre el objeto; pero sin cambiar el contexto, es decir, la referencia a donde localizar los objetos (de hecho puede ver como la ejecución de la orden
answer the defaultStack
que está en el botón "pas4-2" devuelve el nombre de la pila principal en lugar de la pila "aux".
# # Botón pas4-1 # local delay on mouseUp hide me put field "valor2" into delay repeat with i = 1 to 10 wait delay milliseconds end repeat put "(" & the short name of me & ") delay" && delay call "mouseUp" to button "pas4-2" of stack "aux" repeat with i = 11 to 20 wait delay milliseconds end repeat show me end mouseUp
# Botón pas4-2 local delay on mouseUp hide me answer the defaultStack -- answer information delay put field "valor2" into delay put "(" & the short name of me & ") delay" && delay wait delay seconds show me end mouseUp