python3 Tips

Step07:一気にウィンドウを2つ立ち上げる

Use the Toplevel widget

すぐには必要ないのですが、そのうちウィンドウを2つ同時にたちあげたい、という時が出てくるかもしれません。 そんな時のために、一応、練習しておきましょう。

これを実現するためには、Toplevel widget を利用します。使い方は、self.rootを設定したときにTk()としたのと同じように、 2つ目のウィンドウをTopLevel()と指定してあげればOKです(19行目)。pack()する必要はありません。

#!/usr/bin/env python
# coding=utf-8

from tkinter import *  #@UnusedWildImport

class Step7_D_Windows(object):
    def __init__(self):
        # ルートウィンドウ -----------------------------------------------------------------
        self.root = Tk()
        self.root.title('Root Window')

        self.frm_Main = Frame(self.root)
        self.frm_Main.pack()

        self.lbl_Hello = Label(self.frm_Main, text = 'ルートのウィンドウです。')
        self.lbl_Hello.pack()

        # トップレベルウィンドウ -----------------------------------------------------------
        self.t_LevelWindow = Toplevel()
        self.t_LevelWindow.title('Top Level Window')

        self.frm_TopLevel = Frame(self.t_LevelWindow)
        self.frm_TopLevel.pack()

        self.lbl_TopText = Label(self.t_LevelWindow, text = 'トップレベルのウィンドウです。')
        self.lbl_TopText.pack()

        self.root.mainloop()

if __name__ == '__main__':
    app = Step7_D_Windows()
			

少し改良してみる

で、これを少し改造して、rootにボタンを付けてクリックで別ウィンドウを起動なんてこともできます。

19行目: command = self.openWindowを追加
24行目: openWindow(self)以下に先の#トップレベルウィンドウ以下の記述を貼り付ける。

#!/usr/bin/env python
# coding=utf-8

from tkinter import *  #@UnusedWildImport

class Step7_D_Windows(object):
    def __init__(self):
        # ルートウィンドウ --------------------------------------------------------------
        self.root = Tk()
        self.root.title('Root Window')

        self.frm_Main = Frame(self.root)
        self.frm_Main.pack()

        self.lbl_Hello = Label(self.frm_Main, text = 'ルートのウィンドウです。')
        self.lbl_Hello.pack()

        self.btn_OpenWindow = Button(self.frm_Main)
        self.btn_OpenWindow.configure(text = 'ウィンドウを開く', command = self.openWindow)
        self.btn_OpenWindow.pack()

        self.root.mainloop()

    def openWindow(self):
        self.t_LevelWindow = Toplevel()
        self.t_LevelWindow.title('Top Level Window')

        self.frm_TopLevel = Frame(self.t_LevelWindow)
        self.frm_TopLevel.pack()

        self.lbl_TopText = Label(self.t_LevelWindow, text = 'トップレベルのウィンドウです。')
        self.lbl_TopText.pack()

if __name__ == '__main__':
    app = Step7_D_Windows()
			

ちなみに from tkinter import * の後ろについている #@UnusedWildImport ですが、eclipseで作成していると、 やたらと警告が出るので、つけているだけです。


更に改良してみる

重複立ち上げを何とかする

上の例では、ボタンを押すと別ウィンドウが立ち上がるようにしたのですが、押せば押すほど、その回数分ウィンドウが立ち上がってしまいます。 これは、正直言って酷いです。そこで、少し改良を加え、2重立ち上げを防止する策を講じてみましょう。

#!/usr/bin/env python
# coding=utf-8

from tkinter import *  #@UnusedWildImport

class Step7_D_Windows(object):
    def __init__(self):
        # attributes -------------------------------------------------------------
        self.EXIST_OR_NOT = False

        # ルートウィンドウ -------------------------------------------------------
        self.root = Tk()
        self.root.title('Root Window')

        self.frm_Main = Frame(self.root)
        self.frm_Main.pack()

        self.lbl_Hello = Label(self.frm_Main, text = 'ルートのウィンドウです。')
        self.lbl_Hello.pack()

        self.btn_OpenWindow = Button(self.frm_Main)
        self.btn_OpenWindow.configure(text = 'ウィンドウを開く', command = self.openWindow)
        self.btn_OpenWindow.pack()

        self.root.mainloop()

    def openWindow(self):
        if not self.EXIST_OR_NOT:
            self.t_LevelWindow = Toplevel()
            self.t_LevelWindow.title('Top Level Window')

            self.frm_TopLevel = Frame(self.t_LevelWindow)
            self.frm_TopLevel.pack()

            self.lbl_TopText = Label(self.t_LevelWindow, text='トップレベルのウィンドウです。')
            self.lbl_TopText.pack()

            self.EXIST_OR_NOT = self.t_LevelWindow.winfo_exists()

