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

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

 以前Canvasに書いたものを動かす、というのをやったので今回はキャンバスにスクロールバーを付けてみようとやってみました。その中で気づいたことについて書いていきたいと思います。

出来上がりイメージ

出来上がりイメージ

 何もないところをクリックすると、四角形を描画します。四角形はマウスで動かすことができます。

ソースコード

import tkinter as tk

class App(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Canvas move test")
        self.geometry("400x200")
        
        sx = tk.Scrollbar(self, orient=tk.HORIZONTAL)
        sy = tk.Scrollbar(self, orient=tk.VERTICAL)
        c = tk.Canvas(self, background="white",xscrollcommand=sx.set,yscrollcommand=sy.set)
        sx.config(command=c.xview)
        sy.config(command=c.yview)
        self.info = tk.Label(self)
        self.info.grid(row=2,columnspan=2)
        c.grid(row=0, column=0, sticky=tk.NSEW)
        sx.grid(row=1, column=0, sticky=tk.EW)
        sy.grid(row=0, column=1, sticky=tk.NS)
        
        self.canvas = c 
        self.start = None
        c.bind("<Motion>",self.move)
        c.bind("<ButtonPress>",self.button_press)
        c.bind("<ButtonRelease>",self.button_release)
        self.rowconfigure(0, weight=1)
        self.columnconfigure(0, weight=1)
        self.bind("<Configure>", self.resize)
        
        c.create_rectangle(40,40,60,60)

    def move(self, event):
        x = self.canvas.canvasx(event.x)
        y = self.canvas.canvasy(event.y)
        self.info.configure(text=f"mouse position = ({event.x}, {event.y}) ({x},{y})")
        if self.start and self.item:
            original_x, original_y = self.start
            self.canvas.move(self.item,x - original_x,y - original_y)
            self.start = x, y
    
    def button_press(self, event):
        x, y = self.canvas.canvasx(event.x), self.canvas.canvasy(event.y)
        self.start = x, y
        self.item = self.canvas.find_overlapping(x-10,y-10,x+10,y+10)
        if self.item:
            self.item = self.item[0]
        else:
            self.item = self.canvas.create_rectangle(x-10,y-10,x+10,y+10)

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

    def resize(self, event):
        region = self.canvas.bbox(tk.ALL)
        self.canvas.configure(scrollregion=region)
        

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

説明

 ウィンドウを作成するところまではいつも通り、tk.Tkを継承したクラスでバッチリですね。タイトルとウィンドウの大きさをセットしています。

 9、10行目でスクロールバーを作成、11行目でキャンバスを作っています。このとき、作成したスクロールバーを指定しています。9行目のスクロールバーにはtk.HORIZONTALで水平を指定します。10行目のスクロールバーはtk.VERTICALで垂直を指定しています。12、13行で、はconfigでスクロールバーのコマンドをセットしてこれでスクロールバーの実装完了です。

 14行目のラベルはマウスポインターの座標を表示するために追加しました。ウィジェットを作成した後は、gridで配置しました。

 イベントとしては、23~25行目で"<Motion>""<ButtonPress>""<ButtonRelease>"、27行目で"<Configure>"をセットしています。それぞれ、マウスが動いたときのイベントにmoveメソッド、マウスのボタンが押されたときにbutton_press、離されたときにbutton_release、そして、最後は、ウィンドウの大きさが変更されたときのイベントにresizeを割り当てています。

 25、26行目のrowconfigure()メソッドとcolumnconfigure()メソッドを呼び出して、最初の行と列のサイズを可変にしています。weightオプションは、可変にした時に他の行やカラムとスペースを分配する場合の重みを指定しますが、今回は他に分配するスペースはありませんので1を指定しました。

 29行目、矩形を描画しています。(40,40)が左上の角で、(60,60)が右下の角です。

 今回のポイントは32、33行目のself.canvas.canvasx(event.x)y = self.canvas.canvasy(event.y)の部分です。実は、ぼくは最初、eventxyは、キャンバスに描画したときのxyと完全に一致していると思っていました。実は異なる場合があるのです。上記のイメージ左上の矩形、見た目は、(0,0)から描画されているように見えますが、29行目で描画したとおり、(40,40)から描画されているのでした。よくよく考えてみると描画した点とマウスの指す点は、同じ場所でも異なってくるのは当たり前でしたね。特にスクロールバーを付けたら同じにならなくなるのは分かることでした。eventで取得できるマウスの座標は、見えている左上の角からの座標で、描画されている座標はCanvasの左上からの座標になっています。そう、スクロールした分だけずれてくることがあるのでした。このずれを解消するために、canvasxcanvasyが利用できるわけです。

まとめ

 Canvasを使うときは、表示系の座標と描画系の座標の二系統を気にする必要がありますね。

“Python tkinter GUI プログラミング Canvas move” への1件の返信

コメントを残す

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


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