こんにちは。見に来てくださって、ありがとうございます。石川さんです。
今日は、先日書いた記事のスクロールバーが必要のないときに消える方法について書きました。
出来上がりイメージ
ソースコード
ソースコードは以下の通りです。
import tkinter as tk
class AutoScrollbar(tk.Scrollbar): # pylint: disable=too-many-ancestors
''' 必要に応じて自動で消えるスクロールバー
grid と pack geometry manager で利用可能です'''
def __init__(self, master=None, **kw):
super().__init__(master, **kw)
self.geo_mgr = self.key_word = None
def set(self, first, last):
self.geo_mgr = self.geo_mgr if self.geo_mgr else self.winfo_manager()
if float(first) <= 0.0 and float(last) >= 1.0:
if self.geo_mgr == "grid":
self.grid_remove()
elif self.geo_mgr == "pack":
self.pack_forget()
else:
if self.geo_mgr == "grid":
self.grid()
elif self.geo_mgr == "pack":
self.pack(**self.key_word)
super().set(first, last)
def pack(self, **kw): # pylint: disable=arguments-differ
''' pack geometry managerのキーワード引数を保持 '''
self.key_word = kw
super().pack(**kw)
def place(self, **kw): # pylint: disable=no-self-use
''' place geometry manager を使ったときのための警告 '''
raise tk.TclError('placeは使えません')
class App(tk.Tk):
''' メインアプリケーションクラス '''
def __init__(self):
super().__init__()
self.title("Canvas move test")
self.geometry("400x200")
# gridバージョン
self.canvas = can = tk.Canvas(self, background="white")
sbx = AutoScrollbar(self, orient=tk.HORIZONTAL, command=can.xview)
sby = AutoScrollbar(self, orient=tk.VERTICAL, command=can.yview)
can.config(xscrollcommand=sbx.set, yscrollcommand=sby.set)
self.info = tk.Label(self)
can.grid(row=0, column=0, sticky=tk.NSEW)
sbx.grid(row=1, column=0, sticky=tk.EW)
sby.grid(row=0, column=1, sticky=tk.NS)
self.info.grid(row=2, columnspan=2)
# # packバージョン
# self.canvas = can = tk.Canvas(self, background="white")
# sbx = AutoScrollbar(can, orient=tk.HORIZONTAL, command=can.xview)
# sby = AutoScrollbar(can, orient=tk.VERTICAL, command=can.yview)
# can.config(xscrollcommand=sbx.set, yscrollcommand=sby.set)
# self.info = tk.Label(self)
# can.grid(row=0, column=0, sticky=tk.NSEW)
# sbx.pack(side=tk.BOTTOM, fill=tk.X)
# sby.pack(sid=tk.RIGHT, fill=tk.Y)
# self.info.grid(row=1, columnspan=2)
self.start = self.item = None
can.bind("<Motion>", self.move)
can.bind("<ButtonPress>", self.button_press)
can.bind("<ButtonRelease>", self.button_release)
can.bind("<MouseWheel>", self.mouse_wheel)
self.rowconfigure(0, weight=1)
self.columnconfigure(0, weight=1)
self.bind("<Configure>", self.resize)
can.create_rectangle(40, 40, 60, 60)
def move(self, event):
''' マウスが動いたときの処理 '''
x = self.canvas.canvasx(event.x) # pylint: disable=invalid-name
y = self.canvas.canvasy(event.y) # pylint: disable=invalid-name
text = f"mouse position = ({event.x}, {event.y}) ({x},{y})"
self.info.configure(text=text)
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 = self.canvas.canvasx(event.x) # pylint: disable=invalid-name
y = self.canvas.canvasy(event.y) # pylint: disable=invalid-name
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): # pylint: disable=unused-argument
''' ボタンが離されたときの処理 '''
self.start = None
def resize(self, event): # pylint: disable=unused-argument
''' ウィンドウサイズが変わった時の処理 '''
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)
def main():
''' メインプログラム '''
app = App()
app.mainloop()
if __name__ == "__main__":
main()
説明
今回は、Scrollbarを継承して、AutoScrollbarを作成しました。ま、基本的には、ここで記載のあった方法を踏襲しています。違いはpackでも利用できるようにしたことくらいでしょうか。ポイントはScrollbarのsetが呼び出されたときに、必要がなければ、grid_remove(pack_forget)を使って見えなくする、というところでしょうか。あと、packでも利用可能にするために、winfo_managerでセットされているgeometry managerを取得してインスタンス変数に保持するようにしました。packでの利用方法は、コメントアウトしてあるところです。
その他の部分は前回とおんなじです、、、と思いましたが、一部、pylintでチェックして規約違反やら警告やらリファクタと言われたところを修正いたしました。スッキリしました!
まとめ
スクロールバーが自動で消えるようになりました。

