Python tkinter GUIプログラミング Canvas move4

 今日も来てくださってありがとうございます。石川さんです。

 しつこくCanvasの移動についてやってみました。前回移動するためのクラスを作ったときに、移動のメソッドをクラス側に作ったけど、呼び出しはメインクラスの方から呼び出していて、それがちょっと気に入らなかったのですが、解決できたので、紹介します。

できあがりイメージ

 できあがりイメージは前回と同じです。機能的にはほとんど同じですが、イベントの処理がクラスに分離できたところが異なります。ま、見た目はまったく同じなのですけど。

ソースコード

import tkinter as tk


class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Canvas move with class")
        self.geometry("1200x800")

        self.canvas = tk.Canvas(self, background="white")

        self.canvas.grid(row=0, column=0, sticky=tk.NSEW)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)

        self.canvas.bind("<Button-1>", self.button1)

        self.canvas.movers = [Movable(self.canvas, 160, 160, "AliceBlue"),
                              Movable(self.canvas, 200, 200, "LightYellow")]

    def button1(self, event):
        item = self.canvas.find_overlapping(event.x, event.y, event.x, event.y)
        if item:
            return
        m = Movable(self.canvas, event.x, event.y, "Red")
        self.canvas.movers.append(m)

class Movable():
    BOXID = 0
    def __init__(self, canvas, x, y, color):
        self.canvas = c = canvas
        global BOXID
        BOXID += 1
        self.tag = t = "MOVABLE-" + str(BOXID)
        self.t_id = c.create_text(x, y, text=color, tag=t)
        self.r_id = c.create_rectangle(c.bbox(self.t_id), fill=color, tag=t)
        c.tag_lower(self.r_id, self.t_id)
        self.start = None
        c.tag_bind(t, "<Button-1>", self.button1)
        c.tag_bind(t, "<Motion>", self.move)
        c.tag_bind(t, "<ButtonRelease>", self.button_release)
        c.tag_bind(t, "<Button-3>", self.remove)

     def move(self, event):
        if self.start is None:
            return
        x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
        dx, dy = x - self.start[0], y - self.start[1]
        self.canvas.move(self.tag, dx, dy)
        self.start = x, y

    def button1(self, event):
        x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
        self.start = x, y

    def button_release(self, event):
        self.start = None

    def remove(self, event):
        c = self.canvas
        c.tag_unbind(self.tag, "")
        c.delete(self.tag)
        c.movers.remove(self)


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

説明

 最初の違いは、イベントをバインドしている16行目です。クリックしたときのイベントですが、これは、Canvasをクリックしたイベントを処理するためのものです。クリックしたところに箱がなければ、新しく箱を作ります。箱を作るための処理は、21~26行目です。以前は、この位置に、move()とbutton_release()がありましたが、今回はありません。これらは全部、クラスの方で処理するようになりました。

 29行目に追加した「BOXID」ですが、Canvas上の項目をまとめるためのタグに連番をつけるためだけのものです。Movableクラスのインスタンスが作られるごとにひとつずつ増えていきます。32行目、globalキーワードを指定することで、BOXIDが外部で宣言されたグローバル変数だということを宣言しています。33行目で1を足して連番が増えるようにして、34行目でtagを「MOVABLE-連番」となるように作成しています。35行目、36行目のテキストと四角を作成しているところでtagを指定しています。

 39~42行目でtag_bind()を使って、このクラスのオブジェクトで発生したイベントとメソッドをバインドしています。ここでのポイントはtag_bind()を使ってタグごとにイベントを割り当てているところです。前回は、キャンバスごとに割り当てていたので、オブジェクトに割り当てるたびに前回のイベント割り当てが上書きされてしまい、うまく動作しなかったのでした。調べた結果、キャンバスごとでも「add=”+”」オプションをつけてバインドすることでイベント割り当てが上書きされないということが分かったのですが、tag_bind()だとタグごとの割り当てになるので、こちらの方がしっくりきますね。

 バインドされたメソッドの方ですが、イベントはそれぞれのタグごとに発生するので、クラス内でこのオブジェクトが選択されたかどうか、という判定は不要になりました。

 あと、今回は右クリックで箱を消去するようにしてみました。

まとめ

 いかがでしょう、前回よりスッキリしたと思いませんか?
いや~、また自画自賛かなぁ。(笑)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です


The reCAPTCHA verification period has expired. Please reload the page.