74 - Canvas : captura de eventos del mouse

Una actividad muy común es poder detectar cuando hemos presionado el mouse dentro de un control Canvas (o de cualquier otro Widget que hayamos visto) y a partir de eso disparar un evento.

Veremos con una serie de ejemplos la posibilidades de tkinter nos provee.

Problema:

Confeccionar un programa que cree un objeto de la clase Canvas y nos muestre en el título de la ventana la coordenada actual del mouse dentro del control Canvas y al presionar el botón izquierdo del mouse se dibuje un círculo en dicha posición.

La interfaz visual debe ser similar a esta:

Canvas eventos

Programa: ejercicio258.py

Ver video

import tkinter as tk

class Aplicacion:
    def __init__(self):
        self.ventana1=tk.Tk()
        self.canvas1=tk.Canvas(self.ventana1, width=600, height=400, background="black")
        self.canvas1.bind("<Motion>", self.mover_mouse)
        self.canvas1.bind("<Button-1>", self.presion_mouse)
        self.canvas1.grid(column=0, row=1)
        self.ventana1.mainloop()

    def presion_mouse(self, evento):
        self.canvas1.create_oval(evento.x-5,evento.y-5,evento.x+5,evento.y+5, fill="red")

    def mover_mouse(self, evento):        
        self.ventana1.title(str(evento.x)+"-"+str(evento.y))

aplicacion1=Aplicacion()

Luego de crear el objeto de la clase Canvas procedemos a llamar al método bind e indicar el nombre de evento a capturar y el método que lo capturará:

        self.canvas1=tk.Canvas(self.ventana1, width=600, height=400, background="black")
        self.canvas1.bind("<Motion>", self.mover_mouse)
        self.canvas1.bind("<Button-1>", self.presion_mouse)

Para capturar el desplazamiento de la flecha del mouse debemos especificar 'Motion' y la presión del botón izquierdo del mouse debemos especificar ''Button-1'.

Cada vez que se produce un desplazamiento de la flecha del mouse dentro de la componente canvas1 se ejecuta el método 'mover_mouse':

    def mover_mouse(self, evento):        
        self.ventana1.title(str(evento.x)+"-"+str(evento.y))

Todos los métodos que capturan eventos reciben un parámetro con información del evento propiamente dicho. Los atributos x e y almacenan la coordenada actual de la flecha del mouse.

El método 'presion_mouse' se dispara cuando presionamos el botón izquierdo del mouse dentro del objeto canvas1. Dibujamos un círculo teniendo en cuenta donde se encuentra la flecha del mouse en este momento:

    def presion_mouse(self, evento):
        self.canvas1.create_oval(evento.x-5,evento.y-5,evento.x+5,evento.y+5, fill="red")

Acotaciones

La diversidad de eventos que podemos capturar es muy grande, veamos algunos ejemplos:

  • Para capturar el evento clic del botón izquierdo del mouse indicamos en el método bind <Button-1>, el botón central <Button-2> y el botón derecho del mouse <Button-3>
  • Si necesitamos capturar el evento clic del botón derecho del mouse y a su vez que se encuentre presionada la tecla Shift tenemos que codificar:

            self.canvas1.bind("<Shift Button-1>", self.presion_mouse)
    

    En lugar de Shift podemos verificar si se está presionando la tecla control:

            self.canvas1.bind("<Control Button-1>", self.presion_mouse)
    

    Inclusive detectar el evento si se presiona Shift, Control y el botón izquierdo del mouse:

            self.canvas1.bind("<Control Shift Button-1>", self.presion_mouse)
    

    La tecla Alt, Shift, Control y el botón izquierdo del mouse:

            self.canvas1.bind("<Control Shift Alt Button-1>", self.presion_mouse)
    
  • Si necesitamos hacer algo cuando la flecha del mouse entra al control podemos plantear la captura del evento:

  •         self.canvas1.bind("<Enter>", self.entrada)
    

    Y si queremos detectar cuando la flecha del mouse sale de la componente:

            self.canvas1.bind("<Leave>", self.salida)
    
  • Para detectar el doble clic de un botón del mouse:

            self.canvas1.bind("<Double-Button-1>", self.presion_mouse)
    

Problema:

Confeccionar un programa que cree un objeto de la clase Canvas y nos permita dibujar a mano alzada dentro del mismo.

La interfaz visual debe ser similar a esta luego de dibujar unos trazos:

Canvas eventos

Programa: ejercicio259.py

Ver video

import tkinter as tk

class Aplicacion:

    def __init__(self):
        self.ventana1=tk.Tk()
        self.canvas1=tk.Canvas(self.ventana1, width=600, height=400, background="black")
        self.canvas1.grid(column=0,row=0)
        self.canvas1.bind("<ButtonPress-1>",self.boton_presion)
        self.canvas1.bind("<Motion>", self.mover_mouse)
        self.canvas1.bind("<ButtonRelease-1>",self.boton_soltar)
        self.presionado=False
        self.ventana1.mainloop()

    def boton_presion(self, evento):
        self.presionado=True
        self.origenx=evento.x
        self.origeny=evento.y

    def mover_mouse(self, evento):
        if self.presionado:
            self.canvas1.create_line(self.origenx,self.origeny,evento.x,evento.y, fill="red")
            self.origenx=evento.x
            self.origeny=evento.y
    
    def boton_soltar(self,evento):
        self.presionado=False

aplicacion1=Aplicacion()

Para poder dibujar a mano alzada vamos a identificar los eventos cuando se presiona el botón izquierdo del mouse:

        self.canvas1.bind("<ButtonPress-1>", self.boton_presion)

Cuando se mueve el mouse dentro del control Canvas:

        self.canvas1.bind("<Motion>", self.mover_mouse)

Y cuando se suelta el botón izquierdo del mouse:

        self.canvas1.bind("<ButtonRelease-1>", self.boton_soltar)

Cuando se presiona el botón izquierdo del mouse se cambia el estado de la bandera 'presionado' y se definen los atributos origenx y origeny con la coordenada actual de la flecha del mouse:

    def boton_presion(self, evento):
        self.presionado=True
        self.origenx=evento.x
        self.origeny=evento.y

Cada vez que se produce un desplazamiento de la flecha del mouse si la bandera 'presionado' tiene un valor 'True' se pasa a dibujar una línea desde la coordenada donde se encontraba la flecha del mouse cuando se lo presionó y la nueva coordenada, también actualizamos la coordenada origenx y origeny con la nueva posición:

    def mover_mouse(self, evento):
        if self.presionado:
            self.canvas1.create_line(self.origenx,self.origeny,evento.x,evento.y, fill="red")
            self.origenx=evento.x
            self.origeny=evento.y

El método boton_soltar se ejecuta cuando el operador deja de presionar el botón izquierdo del mouse, donde volvemos a disponer el atributo 'presionado' con el valor 'False', lo que hace que cuando se mueve la flecha del mouse no se dibuje la línea:

    def boton_soltar(self,evento):
        self.presionado=False