Envío de mensajes y cancelación

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
                   

Menos bucles y más eventos

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.

Figura 3-3. Aspecto de la pila rellonge.mc para probar el envío y la cancelación de eventos.

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 mouseUp
                      
Prué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.

a)b)

c)d)

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.

Envío de mensajes entre pilas diferentes: contexto de un objeto

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: