如何在按下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
问题原因
- Kivy的
App实例设计为只能调用一次run()方法,首次run()执行完毕后,内部事件监听器、核心循环资源会被自动清理,再次调用会因缺失必要资源触发异常。 - 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
相关产品推荐
相关产品推荐

