今日も見に来てくださって、ありがとうございます。石川さんです。まだまだCanvasについて、調べていきたいと思います。
先日までは、Canvasの移動ができるようになりました。今回は、前回の予言通り、拡大、縮小にチャレンジしたいと思います。イベントにMouseWheelというのがありましたので、これを使いたいと思います。Excelで調べてみたら、ホイールを普通に回すと上下のスクロール、Ctrlキーを押してホイールを回すと拡大縮小、Ctrl+Shiftキーを押してホイールをま指すと左右にスクロールしましたので、こちらに合わせてみたいと思います。
ソースコード
今回もイメージは変わらなかったので、ソースコードのみ掲載したいと思います。
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)
c.bind("<MouseWheel>",self.mouse_wheel)
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
self.resize(event)
elif event.state == 256: # Button1
self.canvas.scan_dragto(event.x, event.y, gain=1)
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.canvas.scan_mark(event.x, event.y)
if event.num == 3:
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)
if region[0] > 0:
region = (0, *region[1:])
if region[1] > 0:
region = (region[0], 0, *region[2:])
self.canvas.configure(scrollregion=region)
def mouse_wheel(self, event):
if event.state == 5: # Shift|Control
self.canvas.scan_mark(event.x, event.y)
self.canvas.scan_dragto(event.x + event.delta // 120, event.y, gain=10)
elif event.state == 4: # Control
scale = 1 + event.delta / 1200
self.canvas.scale(tk.ALL,event.x, event.y, scale, scale)
elif event.state == 1: # Shift
pass
elif event.state == 0: # None
self.canvas.scan_mark(event.x, event.y)
self.canvas.scan_dragto(event.x, event.y + event.delta // 120, gain=10)
self.resize(event)
if __name__ == "__main__":
app = App()
app.mainloop()
解説
まずは、ホイールのイベントをバインドします。25行目にc.bind("<MouseWheel>",self.mouse_wheel)
を追加しました。これによって、ホイールが回されたときにmouse_wheel
が呼び出されるようになります。
それぞれコントロールキーを押されたとき、シフトキーを押されたとき、両方押されたときの実行結果をevent
をprint
してみました。
<MouseWheel event delta=-120 x=244 y=110>
<MouseWheel event state=Control delta=-120 x=244 y=110>
<MouseWheel event state=Shift delta=-120 x=244 y=110>
<MouseWheel event state=Shift|Control delta=-120 x=244 y=110>
このままではstate
の値がわからないので、別途event.state
をprint
してみたところ、以下のようになっていました。
- Shift=1
- Ctrl=4
- Shift+Ctrl=5
delta
の値は、ホイールが下に回されると、-120、上に回されると120がセットされてきました。これらの値と先日使った、scan_mark
とscan_dragto
で上下スクロールを実装、scale
を使って拡大縮小を実装しました。拡大縮小のscale
は、このページを参考にさせていただきました。
あとは、resize
したときに左上が(0,0)の位置じゃなくなってしまうのは、どうなのかなぁ、と、思ったので、resize
を変更してみました。もしかしたら、もっといい方法があるかも知れませんが、とりあえず満足いく動作になりました。
まとめ
今回は、割合すんなりと、ホイールを使った拡大縮小ができるようになりました。拡大縮小したときに、箱を作ると、大きさの違う箱になってしまうので、拡大率、縮小率を保持して箱を作るときに参照する必要がありそうですね。