Tkinterでウィンドウクローズイベントを処理するにはどうすればよいですか?


130

Python Tkinterプログラムでウィンドウを閉じるイベント(ユーザーが「X」ボタンをクリックする)を処理するにはどうすればよいですか?

回答:


178

Tkinterは、プロトコルハンドラーと呼ばれるメカニズムをサポートしています。ここで、プロトコルという用語は、アプリケーションとウィンドウマネージャー間の相互作用を指します。最も一般的に使用されるプロトコルはと呼ばれWM_DELETE_WINDOW、ユーザーがウィンドウマネージャーを使用してウィンドウを明示的に閉じたときに何が起こるかを定義するために使用されます。

protocolメソッドを使用して、このプロトコルのハンドラーインストールできます(ウィジェットはTkまたはToplevelウィジェットでなければなりません):

ここに具体的な例があります:

import tkinter as tk
from tkinter import messagebox

root = tk.Tk()

def on_closing():
    if messagebox.askokcancel("Quit", "Do you want to quit?"):
        root.destroy()

root.protocol("WM_DELETE_WINDOW", on_closing)
root.mainloop()

2
あなたが独立して、またはTkinterの(例:のねじれリアクタオブジェクト)のイベントループを維持ツイストのようなものを使用している場合は必ず外側のメインループは、それがその目的のために提供してどんなsmenaticsで停止されていることを確認し(例:ツイスト用)reactor.stop()
ブライアンジャック

4
Windows上のPython 2.7では、Tkinterサブモジュールメッセージボックスがありませんでした。私が使用したimport tkMessageBox as messagebox
IronManMark20

私はあなたがこの答えとコードを誰か/どこかからコピーしたことを知らせるべきだと思います。
クリスチャンディーン

1
わかりません。それは私が最初に投稿したコードではありません。
マットグレゴリー

私にはうまくいきません。ウィンドウを強制的に閉じたとき(Alt + F4などを使用)のグラフィックスの中断に対する古典的なPythonの無秩序な反応は変わりません。
Apostolos

29

Mattは、閉じるボタンの1つの古典的な変更を示しました。
もう1つは、閉じるボタンでウィンドウを最小化することです。
あなたは持っていることによって、この動作を再現することができますアイコン化方法を
されたプロトコルメソッドの第二引数。

これは、Windows 7および10でテストされた実際の例です。

# Python 3
import tkinter
import tkinter.scrolledtext as scrolledtext

root = tkinter.Tk()
# make the top right close button minimize (iconify) the main window
root.protocol("WM_DELETE_WINDOW", root.iconify)
# make Esc exit the program
root.bind('<Escape>', lambda e: root.destroy())

# create a menu bar with an Exit command
menubar = tkinter.Menu(root)
filemenu = tkinter.Menu(menubar, tearoff=0)
filemenu.add_command(label="Exit", command=root.destroy)
menubar.add_cascade(label="File", menu=filemenu)
root.config(menu=menubar)

# create a Text widget with a Scrollbar attached
txt = scrolledtext.ScrolledText(root, undo=True)
txt['font'] = ('consolas', '12')
txt.pack(expand=True, fill='both')

root.mainloop()

この例では
、従来の[ファイル]→[終了Esc]とボタンの2つの新しい終了オプションをユーザーに提供します。


12

Tkinterアクティビティに応じて、特にTkinter.afterを使用する場合は、destroy()protocol()やボタンなどを使用してこのアクティビティを停止すると、単に終了するのではなく、このアクティビティを妨害します(「実行中」エラー)。 。ほとんどすべての場合の最良の解決策は、フラグを使用することです。これは、それを使用する方法の単純で愚かな例です(ほとんどの人はそれを必要としないと確信しています!:)

from Tkinter import *

def close_window():
  global running
  running = False  # turn off while loop
  print( "Window closed")

root = Tk()
root.protocol("WM_DELETE_WINDOW", close_window)
cv = Canvas(root, width=200, height=200)
cv.pack()

running = True;
# This is an endless loop stopped only by setting 'running' to 'False'
while running: 
  for i in range(200): 
    if not running: 
        break
    cv.create_oval(i, i, i+1, i+1)
    root.update() 

これにより、グラフィックアクティビティが適切に終了します。running適切な場所で確認するだけです。


4

これに注目していただいたApostolosの回答に感謝します。2019年のPython 3のより詳細な例を以下に示します。説明とコード例が明確になっています。


ユーザーがdestroy()ウィンドウを閉じると、ウィンドウとそのウィンドウの実行中のすべてのコールバックが即座に破棄される(またはカスタムウィンドウを閉じるハンドラーがない場合)ことに注意してください。

