Pythonで簡単なGUIを表示する場合、標準モジュールで何も追加しなくても使えるtkinterが便利です。
tkinterには、tkinter.simpledialog.Dialog
というダイアログを表示するためのひな形クラスが存在するので、それを使用すると割と簡単にダイアログを作ることが可能です。
# 初期案 import tkinter.simpledialog class ChooseDialog(tkinter.simpledialog.Dialog): def body(self, master) -> None: # 入力部分に表示するコントロールを作成する return super().body(master) def ok(self, event=None): super().ok(event); self.result = True def cancel(self, event=None): super().cancel(event); self.result = False owner = tkinter.Tk() owner.withdraw() dlg = ChooseDialog(owner, 'Choose Link Style')
ダイアログをマウスポインタの位置に表示したい
さて、このダイアログ、初期の設定だとウィンドウの中央に画面が表示されるようになっています*1。
ただ、個人的にはちょっとしたダイアログなのでマウスカーソルの位置に表示させて、すぐに操作できるようにしたい。マウスポインタの位置を取得すればできるはずですので、やってみましょう。
# 動かないバージョン import tkinter.simpledialog, ctypes, ctypes.wintypes class ChooseDialog(tkinter.simpledialog.Dialog): def body(self, master) -> None: p = ctypes.wintypes.POINT() ctypes.windll.user32.GetCursorPos(ctypes.byref(p)) self.geometry(f"+{p.x - self.winfo_width() // 2}+{p.y - self.winfo_height() // 2}") # 入力部分に表示するコントロールを作成する return super().body(master) def ok(self, event=None): super().ok(event); self.result = True def cancel(self, event=None): super().cancel(event); self.result = False
Pythonからマウスカーソルを取得する方法については、ググってみても大抵「PyAutoGUI.position
を使え」と書いてあるのですが、マウスポインタをとるためだけにPyAutoGUIをインポートするのはちょっと・・・ と思い、PyAutoGUIのソースを参照してみたところ、ふつうにWindows APIのGetCursorPos
を呼んでるだけだったので、こちらを使いました。
さて、そんな感じでマウスカーソルの周囲にウィンドウを表示するよう、ウィンドウのジオメトリを設定するのですが、なぜか動きません。なぜか。
調べてみると、tkinter.simpledialog.Dialog
はコンストラクタで_place_window
というプライベート関数を呼んでいるようで、このメソッドがダイアログの位置を設定しているようです。
それでも強引にウィンドウの位置を設定する
それでも自分でダイアログの表示位置を設定したい場合は、_place_window()
関数の呼び出しよりあとにウィンドウの位置を矯正する必要がある。
と、いうことで、引数がなく副作用も最小限で済みそうな、grab_set()
メソッドをオーバーライドして、処理を付け足します。
# 動くバージョン import tkinter.simpledialog, ctypes, ctypes.wintypes class ChooseDialog(tkinter.simpledialog.Dialog): def body(self, master) -> None: # 入力部分に表示するコントロールを作成する return super().body(master) def grab_set(self) -> None: p = ctypes.wintypes.POINT() ctypes.windll.user32.GetCursorPos(ctypes.byref(p)) self.geometry(f"+{p.x - self.winfo_width() // 2}+{p.y - self.winfo_height() // 2}") return super().grab_set() def ok(self, event=None): super().ok(event); self.result = True def cancel(self, event=None): super().cancel(event); self.result = False
こうすると、マウスカーソルの位置にウィンドウを表示させることができます。
わかったこと
tkinter.simpledialog.Dialog
はちょっとしたダイアログを表示させたいときに結構便利- 挙動が分からないことがあったらソースコードを読もう(Visual Studio Codeからであれば、Ctrlキーを押しながらキーワードをクリックでもソースが表示できる)
- Pythonの標準モジュールのソースはGithubでも公開されているのでそちらも確認を
- なんだかんだ他人のソースコードを読みたいときに読めるようにしておくトレーニングは重要
参考文献
- Tkinter ダイアログ — Python 3.10.0b2 ドキュメント
- cpython/simpledialog.py at 3.10 · python/cpython
- pyautogui/_pyautogui_win.py at master · asweigart/pyautogui
*1:の、はずなんですが、なぜか自分の環境では1モニタ目の画面左上に表示されます…。マルチスクリーンに対応していないのか・・・?