在 python tkinter 应用程序中捕获 macos ⌘Q

问题描述 投票:0回答:3

在 tkinter 应用程序中,我正在捕获几个事件,以便在主线程终止之前正常关闭应用程序中的某些线程。 只要我使用绑定组合键或 窗口控制,红圈内的十字。

在 Macos 上,应用程序会自动获取一个“Python”菜单,其中包含绑定到组合键 ⌘Q 的关闭功能。此事件没有得到妥善处理。似乎杀死了主线程,但其他线程没有正确关闭。

以下绑定用于捕获所有关闭事件:

self.root.bind('<Control-x>', self.exitapp)
self.root.protocol("WM_DELETE_WINDOW", self.exitapp)

atexit.register(self.catch_atexit)

最近发现左右键分别表示为Meta_L和Meta_R,但不能与第二个键(即'')组合。

谁能解释一下如何捕捉⌘Q

请参阅下面的代码示例:

#!/usr/bin/env python3
import sys
from tkinter import *
from tkinter import ttk

import threading
import time

import atexit


class subthread():
    def __init__(self):
        self.thr = None
        self.command = ''
        self.proof = ""

    def start(self):
        if not self.thr or not self.thr.is_alive():
            self.command = 'run'
            self.thr = threading.Thread(target=self.loop)
            self.thr.start()
        else:
            print('thread already running')

    def stop(self):
        self.command = 'stop'
        if self.thr and self.thr.is_alive():
            print('stopping thread')
        else:
            print('thread not running')

    def running(self):
        return True if self.thr and self.thr.is_alive() else False

    def get_proof(self):
        return self.proof

    def loop(self):
        while self.command == 'run':
            time.sleep(0.5)
            print('+', end='')
            self.proof += '+'
            if len(self.proof) > 30:
                self.proof = ""

    def __del__(self):
        print('del instance subthread')
        self.command = 'stop'
        if self.thr and self.thr.is_alive():
            self.thr.join(2)


class app():
    def __init__(self, rootframe):
        self.root = rootframe
        self.gui = ttk.Frame(self.root)
        self.gui.pack(fill=BOTH)
        row = 0
        self.checkvar = IntVar()
        self.checkvar.trace('w', self.threadchange)
        ttk.Label(self.gui, text="Use checkbox to start and stop thread").grid(row=row, column=0, columnspan=2)
        ttk.Checkbutton(self.gui, text='thread', variable=self.checkvar).grid(row=1, column=0)
        self.threadstatus = StringVar()
        self.threadstatus.set('not running')
        row += 1
        ttk.Label(self.gui, textvariable=self.threadstatus).grid(row=row, column=1)
        row += 1
        self.alivestring = StringVar()
        ttk.Entry(self.gui, textvariable=self.alivestring).grid(row=row, column=0, padx=10, sticky="ew",
                                                                      columnspan=3)
        row += 1
        ttk.Separator(self.gui, orient="horizontal").grid(row=row, column=0, padx=10, sticky="ew",
                                                                      columnspan=3)
        row += 1
        ttk.Label(self.gui, text="- Available options to close application: [ctrl]-x,"
                                 " window-control-red, [CMD]-q").grid(row=row, column=0, padx=10, columnspan=3)
        row += 1
        ttk.Label(self.gui, text="1. Try all three without thread running").grid(row=row, column=0,
                                                                                 columnspan=3, sticky='w')

        row += 1
        ttk.Label(self.gui, text="2. Retry all three after first starting the thread").grid(row=row, column=0,
                                                                                            columnspan=3, sticky='w')

        row += 1
        ttk.Label(self.gui, text="3. Experience that only [CMD]-q fails").grid(row=row, column=0,
                                                                               columnspan=3, sticky='w')


        self.subt = subthread()

        self.root.bind('<Control-x>', self.exitapp1)
        self.root.protocol("WM_DELETE_WINDOW", self.exitapp2)

        atexit.register(self.catch_atexit)

        self.root.after(500, self.updategui)

    def threadchange(self, a, b, c):
        """ checkbox change handler """
        try:
            if self.checkvar.get() == 1:
                self.subt.start()
            else:
                self.subt.stop()
        except Exception as ex:
            print('failed to control subt', str(ex))

    def updategui(self):
        """ retriggering timer handler to update status label gui """
        try:
            if self.subt.running():
                self.threadstatus.set("thread is running")
            else:
                self.threadstatus.set("thread not running")
            self.alivestring.set(self.subt.get_proof())
        except:
            pass
        else:
            self.root.after(500, self.updategui)

    def __del__(self):
        print('app del called')

    def exitapp1(self, a):
        print('exitapp1 called')
        self.subt.stop()
        sys.exit(0)

    def exitapp2(self):
        print('exitapp2 called')
        self.subt.stop()
        sys.exit(0)

    def catch_atexit(self):
        print('exitapp called')
        self.subt.stop()
        self.subt = None
        sys.exit(0)


if __name__ == '__main__':
    root = Tk()
    dut = app(rootframe=root)

    root.mainloop()

    print('main exiting')
    sys.exit(0)

python-3.x macos tkinter
3个回答
3
投票

@EddyHoogevorst 是的,它在 Big Sur 上不起作用。
有效的代码是:

root.createcommand("::tk::mac::Quit", action)

注意:需要更改
action
的函数签名,因为
event
在此上下文中不作为参数传递。


1
投票

您可以用 <Command-q> 捕捉

⌘Q
:

...
def action(event):
  print("bind!")

root.bind_all("<Command-q>", action)
...

这在 macOS HightSierra 上对我有用


0
投票

我也遇到了同样的问题。

我是这样解决的

,我将此代码放入我的 init (类)中:

MultiPageApp 类(tk.Tk):
def init(自我):
超级()。init()
self.protocol("WM_DELETE_WINDOW", self.on_ opening)

self.createcommand("tk::mac::Quit" , lambda:self.on_ending())

def on_close(self): if messagebox.askokcancel("退出", "您确定要退出吗?"): 自毁()

© www.soinside.com 2019 - 2024. All rights reserved.