LoginSignup
0
0

Tkinterで閉じると親ウィンドウに戻るサブウィンドウを作る

Last updated at Posted at 2024-05-16

はじめに

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クラスを継承した SubWindow1SubWindow2という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

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0