【问题标题】:Exiting a tkinter app with Ctrl-C and catching SIGINT使用 Ctrl-C 退出 tkinter 应用程序并捕获 SIGINT
【发布时间】:2016-10-03 21:37:43
【问题描述】:

Ctrl-C/SIGTERM/SIGINT 似乎被 tkinter 忽略了。通常可以是captured again with a callback。这似乎不起作用,所以我想我会运行 tkinter in another thread,因为它是 mainloop() is an infinite loop and blocks。我实际上也想这样做以在单独的线程中从标准输入读取。即使在此之后,在我关闭窗口之前,仍然不会处理 Ctrl-C。这是我的 MWE:

#! /usr/bin/env python
import Tkinter as tk
import threading
import signal
import sys

class MyTkApp(threading.Thread):
    def run(self):
        self.root = tk.Tk()
        self.root.mainloop()

app = MyTkApp()
app.start()

def signal_handler(signal, frame):
    sys.stderr.write("Exiting...\n")

    # think only one of these is needed, not sure
    app.root.destroy()
    app.root.quit()

signal.signal(signal.SIGINT, signal_handler)

结果:

  • 运行应用程序
  • 终端中的 Ctrl-C(没有任何反应)
  • 关闭窗口
  • “Exiting...”被打印出来,我收到一个关于循环已经退出的错误。

这里发生了什么,如何让 Ctrl-C 从终端关闭应用程序?


更新:Adding a pollas suggested,在主线程中工作,但在另一个线程中启动时没有帮助...

class MyTkApp(threading.Thread):
    def poll(self):
        sys.stderr.write("poll\n")
        self.root.after(50, self.poll)

    def run(self):
        self.root = tk.Tk()
        self.root.after(50, self.poll)
        self.root.mainloop()

【问题讨论】:

  • 明确一点:您想从终端而不是从 GUI 本身执行 control-c,对吗?
  • @BryanOakley 是的,在开发过程中经常测试会非常方便。
  • 以下链接是否回答了您的问题? stackoverflow.com/a/13784297/7432
  • @BryanOakley 我的应用程序中已经有这个投票,它不会影响行为。信号回调在窗口关闭之前不会运行。
  • 您确定投票正在进行吗?在使用 poll 的时候,你是不是也在使用多线程呢?另外,你是在什么平台上体验的?如果你从stackoverflow.com/a/13784297/7432 获取确切的代码并运行它,它对你有用吗?

标签: python multithreading tkinter signals


【解决方案1】:

由于您的 tkinter 应用程序在另一个线程中运行,您无需在主线程中设置信号处理程序,只需在 app.start() 语句之后使用以下代码块:

import time

while app.is_alive():
    try:
        time.sleep(0.5)
    except KeyboardInterrupt:
        app.root.destroy()
        break

然后您可以使用 Ctrl-C 引发 KeyboardInterrupt 异常以关闭 tkinter 应用程序并中断 while 循环。如果您关闭 tkinter 应用程序,while 循环也将终止。

请注意,上述代码仅适用于 Python 2(因为您在代码中使用 Tkinter)。

【讨论】:

  • 谢谢!这确实关闭了窗口,但某些东西仍在运行并且进程拒绝关闭。你还能说出为什么信号处理程序不起作用吗?我想知道为什么这么难。
  • 信号处理程序不起作用,因为它只在主线程中起作用。但是代码中的主线程在signal.signal(...) 语句之后完成。您可以尝试在 signal.signal(...) 语句后添加 while True: pass 以保持主线程处于活动状态,然后信号处理程序将正常工作。
【解决方案2】:

这是一个在窗口或命令行中捕获控制 c 的工作示例。这是用 3.7.2 测试的,这似乎比其他解决方案更简单。我几乎觉得我错过了什么。

import tkinter as TK

import signal

def hello():
    print("Hello")

root = TK.Tk()

TK.Button(root, text="Hi", command=(hello)).pack(  )

def handler(event):
    root.destroy()
    print('caught ^C')

def check():
    root.after(500, check)  #  time in ms.

# the or is a hack just because I've shoving all this in a lambda. setup before calling main loop
signal.signal(signal.SIGINT, lambda x,y : print('terminal ^C') or handler(None))

# this let's the terminal ^C get sampled every so often
root.after(500, check)  #  time in ms.

root.bind_all('<Control-c>', handler)
 
root.mainloop()

【讨论】:

    【解决方案3】:

    在 Python 中正确使用 CTRL-C 和 SIGINT

    问题是你正在退出主线程,所以信号处理程序基本上没用。您需要在 while 循环或我的个人偏好中保持它运行,Events 来自 threading 模块。您也可以只捕获由 CTRL-C 事件生成的KeyboardInterrupt 异常,而不是处理信号处理程序。

    Tkinter 中的 SIGINT

    使用 tkinter,您必须让 tkinter 应用程序在单独的线程中运行,这样它就不会干扰信号处理程序或KeyboardInterrupt 异常。在处理程序中,要退出,您需要销毁然后更新 tkinter 根。更新允许 tkinter 更新以便它关闭,而无需等待主循环。否则,用户必须点击活动窗口来激活主循环。

    # Python 3
    from tkinter import *
    from threading import Thread
    import signal
    
    class MyTkApp(Thread):
        def run(self):
            self.root = Tk()
            self.root.mainloop()
    
    def sigint_handler(sig, frame):
        app.root.quit()
        app.root.update()
    
    app = MyTkApp()
    
    # Set signal before starting
    signal.signal(signal.SIGINT, sigint_handler)
    
    app.start()
    

    注意:如果您将处理程序设置在与 tkinter mainloop 相同的线程中,也可以捕获 SIGINT,但是您需要在信号之后使 tkinter 窗口处于活动状态,以便它的 mainloop 可以运行。除非您在新线程中运行,否则无法解决此问题。

    更多关于 Tkinter 和命令行通信的信息

    有关 tkinter 和命令行之间通信的更多信息,请参阅Using Tkinter Without Mainloop。基本上,您可以在循环中使用更新方法,然后与其他线程和进程等进行通信。我个人不推荐这样做,因为您本质上是在做 python 线程控制系统的工作,这可能与您的相反想要做。 (python 有一个进程在一个外部线程中运行所有内部线程,因此除非使用multiprocessing 模块,否则您不会利用多标题)

    # Python 2
    from Tkinter import *
    
    ROOT = Tk()
    LABEL = Label(ROOT, text="Hello, world!")
    LABEL.pack()
    LOOP_ACTIVE = True
    while LOOP_ACTIVE:
        ROOT.update()
        USER_INPUT = raw_input("Give me your command! Just type \"exit\" to close: ")
        if USER_INPUT == "exit":
            ROOT.quit()
            LOOP_ACTIVE = False
        else:
            LABEL = Label(ROOT, text=USER_INPUT)
            LABEL.pack()
    

    【讨论】:

    • 谢谢@gagarwal。为避免“仅链接答案”,您是否可以从链接中复制/粘贴密钥 sn-p 或其他内容,以防页面关闭或移动。
    • 我根据自己的经验进行了更新。抱歉,现在无法发布代码,就像在移动设备上一样。如果您还有其他问题,请联系我们。
    猜你喜欢
    • 2012-07-03
    • 2010-09-15
    • 2011-11-22
    • 1970-01-01
    • 2011-12-30
    • 2017-09-05
    • 2013-12-02
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多