Tkinter'de pencere kapatma olayını nasıl işleyebilirim?


131

Bir Python Tkinter programında pencere kapatma olayını (kullanıcı 'X' düğmesine tıklar) nasıl işleyebilirim?

Yanıtlar:


178

Tkinter, protokol işleyicileri adı verilen bir mekanizmayı destekler . Burada protokol terimi, uygulama ile pencere yöneticisi arasındaki etkileşimi ifade eder. En sık kullanılan protokoleWM_DELETE_WINDOW ve kullanıcı pencere yöneticisini kullanarak bir pencereyi açıkça kapattığında ne olacağını tanımlamak için kullanılır.

Bu protokol için bir işleyici yüklemek için protocolyöntemi kullanabilirsiniz (pencere öğesi bir veya pencere öğesi olmalıdır ):TkToplevel

Burada somut bir örnek var:

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
Bir olay döngüsünü bağımsız olarak sürdüren Twisted veya Tkinter gibi bir şey kullanıyorsanız (örneğin: twisted'ın reaktör nesnesi), dış ana döngünün bu amaç için sağladığı smenatiklerle durdurulduğundan emin olun (örneğin: reactor.stop () bükülmüş için)
Brian Jack

4
Windows üzerindeki Python 2.7'de, Tkinterbir alt modül mesaj kutusu yoktu. Kullandımimport tkMessageBox as messagebox
IronManMark20

Sanırım bu cevabı ve kodu birinden / başka bir yerden kopyaladığınızı bilmelisiniz.
Christian Dean

1
Bilmiyorum, ilk gönderdiğim kod bu değil.
Matt Gregory

Benim için çalışmıyor. Klasik Python'un pencereyi sert bir şekilde kapattığında (örneğin Alt + F4 ile) grafik kesintisine olan kaotik tepkisini değiştirmez.
Apostolos

29

Matt, kapat düğmesinin klasik bir değişikliğini gösterdi.
Diğeri ise kapat düğmesinin pencereyi simge durumuna küçültmesidir.
Bu davranışı, iconify yönteminin protokol yönteminin ikinci bağımsız değişkeni
olmasını sağlayarak yeniden oluşturabilirsiniz .

İşte Windows 7 ve 10'da test edilmiş çalışan bir örnek:

# 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()

Bu örnekte, kullanıcıya iki yeni çıkış seçeneği sunuyoruz:
klasik Dosya → Çıkış ve ayrıca Escdüğme.


14

Tkinter etkinliğine bağlı olarak ve özellikle Tkinter.after kullanılırken, bu etkinliği durdurmak destroy()- protokol (), bir düğme, vb. Kullanarak bile - bu etkinliği sonlandırmak yerine ("hata yürütürken") bozacaktır. . Hemen hemen her durumda en iyi çözüm bir bayrak kullanmaktır. İşte nasıl kullanılacağına dair basit, saçma bir örnek (çoğunuzun buna ihtiyaç duymadığına eminim! :)

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() 

Bu, grafik aktivitesini güzelce sonlandırır. Sadece runningdoğru yer (ler) de kontrol etmeniz gerekiyor .


4

Bunu dikkatime sunduğunuz için Apostolos'un cevabına teşekkür etmek istiyorum. İşte 2019 yılında Python 3 için daha net bir açıklama ve örnek kodla çok daha ayrıntılı bir örnek.


Kullanıcı destroy()pencereyi kapattığında pencereyi ve çalışan tüm geri aramalarını anında yok edeceğine (veya özel bir pencere kapatma işleyicisine sahip olmadığına) dikkat edin .

Mevcut Tkinter etkinliğinize bağlı olarak ve özellikle kullanırken bu sizin için kötü olabilir. tkinter.after (periyodik geri aramalar) . Bazı verileri işleyen ve diske yazan bir geri arama kullanıyor olabilirsiniz ... bu durumda, açıkça veri yazmanın aniden öldürülmeden bitmesini istersiniz.

Bunun için en iyi çözüm bayrak kullanmaktır. Dolayısıyla, kullanıcı pencerenin kapatılmasını istediğinde, bunu bir bayrak olarak işaretler ve ardından buna tepki verirsiniz.

(Not: Normalde GUI'leri güzel bir şekilde kapsüllenmiş sınıflar ve ayrı çalışan iş parçacıkları olarak tasarlıyorum ve kesinlikle "global" kullanmıyorum (bunun yerine sınıf örnek değişkenleri kullanıyorum), ancak bunun gösterilmesi için basit, soyulmuş bir örnek olması gerekiyor. Tk, kullanıcı pencereyi kapattığında periyodik geri aramalarınızı nasıl aniden sonlandırır ...)

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()

Bu kod, WM_DELETE_WINDOWişleyicinin bizim özel sistemimizperiodic_call() işimiz / döngülerin ortasında meşgulken !

Oldukça abartılı .after()değerler kullanıyoruz: 500 milisaniye. Bu sadece periyodik çağrı meşgulken kapatmak veya olmamak arasındaki farkı görmenizi çok kolaylaştırmak içindir ... Numaralar güncellenirken kapatırsanız , periyodik aramanız sırasındaWM_DELETE_WINDOW gerçekleştiğini göreceksiniz " meşgul işleme: Doğru ". Numaralar duraklatılmış haldeyken kapatırsanız (yani periyodik geri aramanın o anda işlenmediği anlamına gelir), kapanmanın "meşgul değilken" gerçekleştiğini görürsünüz.

Gerçek dünya kullanımında, .after()duyarlı bir GUI'ye sahip olmak için 30-100 milisaniye gibi bir şey kullanırsınız. Bu, kendinizi Tk'nin varsayılan "kapanırken tüm işleri anında durdur" davranışına karşı nasıl koruyacağınızı anlamanıza yardımcı olacak bir gösterimdir.

Özetle: WM_DELETE_WINDOWİşleyicinin bir bayrak ayarlamasını .destroy()sağlayın ve ardından bu bayrağı periyodik olarak ve güvenli olduğunda pencereyi manuel olarak kontrol edin (uygulamanız tüm işlerle bittiğinde).

NOT: Eger kullanabilirsiniz WM_DELETE_WINDOWiçin sormak onlar gerçekten pencereyi kapatmak istiyorsanız kullanıcıyı; ve hayır cevabını verirlerse bayrağı koymazsınız. Çok basit. Siz sadece bir mesaj kutusu gösterin WM_DELETE_WINDOWve kullanıcının cevabına göre bayrağı ayarlayın.


1

Basit Sürümü Deneyin:

import tkinter

window = Tk()

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

window.mainloop()

Veya Daha Fazla Komut Eklemek İstiyorsanız:

import tkinter

window = Tk()


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


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

window.mainloop()

Soru, normal bir düğme kontrolü değil, OS'nin pencereyi kapatmak için X düğmesiyle ilgili.
user1318499

-1
from tkinter import*
root=Tk()
exit_button=Button(root,text="X",command=root.quit)
root.mainloop()
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.