【问题标题】:How do I handle the window close event in Tkinter?如何处理 Tkinter 中的窗口关闭事件?
【发布时间】:2023-04-02 07:13:01
【问题描述】:

如何在 Python Tkinter 程序中处理窗口关闭事件(用户单击“X”按钮)?

【问题讨论】:

    标签: python events tkinter window


    【解决方案1】:

    Tkinter 支持一种称为protocol handlers 的机制。这里,术语protocol 指的是应用程序和窗口管理器之间的交互。最常用的协议称为WM_DELETE_WINDOW,用于定义当用户使用窗口管理器显式关闭窗口时会发生什么。

    您可以使用protocol 方法为此协议安装处理程序(小部件必须是TkToplevel 小部件):

    这里有一个具体的例子:

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

    【讨论】:

    • 如果您使用的是像 Twisted 这样独立维护事件循环的东西或 Tkinter(例如:twisted 的反应器对象),请确保外部主循环停止使用它为此目的提供的任何符号(例如:reactor .stop() 表示扭曲)
    • 在我的 Windows 上的 Python 2.7 上,Tkinter 没有子模块消息框。我用import tkMessageBox as messagebox
    • 我认为您应该知道您从其他人/其他地方复制了此答案和代码。
    • 我不知道,这不是我最初发布的代码。
    • 对我不起作用。当硬关闭窗口(例如使用 Alt+F4)时,它不会改变经典 Python 对图形中断的混乱反应。
    【解决方案2】:

    马特展示了关闭按钮的一个经典修改。
    另一种是让关闭按钮最小化窗口。
    您可以通过 iconify 方法重现此行为
    成为protocol 方法的第二个参数。

    这是一个工作示例,在 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 按钮。

    【讨论】:

    • 绝对有趣!但是,当我点击关闭按钮时,我会立即卸载一个没有退出的程序。
    • 是的,它违反了the principle of least astonishment。我将留下它,因为它仍然是问题的有效答案,并且该示例具有一些额外的教育价值。
    • 当然——它得到了我的支持。 :-)
    【解决方案3】:

    取决于 Tkinter 活动,尤其是在使用 Tkinter.after 时,使用 destroy() 停止此活动 - 即使使用协议()、按钮等 - 会干扰此活动(“执行时”错误) 而不是仅仅终止它。几乎在所有情况下,最好的解决方案是使用标志。这是一个如何使用它的简单而愚蠢的示例(尽管我确信你们中的大多数人都不需要它!:)

    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】:

      试试简单版:

      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 用于关闭窗口的按钮,而不是常规的按钮控件。
      【解决方案5】:

      我要感谢 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 发生 您的定期通话“忙于处理:真”。如果您在数字暂停时关闭(意味着此时没有处理周期性回调),您会看到关闭发生在它“不忙”时。

      在实际使用中,您的 .after() 将使用 30-100 毫秒之类的时间来获得响应式 GUI。这只是一个演示,帮助您了解如何保护自己免受 Tk 默认的“关闭时立即中断所有工作”行为。

      总而言之:让WM_DELETE_WINDOW 处理程序设置一个标志,然后定期检查该标志,并在窗口安全时手动.destroy()(当您的应用完成所有工作时)。

      PS:您也可以使用WM_DELETE_WINDOW询问用户是否真的想关闭窗口;如果他们回答“否”,您就不会设置标志。这很简单。您只需在 WM_DELETE_WINDOW 中显示一个消息框并根据用户的回答设置标志。

      【讨论】:

        【解决方案6】:

        我说更简单的方法是使用break 命令,比如

        import tkinter as tk
        win=tk.Tk
        def exit():
            break
        btn= tk.Button(win, text="press to exit", command=exit)
        win.mainloop()
        

        或使用sys.exit()

        import tkinter as tk
        import sys
        win=tk.Tk
        def exit():
            sys.exit
        btn= tk.Button(win, text="press to exit", command=exit)
        win.mainloop()
        

        【讨论】:

        • 正如其他答案的 cmets 中所说,“问题是关于操作系统的 X 按钮用于关闭窗口,而不是常规按钮控件。”
        【解决方案7】:

        如果您想更改 x 按钮的功能或使其无法关闭,请尝试此操作。

        yourwindow.protocol("WM_DELETE_WINDOW", whatever)
        

        然后无视“随便”是什么意思

        def whatever():
            # Replace this with your own event for example:
            print("oi don't press that button")
        

        您也可以这样做,以便在关闭该窗口时可以像这样调用它

        yourwindow.withdraw() 
        

        这会隐藏窗口但不会关闭它

        yourwindow.deiconify()
        

        这使窗口再次可见

        【讨论】:

          【解决方案8】:

          你应该使用 destroy() 来关闭一个 tkinter 窗口。

             from Tkinter import *
             root = Tk()
             Button(root, text="Quit", command=root.destroy).pack()
             root.mainloop()
          

          解释:

          root.quit() 上面的行只是绕过了root.mainloop(),即如果quit()命令被执行,root.mainloop()仍将在后台运行。

          root.destroy()destroy() 命令消失时root.mainloop()root.mainloop() 停止。

          所以你只想退出程序,所以你应该使用root.destroy(),因为它会停止 mainloop()`。

          但是如果你想运行一些无限循环并且你不想破坏你的 Tk 窗口并且想在root.mainloop() 行之后执行一些代码,那么你应该使用root.quit()。 例如:

          from Tkinter import *
          def quit():
              global root
              root.quit()
          
          root = Tk()
          while True:
              Button(root, text="Quit", command=quit).pack()
              root.mainloop()
              #do something
          

          【讨论】:

            【解决方案9】:

            最简单的代码是:

            from tkinter import *
            window = Tk()
            

            隐藏窗口:window.withdraw()

            出现窗口:window.deiconify()

            退出窗口:exit()

            用于退出窗口(如果您制作了 .exe 文件):

            from tkinter import *
            import sys
            window = Tk()
            sys.exit()
            

            当然你必须放置一个按钮并在函数中使用上面的代码,这样你就可以在按钮的命令部分输入函数的名称

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2021-12-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多