if __name__ == '__main__':
    app = Step7_D_Windows()
			

winfo_exists()メソッドを利用します。このメソッドは該当するウィジットが存在すれば1(True)を返すので、 トップレベル・ウィンドウが立ち上がっていないとき、つまり、Falseの際にトップレベルが立ち上がるようにします。

まず、True Falseの受け皿用に、EXIST_OR_NOTという変数をStep7_D_Windowsの属性に追加しました(9行目 名前は何でもいいです)。 これにFalseを入れておきます。そしてopenWindow()メソッドの最初の行(28行目)に、if文を追加しEXIST_OR_NOTが、not Trueの場合 (つまり、Falseの場合)、トップレベルが立ち上がり、これ以上ボタンを押してもトップレベルが立ち上がらないようにEXIST_OR_NOTに、 Trueの値を与えます。それが38行目の


self.EXIST_OR_NOT = self.t_LevelWindow.winfo_exists()

			

という箇所です。この場合すでにトップレベルが立ち上がっているので、1(True)の値がEXIST_OR_NOT に代入されます。

これで2重立ち上げを防止することができました。


そして、完成へ…。

その後のことも考える

トップレベルの2重立ち上げを防止することはできたのですが、一旦、トップレベルを閉じて、再度、 トップレベルを立ち上げようとすると何も起こりません。これは、先に、


self.EXIST_OR_NOT = self.t_LevelWindow.winfo_exists()

			

で設定したEXIST_OR_NOT にTrueの値が残っているためです。トップレベルが閉じられた後に、再度、 ボタンが押された時の場合を考慮しましょう。

#!/usr/bin/env python
# coding=utf-8

from tkinter import *  #@UnusedWildImport

class Step7_D_Windows(object):
    def __init__(self):
        # attributes -------------------------------------------------------------
        self.EXIST_OR_NOT = False

        # ルートウィンドウ ------------------------------------------------------
        self.root = Tk()
        self.root.title('Root Window')

        self.frm_Main = Frame(self.root)
        self.frm_Main.pack()

        self.lbl_Hello = Label(self.frm_Main, text = 'ルートのウィンドウです。')
        self.lbl_Hello.pack()

        self.btn_OpenWindow = Button(self.frm_Main)
        self.btn_OpenWindow.configure(text = 'ウィンドウを開く', command = self.openWindow)
        self.btn_OpenWindow.pack()

        self.root.mainloop()

    def openWindow(self):
        if not self.EXIST_OR_NOT:
            self.t_LevelWindow = Toplevel()
            self.t_LevelWindow.title('Top Level Window')

            self.frm_TopLevel = Frame(self.t_LevelWindow)
            self.frm_TopLevel.pack()

            self.lbl_TopText = Label(self.t_LevelWindow, text='トップレベルのウィンドウです。')
            self.lbl_TopText.pack()

            self.EXIST_OR_NOT = self.t_LevelWindow.winfo_exists()

            self.t_LevelWindow.protocol('WM_DELETE_WINDOW', self.changeFlag)

    def changeFlag(self):
        self.EXIST_OR_NOT = False
        self.t_LevelWindow.destroy()

if __name__ == '__main__':
    app = Step7_D_Windows()
			

これを実現するためには、protocol()メソッドを使います(40行目)。 第1引数に'WM_DELETE_WINDOW'を渡し、第2引数にfunction(この場合:self.changeFlag)を渡します。 WM_DELETE_WINDOWはウィンドウが閉じられた時のイベントを定義するときに使用します。 このfunctionをEXIST_OR_NOT=Falseに戻しておけば、再度ボタンが押されたときも、トップレベルが立ち上がるようになります。 そして、最後にトップレベルを.destroy()で終了させます(44行目)。 .destroy()を書いておかないとウィンドウ右上の×を押しても終了しないので、注意しましょう。