使用Ctrl-C退出tkinter应用程序并捕获SIGINT

时间:2016-10-03 21:37:43

标签: python multithreading tkinter signals

tkinter似乎忽略了Ctrl-C / SIGTERM / SIGINT。通常它可以是captured again with a callback。这似乎没有用,所以我认为自in another thread以来我就会运行tkinter mainloop() is an infinite loop and blocks。我实际上也想在另一个线程中从stdin读取。即使在此之后,在关闭窗口之前,仍然不会处理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(没有任何反应)
  • 关闭窗口
  • “退出...”已打印,我收到有关已经退出的循环的错误。

这里发生了什么,如何从终端关闭应用程序使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()

3 个答案:

答案 0 :(得分:3)

Python中正确的CTRL-C和SIGINT用法

问题是您要退出主线程,因此信号处理程序基本上没有用。您需要使其循环运行,或者循环运行,或者根据我的个人喜好Events模块中的threading。您也可以只捕获CTRL-C事件生成的KeyboardInterrupt异常,而不用处理信号处理程序。

Tkinter中的SIGINT

使用tkinter,必须使tkinter应用程序在单独的线程中运行,以免干扰信号处理程序或KeyboardInterrupt异常。在处理程序中,要退出,您需要销毁然后更新tkinter根目录。 Update允许tkinter更新,使其关闭,而无需等待mainloop。否则,用户必须单击活动窗口以激活主循环。

# 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。基本上,您可以在循环中使用update方法,然后与其他线程和进程等进行通信。我个人不建议这样做,因为您实际上是在执行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()

答案 1 :(得分:2)

由于你的tkinter应用程序在另一个线程中运行,你不需要在主线程中设置信号处理程序,只需在int visits[] = null; // Increments visit count of crewMemberName on planetNameVariable visits = tm.get(planetNameVariable); if (visits == null) { tm.put(planetNameVariable, new int[14]); visits = tm.get(planetNameVariable); } visits[indexDesired]++; // Get an iterator Iterator<String> iterator = tm.keySet().iterator(); while (iterator.hasNext()) { String key = iterator.next(); int[] temp = tm.get(key); if (temp != null) { for (int i = 0; i < temp.length; i++) { System.out.println(key + " " + temp[i]); } } } 语句之后使用以下代码块:

app.start()

然后,您可以使用 Ctrl-C 引发import time while app.is_alive(): try: time.sleep(0.5) except KeyboardInterrupt: app.root.destroy() break 异常以关闭tkinter应用并中断while循环。如果您关闭tkinter应用程序,也会终止while循环。

请注意,上面的代码仅适用于Python 2(在代码中使用KeyboardInterrupt)。

答案 2 :(得分:1)

这是一个在窗口中或从命令行捕获控件 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()