Desencadenando eventos

Los sistemas operativos visuales facilitan la conexión de diferentes dispositivos que actúan como intermediarios entre la persona1 y el ordenador, interfaces de comunicación persona-ordenador.

Si el usuario realiza una acción sobre la interfaz (mueve el ratón, pulsa una tecla...) ésta se lo notifica al sistema operativo, que decidirá a qué aplicación, o aplicaciones, se lo comunica. Si el usuario mueve el ratón, el sistema se lo notifica a la pantalla para que mueva el puntero en la misma dirección y distancia, y también se lo notifica a la ventana más cercana al puntero para que, por ejemplo, cambie su forma si está pasando sobre un hiperenlace.

Los eventos de ratón se comunican a la ventana más cercana al puntero, que es la que tiene el foco del ratón. En el caso del teclado, los eventos se comunican a la aplicación (ventana) que tiene el foco del escritorio, y dentro de ella a la ventana (widget) que tiene el foco de la aplicación.

Las aplicaciones visuales están siempre pendientes de los eventos que les comunica el sistema. En el caso de las aplicaciones basadas en tkinter, la aplicación gestiona los eventos que recibe después de ejecutarse mediante la función Tk.mainloop, que inicia el bucle principal de la aplicación.

Para asociar un evento a un widget se usa el método bind, heredado de la clase Misc por lo que es genérica a todos los widgets. En la ayuda que proporciona la consola interactiva de Python sobre cualquier widget encontramos la siguiente explicación sobre este método:

 |  bind(self, sequence=None, func=None, add=None)
 |      Bind to this widget at event SEQUENCE a call to function FUNC.
 |      
 |      SEQUENCE is a string of concatenated event
 |      patterns. An event pattern is of the form
 |      <MODIFIER-MODIFIER-TYPE-DETAIL> where MODIFIER is one
 |      of Control, Mod2, M2, Shift, Mod3, M3, Lock, Mod4, M4,
 |      Button1, B1, Mod5, M5 Button2, B2, Meta, M, Button3,
 |      B3, Alt, Button4, B4, Double, Button5, B5 Triple,
 |      Mod1, M1. TYPE is one of Activate, Enter, Map,
 |      ButtonPress, Button, Expose, Motion, ButtonRelease
 |      FocusIn, MouseWheel, Circulate, FocusOut, Property,
 |      Colormap, Gravity Reparent, Configure, KeyPress, Key,
 |      Unmap, Deactivate, KeyRelease Visibility, Destroy,
 |      Leave and DETAIL is the button number for ButtonPress,
 |      ButtonRelease and DETAIL is the Keysym for KeyPress and
 |      KeyRelease. Examples are
 |      <Control-Button-1> for pressing Control and mouse button 1 or
 |      <Alt-A> for pressing A and the Alt key (KeyPress can be omitted).
 |      An event pattern can also be a virtual event of the form
 |      <<AString>> where AString can be arbitrary. This
 |      event can be generated by event_generate.
 |      If events are concatenated they must appear shortly
 |      after each other.
 |      
 |      FUNC will be called if the event sequence occurs with an
 |      instance of Event as argument. If the return value of FUNC is
 |      "break" no further bound function is invoked.
 |      
 |      An additional boolean parameter ADD specifies whether FUNC will
 |      be called additionally to the other bound function or whether
 |      it will replace the previous function.
 |      
 |      Bind will return an identifier to allow deletion of the bound function with
 |      unbind without memory leak.
 |      
 |      If FUNC or SEQUENCE is omitted the bound function or list
 |      of bound events are returned.

Es fácil comprobar que hay muchos más tipos de eventos que los que trataremos en estos apuntes.

Eventos de ratón

