こんにちは。見に来てくださって、ありがとうございます。石川さんです。
今日は、先日書いた記事のスクロールバーが必要のないときに消える方法について書きました。
出来上がりイメージ
ソースコード
ソースコードは以下の通りです。
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でチェックして規約違反やら警告やらリファクタと言われたところを修正いたしました。スッキリしました!
まとめ
スクロールバーが自動で消えるようになりました。