どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

Canvasの図形と画像をドラッグ・アンド・ドロップで動かす。

今回はeventを使ってみます。tkinterのアプリケーションはmainloopメソッドを実行することにより、イベントループの状態、つまりイベント待ちの状態になっています。

イベント待ちの状態でイベントを発生させることにより、様々な用途のアプリケーションを作ることができます。

イベントを発生させるには例えば、キーボード操作やマウスの操作といった具合です。今回やることはマウス操作で図形と画像を動かすアプリケーションを作ることです。これを応用すればパズルゲームなどができるようになると思います。

はじめに、コードを晒してからいくつかのポイントを順番に説明していきます。

全体のコード

※画像は各自用意してください※

 

import tkinter as tk
from PIL import Image, ImageTk


class App(tk.Tk):
    def __init__(self):
        super(App, self).__init__()
        self.title('Event sample')
        self.geometry("{}x{}+{}+{}".format(300, 300, 300, 100))
        self.iconbitmap('python.ico')

        self._id = None
        self._id_list = []

        self._set_widget()
        self._put_widget()
        self._binding()

    def _set_widget(self):
        ### Canvas ###
        self._canvas = tk.Canvas(self, bg='white', width=300, height=300)

        # Rectangle
        _id = self._canvas.create_rectangle(30, 30, 60, 60, fill="skyblue", outline="red")
        self._id_list.append(_id)

        # Image
        img = Image.open("python.png").resize((100, 100), Image.ANTIALIAS)
        width, height = img.size
        new_img = Image.new("RGBA", (width, height), (255, 255, 255))
        new_img.paste(img, (0, 0))
        self._image = ImageTk.PhotoImage(new_img)
        _id = self._canvas.create_image(190, 190, image=self._image)
        self._id_list.append(_id)

    def _put_widget(self):
        self._canvas.place(x=0, y=0)

    def _binding(self):
        for _id in self._id_list:
            self._canvas.tag_bind(_id, "<ButtonPress-1>", self._pressed)
            self._canvas.tag_bind(_id, "<B1-Motion>", self._dragged)

    def _pressed(self, event):
        self._id = self._canvas.find_closest(event.x, event.y)[0]
        self._pressed_x = event.x
        self._pressed_y = event.y

    def _dragged(self, event):
        item = self._canvas.type(self._id)
        delta_x = event.x - self._pressed_x
        delta_y = event.y - self._pressed_y
        if item == 'rectangle':
            x0, y0, x1, y1 = self._canvas.coords(self._id)
            self._canvas.coords(self._id, x0+delta_x, y0+delta_y, x1+delta_x, y1+delta_y)
        else:
            x, y = self._canvas.coords(self._id)
            self._canvas.coords(self._id, x+delta_x, y+delta_y)
        self._pressed_x = event.x
        self._pressed_y = event.y


    def run(self):
        self.mainloop()


if __name__ == "__main__":
    app = App()
    app.run()

実行結果

下の画像は上のプログラムを実行したときのものです。

左上の長方形と右下の画像はマウスのドラッグ・アンド・ドロップで動かすことができます。

 

f:id:pytry3g:20180209175040p:plain

 

コードの説明

_set_widget

_set_widgetメソッドでCanvasを作成した後、長方形と画像を作成しています。長方形と画像については以下の記事を参照してください。

pytry3g.hatenablog.com

 

それぞれcreate_rectangleとcreate_imageで作成していますが、両方とも返り値としてそのアイテムのidが返ってきます。idは後ほど必要になるのでインスタンス変数の_id_listに入れておきます。

_binding

    def _binding(self):
        for _id in self._id_list:
            self._canvas.tag_bind(_id, "<ButtonPress-1>", self._pressed)
            self._canvas.tag_bind(_id, "<B1-Motion>", self._dragged)

_bindingメソッドでバインディングの設定をしています。

_set_widget()メソッドで長方形と画像のidを_id_listに入れていました。

ここでは、tag_bindメソッドを使ってそれぞれのアイテムとイベントを結びつけています。

<ButtonPress-1>はマウスの左クリックに対応します。それぞれのアイテムが左クリックされたとき、_pressedメソッドが呼ばれるようになります。

<B1-Motion>はマウスが左クリックされている状態で動かしたときに対応します。それぞれのアイテムは左クリックされた状態で動かされたとき、_draggedメソッドが呼ばれるようになります。

_pressed

    def _pressed(self, event):
        self._id = self._canvas.find_closest(event.x, event.y)[0]
        self._pressed_x = event.x
        self._pressed_y = event.y

_pressedメソッドはアイテムが左クリックされたとき呼ばれます。

1行目でfind_closestメソッドを使って与えられた座標に最も近いアイテムのidを返しています。アイテムが左クリックして発生したeventなので、eventにはアイテムが左クリックされたときの情報が入っています。event.xとevent.yはそれぞれ左クリックされたときのCanvas上のx座標とy座標が入っています。

クリックされたときの座標をそれぞれ_pressed_xと_pressed_yに入れておきます。

_dragged

    def _dragged(self, event):
        item = self._canvas.type(self._id)
        delta_x = event.x - self._pressed_x
        delta_y = event.y - self._pressed_y
        if item == 'rectangle':
            x0, y0, x1, y1 = self._canvas.coords(self._id)
            self._canvas.coords(self._id, x0+delta_x, y0+delta_y, x1+delta_x, y1+delta_y)
        else:
            x, y = self._canvas.coords(self._id)
            self._canvas.coords(self._id, x+delta_x, y+delta_y)
        self._pressed_x = event.x
        self._pressed_y = event.y

_draggedメソッドはアイテムが左クリックされた状態で動かしてるときに呼ばれます。

まず1行目でtypeメソッドにアイテムがクリックされたときのidを渡しています。返り値はアイテムの名前が文字列で返ってきます。長方形だと"rectangle"、画像だと"image"です。

そして、移動した距離をそれぞれdelta_xとdelta_yに入れています。移動した距離を求めた後に、coordsメソッドを使ってアイテムを動かします。

はじめに、coordsにアイテムのidを渡してそのアイテムの座標を得ています。

次に、アイテムのidと動かした後の座標を渡すことにより、アイテムが動くことができます。

※補足※

アイテムを動かすときに、if文を使って動かしたいアイテムが長方形か画像かの判別をしています。これは長方形と画像のオブジェクトを作るときの位置の設定方法が違うからです。

長方形は長方形の左上の頂点の座標と右下の頂点の座標が必要になりますが、画像の場合、画像の中心の座標だけが必要になるので2つの座標しか必要になりません。

1行目でどのアイテムがクリックされたのかを見ているのはそのためです。