Cuando el puntero del ratón está sobre un widget, el sistema lo notifica a la aplicación y ésta, si lo ve necesario, se lo comunica al widget, que puede llamar a un método o función si el desarrollador lo ha decidido. Los eventos de ratón que se notifican a un widget pueden ser:

  • <Enter> indica que el puntero ha entrado en el widget.
  • <Leave> indica que el puntero ha salido del widget.
  • <ButtonPress-1>, <Button-1>, <B-1 o simplemente <1> son sinónimos e indican que se ha pulsado el botón 1 del ratón sobre el widget. El botón 1 es el botón de la izquierda del ratón, el 2 es el central (que no siempre existe físicamente) y el 3 es el de la derecha2. Este evento no se realiza nunca en solitario, tras pulsar un botón podemos mover el puntero y, finalmente, se ha de soltar el botón. Al hacer clic sobre un botón de la aplicación no importa qué ocurre después ya que lo que quiere el usuario es pulsar el botón de la aplicación (a través del botón izquierdo de su ratón), sin embargo cuando se está dibujando una línea recta tiene mucha importancia saber dónde se ha pulsado el botón del ratón y dónde se ha soltado. Y si lo que se quiere es dibujar una curva también tiene importancia conocer el movimiento del ratón mientras está pulsado su botón.
  • <B1-Motion> indica que se está moviendo el ratón con el botón 1 pulsado.
  • <ButtonRelease-1> indica que se ha soltado el botón 1 del ratón.
  • <Double-Button-1> o <Triple-Button-1> indica que se ha pulsado 2 o 3 veces el botón 1 del ratón. En ambos casos hay que tener en cuenta que también se lanza el evento <Button-1> tras la primera pulsación del ratón, y <Double-Button-1> tras las dos primeras pulsaciones.

Ejercicio

Modifica el siguiente código de modo que la aplicación muestre en una etiqueta dónde se ha hecho clic.

from Tkinter import *

root = Tk()

def callback(event):
    print "clicked at", event.x, event.y

frame = Frame(root, width=100, height=100)
frame.bind("<Button-1>", callback)
frame.pack()

root.mainloop()

Añade a la aplicación otra función y otra etiqueta que informe al usuario dónde se ha pulsado con el botón derecho del ratón.

Ejercicio

Modifica el siguiente código de modo que la aplicación muestre en dos etiquetas dónde se ha hecho clic y qué tecla se ha pulsado.

from Tkinter import *

root = Tk()

def key(event):
    print "pressed", repr(event.char)

def callback(event):
    frame.focus_set()
    print "clicked at", event.x, event.y

frame = Frame(root, width=100, height=100)
frame.bind("<Key>", key)
frame.bind("<Button-1>", callback)
frame.pack()

root.mainloop()

Protocolos

Los eventos que suponen una interacción entre la aplicación que lo recibe y el gestor de ventanas se llaman protocolos.

Los protocolos son comunes a muchas aplicaciones, como el evento 'El usuario ha hecho clic sobre el botón de cierre de la aplicación', que en MS-Windows se genera también con la combinación de teclas Alt+F4, en macOS con Cmd+W y en Linux con Ctrl+W.

Estos eventos se tratan de forma automática por la aplicación, sin que el desarrollador tenga que escribir código para conseguirlo. Sin embargo, en algunas ocasiones el desarrollador quiere tener mayor control sobre ellos, por ejemplo para notificar al usuario que no se ha guardado los últimos cambios del documento que está editando. En estos casos podemos interceptar el evento mediante una función.

from tkinter import Tk
from tkinter.messagebox import askyesno

def el_usuario_quiere_salir:
    if askyesno('Salir de la aplicación',
                '¿Seguro que quieres cerrar la aplicación?'):
        v_principal.destroy()


v_principal = Tk()

v_principal.protocol('WM_DELETE_WINDOW', el_usuario_quiere_salir)

v_principal.mainloop()

Ejercicio

Cambia el anterior código para que el usuario pueda elegir entre tres opciones: salir de la aplicación sin hacer nada más, guardar los cambios en el documento y salir, o cancelar el cierre de la aplicación. Reflexiona sobre el título del mensaje y sobre el mensaje en sí, teniendo en cuenta que las tres alternativas se muestran al usuario en un mensaje con los botones Cancelar, y No.

Referencias

1. O incluso sensores...
2. En macOS se intercambia el significado de los botones 2 y 3.

results matching ""

    No results matching ""