これは、現在のTkinterアクティビティに応じて、特にtkinter.after(定期的なコールバック)を使用している場合に悪い場合があります。一部のデータを処理してディスクに書き込むコールバックを使用している可能性があります。その場合、データが突然強制終了されずに終了するようにしたいことは明らかです。

そのための最良の解決策は、フラグを使用することです。したがって、ユーザーがウィンドウを閉じるように要求した場合、それをフラグとしてマークし、それに反応します。

(注:通常、私はGUIを適切にカプセル化されたクラスと個別のワーカースレッドとして設計し、「グローバル」は使用しません(代わりにクラスインスタンス変数を使用します)。ユーザーがウィンドウを閉じたときにTkが定期的なコールバックを突然停止する方法...)

from tkinter import *
import time

# Try setting this to False and look at the printed numbers (1 to 10)
# during the work-loop, if you close the window while the periodic_call
# worker is busy working (printing). It will abruptly end the numbers,
# and kill the periodic callback! That's why you should design most
# applications with a safe closing callback as described in this demo.
safe_closing = True

# ---------

busy_processing = False
close_requested = False

def close_window():
    global close_requested
    close_requested = True
    print("User requested close at:", time.time(), "Was busy processing:", busy_processing)

root = Tk()
if safe_closing:
    root.protocol("WM_DELETE_WINDOW", close_window)
lbl = Label(root)
lbl.pack()

def periodic_call():
    global busy_processing

    if not close_requested:
        busy_processing = True
        for i in range(10):
            print((i+1), "of 10")
            time.sleep(0.2)
            lbl["text"] = str(time.time()) # Will error if force-closed.
            root.update() # Force redrawing since we change label multiple times in a row.
        busy_processing = False
        root.after(500, periodic_call)
    else:
        print("Destroying GUI at:", time.time())
        try: # "destroy()" can throw, so you should wrap it like this.
            root.destroy()
        except:
            # NOTE: In most code, you'll wanna force a close here via
            # "exit" if the window failed to destroy. Just ensure that
            # you have no code after your `mainloop()` call (at the
            # bottom of this file), since the exit call will cause the
            # process to terminate immediately without running any more
            # code. Of course, you should NEVER have code after your
            # `mainloop()` call in well-designed code anyway...
            # exit(0)
            pass

root.after_idle(periodic_call)
root.mainloop()

このコードはWM_DELETE_WINDOW、カスタムperiodic_call()が作業/ループの最中にビジー状態でもハンドラーが実行されることを示しています!

.after()500ミリ秒というかなり誇張された値を使用します。これは、ちょうどあなたが定期的なコールがビジー状態の間、閉じ、あるいはないとの違いを確認するのは非常に簡単にそれを作るためのものです...あなたは近い数字が更新している間場合は、ことがわかりますWM_DELETE_WINDOWが起こっている間、あなたの定期的な呼び出しが「でしたビジー処理:True」。数字が一時停止しているときにクローズすると(定期的なコールバックがその時点で処理されないことを意味します)、「ビジーでない」間にクローズが発生したことがわかります。

実際の使用では、.after()30〜100ミリ秒のようなものを使用して、応答性の高いGUIを作成します。これは、Tkのデフォルトの「閉じるときにすべての作業を即座に中断する」動作から身を守る方法を理解するのに役立つデモにすぎません。

要約すると、WM_DELETE_WINDOWハンドラーにフラグを設定し、そのフラグを定期的かつ手動でチェックして.destroy()、ウィンドウが安全なときに(アプリがすべての作業を終えたとき)にします。

PS:ユーザーが本当にウィンドウを閉じたいかどうかWM_DELETE_WINDOW尋ねるのにも使用できます。そして、彼らがノーと答えた場合、あなたはフラグをセットしません。とても簡単です。にメッセージボックスを表示WM_DELETE_WINDOWし、ユーザーの回答に基づいてフラグを設定するだけです。


1

シンプルバージョンを試してください:

import tkinter

window = Tk()

closebutton = Button(window, text='X', command=window.destroy)
closebutton.pack()

window.mainloop()

または、さらにコマンドを追加したい場合:

import tkinter

window = Tk()


def close():
    window.destroy()
    #More Functions


closebutton = Button(window, text='X', command=close)
closebutton.pack()

window.mainloop()

問題は、ウィンドウを閉じるためのOSのXボタンに関するもので、通常のボタンコントロールではありません。
user1318499

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.