はじめに
Tkinterで、子ウィンドウを開いた後に別アプリを開いてから子ウィンドウを閉じると、親ウィンドウへ戻らず直前に開いたアプリのウィンドウへフォーカスが行ってしまう。メインウィンドウどこ行った?
てなわけで、子ウィンドウを閉じたら親ウィンドウへ戻るようにしたい。
考え方
ウィンドウが閉じられたら、親ウィンドウにフォーカスをセットする。
ポイント
実際に子ウィンドウが閉じられる前に親ウィンドウへフォーカスをセットし、その後に子ウィンドウを閉じる。
実際のコード
import tkinter as tk
from tkinter import ttk
class View(ttk.Frame):
def __init__(self, master):
super().__init__(master)
self.app = app
self.label = ttk.Label(self, text='これがメインウィンドウ')
self.btn1 = ttk.Button(self, text='OPEN1', command=self.app.op_sub_win1)
self.btn2 = ttk.Button(self, text='OPEN2', command=self.app.op_sub_win2)
self.label.pack()
self.btn1.pack(anchor=tk.S, expand=True)
self.btn2.pack(anchor=tk.N, expand=True)
class SubWindow1(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.geometry('250x250')
self.focus_set()
self.label = ttk.Label(self, text='サブウィンドウ1')
self.label.pack(expand=True)
class SubWindow2(tk.Toplevel):
def __init__(self, master):
super().__init__(master)
self.geometry('250x250')
self.focus_set()
# ウィンドウを閉じる動作を上書き
self.protocol('WM_DELETE_WINDOW', self.destroy)
self.label1 = ttk.Label(self, text='サブウィンドウ2')
self.label2 = ttk.Label(self, text='閉じるとメインウィンドウに戻る')
self.label1.pack(anchor=tk.S, expand=True)
self.label2.pack(anchor=tk.N, expand=True)
def destroy(self):
self.master.focus_set()
super().destroy()
class App:
def __init__(self):
self.root = tk.Tk()
self.root.geometry('400x300')
self.view = View(self.root, self)
self.view.pack(expand=True, fill=tk.BOTH)
def op_sub_win1(self):
# ウィンドウを作る
SubWindow1(self.root)
def op_sub_win2(self):
# ウィンドウを作る
SubWindow2(self.root)
def mainloop(self):
self.root.mainloop()
if __name__ == '__main__':
app = App()
app.mainloop()
上記のコードでは、Toplevel
クラスを継承した SubWindow1
、SubWindow2
という2種類のクラスを用意し、挙動を比べられるようにした。両者の違いは2か所のみ。
class SubWindow2(tk.Toplevel):
def __init__(self, master, app):
########### 省略 #############
# ウィンドウを閉じる動作を上書き
self.protocol('WM_DELETE_WINDOW', self.destroy)
########### 省略 #############
def destroy(self): # <- destroyメソッドをオーバーライド
self.master.focus_set() # <- rootにフォーカスをセット
super().destroy() # <- SubWindowを閉じる
self.protocol('WM_DELETE_WINDOW', "任意の関数")
ではウィンドウが閉じられるときの処理を再定義している。つまり、[×]をクリックしたり Alt + F4
を押したりでウィンドウを閉じた時の処理を変更している。余談だが、 'WM_DELETE_WINDOW'
の "WM" は "Window Manager" のことのようだ。
def destroy(self)
では、 destroy
メソッドをオーバーライドし、SubWindow2
を 閉じる前に その親ウィジェットへフォーカスをセットするようにしている。
これにより、 SubWindow2
に対して destroy
メソッドを呼び出せば、必ず親ウィンドウにフォーカスをセットしてからウィンドウを閉じるようになる。一枚のメインウィンドウに多数のモーダルを用意する場合は、あらかじめdestroy
メソッドをオーバーライドした新クラスを定義しておき、その新クラスを用いるようにすると手間が省ける……かもしれない。
やりがちなミス
メソッドのオーバーライドミス
def destroy(self):
self.master.focus_set()
self.destroy() # <- super().destroy()
この場合は RecursionError
が生じてアプリが終了できなくなる可能性がある。
閉じてからfocus_setしてしまう
class App:
#### 中略 ####
def op_sub_win1(self):
win = SubWindow1(self.root) # サブウィンドウを開く
self.root.wait_window(win) # サブウィンドウが閉じられるのを待つ
self.root.focus_set() # メインウィンドウにフォーカス
この場合、サブウィンドウが閉じられたタイミングでPC画面上の2番目のウィンドウ(アプリ)にフォーカスが移動してしまい、 self.root.focus_set()
が働かない。
おまけ
複数の子ウィンドウを別個に定義するのも手間なので、閉じたら親ウィンドウに戻るだけの機能拡張を施したサブクラスの定義を置いておきます。
class SubWindow(tk.Toplevel):
def __init__(self, master: tk.Misc = None, cnf: dict ={}, **kw):
super().__init__(master, cnf, **kw)
self.protocol("WM_DELETE_WINDOW", self.destroy)
def destroy(self):
self.master.focus_set()
super().destroy()
参考
toplevel manual page - Tk Built-In Commands
wm manual page - Tk Built-In Commands