78 - Canvas : mover una figura

Otro algoritmo útil cuando trabajamos con figuras dentro de un control Canvas es la posibilidad de arrastrar y soltar figuras.

Debemos capturar los eventos cuando se hace clic dentro de una figura y luego cada vez que se desplaza la flecha del mouse.

Problema:

Se cuenta con dos archivos de tipo png con las imágenes de distintas cartas. Mostrarlas a cada una dentro de una componente de tipo Canvas y permitir moverlas dentro del control mediante el mouse.

Puede descargar estas dos imágenes y copiarlas en la carpeta donde codifica sus programas en Python:
carta1 carta1

La interfaz visual debe ser similar a esta luego de mover con el mouse las imágenes:

Canvas drag and drop figuras

Programa: ejercicio265.py

Ver video

import tkinter as tk

class Aplicacion:
    def __init__(self):
        self.ventana1=tk.Tk()
        self.canvas1=tk.Canvas(self.ventana1, width=900, height=500, background="black")
        self.canvas1.grid(column=0, row=0)
        archi1=tk.PhotoImage(file="carta1.png")
        self.canvas1.create_image(30, 100, image=archi1, anchor="nw", tags="movil")
        archi2=tk.PhotoImage(file="carta2.png")
        self.canvas1.create_image(400, 100, image=archi2, anchor="nw", tags="movil")
        self.canvas1.tag_bind("movil", "<ButtonPress-1>", self.presion_boton)
        self.canvas1.tag_bind("movil", "<Button1-Motion>", self.mover)
        self.carta_seleccionada = None
        self.ventana1.mainloop()

    def presion_boton(self, evento):
        carta = self.canvas1.find_withtag(tk.CURRENT)
        self.carta_seleccionada = (carta, evento.x, evento.y)

    def mover(self, evento):
        x, y = evento.x, evento.y
        carta, x1, y1 = self.carta_seleccionada
        self.canvas1.move(carta, x - x1, y - y1)
        self.carta_seleccionada = (carta, x, y)    

aplicacion1=Aplicacion()

Creamos primero el control de tipo Canvas y las dos imágenes respectivas. A cada una de las imágenes iniciamos el parámetro 'tags' con un valor:

        self.canvas1=tk.Canvas(self.ventana1, width=900, height=500, background="black")
        self.canvas1.grid(column=0, row=0)
        archi1=tk.PhotoImage(file="carta1.png")
        self.canvas1.create_image(30, 100, image=archi1, anchor="nw", tags="movil")
        archi2=tk.PhotoImage(file="carta2.png")
        self.canvas1.create_image(400, 100, image=archi2, anchor="nw", tags="movil")

Mediante el método 'tag_bind' de la clase Canvas enlazamos el evento de presión del botón izquierdo para todas las figuras que tienen el tag con el valor 'movil':

        self.canvas1.tag_bind("movil", "<ButtonPress-1>", self.presion_boton)

De forma idéntica hacemos la captura del desplazamiento del mouse:

        self.canvas1.tag_bind("movil", "<Button1-Motion>", self.mover)

Inicializamos el atributo 'carta_seleccionada' con el valor None, indicando que ninguna de las cartas se ha hecho clic sobre la misma:

        self.carta_seleccionada = None

Cuando se presiona el botón izquierdo sobre alguna de las cartas se extrae mediante el método 'find_withtag' la referencia de la carta presionada y se guarda en el atributo 'carta_seleccionada' una tupla que contiene la carta que se acaba de presionar y la coordenada x e y actual:

    def presion_boton(self, evento):
        carta = self.canvas1.find_withtag(tk.CURRENT)
        self.carta_seleccionada = (carta, evento.x, evento.y)

Cuando se mueve la flecha del mouse extraemos del atributo 'carta_seleccionada' la carta que se había presionado y su coordenada, procedemos a desplazarla mediante el método 'move' y guardamos la nueva posición de la carta:

    def mover(self, evento):
        x, y = evento.x, evento.y
        carta, x1, y1 = self.carta_seleccionada
        self.canvas1.move(carta, x - x1, y - y1)
        self.carta_seleccionada = (carta, x, y)    

Problema propuesto

  • Crear 100 cuadrados de color rojo y disponerlos en el control Canvas en posiciones aleatorias. Permitir desplazar con el mouse cualquiera de los cuadrados.

    Ver video

Solución

ejercicio266.py

import tkinter as tk
import random

class Aplicacion:
    def __init__(self):
        self.ventana1=tk.Tk()
        self.canvas1=tk.Canvas(self.ventana1, width=900, height=500, background="black")
        self.canvas1.grid(column=0, row=0)
        for x in range(101):
            x1=random.randint(1,900)
            y1=random.randint(1,500)
            self.cuadrado=self.canvas1.create_rectangle(x1, y1, x1+20, y1+20, fill="red", outline="red", tags="movil")
        self.canvas1.tag_bind("movil", "<ButtonPress-1>", self.presion_boton)
        self.canvas1.tag_bind("movil", "<Button1-Motion>", self.mover)
        self.carta_seleccionada = None
        self.ventana1.mainloop()

    def presion_boton(self, evento):
        carta = self.canvas1.find_withtag(tk.CURRENT)
        self.carta_seleccionada = (carta, evento.x, evento.y)

    def mover(self, evento):
        x, y = evento.x, evento.y
        carta, x1, y1 = self.carta_seleccionada
        self.canvas1.move(carta, x - x1, y - y1)
        self.carta_seleccionada = (carta, x, y)    

aplicacion1=Aplicacion()