You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在按下ESC键后重启Kivy窗口?

Kivy托盘应用重启异常问题解决

问题场景

开发托盘式Kivy应用时,按下Esc键关闭窗口后,再次调用app.run()会触发No event listeners have been created异常;但点击窗口栏关闭按钮后重启则无此问题。环境:Kivy2.3.0、Python3.11、Windows11。

复现代码:

from time import sleep

from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget


class MyApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.my_window = Widget()

    def build(self):
        return self.my_window

    def on_stop(self):
        Window.hide()


if __name__ == '__main__':
    Window.top = 30
    app = MyApp()
    app.run()
    # Restart the App after one second
    sleep(1)
    Window.show()
    app.run()

报错信息:

Connected to pydev debugger (build 223.8836.43)
[INFO   ] WM_MotionEventProvider <kivy.input.providers.wm_touch.WM_MotionEventProvider object at 0x0000016E2A86E790>
[INFO   ] WM_MotionEventProvider <kivy.input.providers.wm_touch.WM_MotionEventProvider object at 0x0000016E2A8816D0>
[ERROR  ] [Base        ] No event listeners have been created
[ERROR  ] [Base        ] Application will leave
python-BaseException

问题原因

  1. Kivy的App实例设计为只能调用一次run()方法,首次run()执行完毕后,内部事件监听器、核心循环资源会被自动清理,再次调用会因缺失必要资源触发异常。
  2. Esc键会触发Kivy的默认停止流程,完整清理App的核心资源;而点击窗口关闭按钮时,Windows平台的Kivy处理逻辑存在差异,未完全销毁资源,导致第二次run()看似可用,但这属于非预期的平台兼容问题,并非正确用法。

解决方案

方案一:每次重启创建新的App实例

放弃复用原App实例,每次重启时新建实例并调用run(),这是最符合Kivy设计规范的做法:

if __name__ == '__main__':
    Window.top = 30
    # 首次运行
    app = MyApp()
    app.run()
    sleep(1)
    # 重启:新建App实例
    Window.show()
    app = MyApp()
    app.run()

方案二:拦截Esc键,避免触发完整App停止流程

如果需要复用窗口和App状态,可拦截Esc键的默认行为,仅隐藏窗口而不触发App的停止流程,同时避免重复调用run(),改用手动维护事件循环:

from time import sleep
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.base import runTouchApp


class MyApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.my_window = Widget()
        # 绑定键盘事件拦截Esc
        self._keyboard = Window.request_keyboard(self._keyboard_closed, self.my_window)
        self._keyboard.bind(on_key_down=self._on_key_down)
        # 拦截窗口关闭按钮事件
        Window.bind(on_request_close=self._on_window_close)

    def _keyboard_closed(self):
        self._keyboard.unbind(on_key_down=self._on_key_down)
        self._keyboard = None

    def _on_key_down(self, keyboard, keycode, text, modifiers):
        if keycode[1] == 'escape':
            Window.hide()
            return True  # 阻止默认的Esc停止行为
        return False

    def _on_window_close(self, window):
        Window.hide()
        return True  # 阻止窗口关闭触发App停止

    def build(self):
        return self.my_window


if __name__ == '__main__':
    Window.top = 30
    app = MyApp()
    # 启动事件循环
    runTouchApp(app.my_window)
    # 后续如需显示窗口,直接调用Window.show()即可,无需重新run()
    # 示例:模拟1秒后显示窗口
    sleep(1)
    Window.show()

方案二通过拦截Esc和窗口关闭事件,让App的事件循环持续运行,仅通过Window.hide()/Window.show()控制窗口显示,更适合托盘应用的场景,避免了重复启动App的问题。

内容的提问来源于stack exchange,提问作者seenorth

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 17:17:02