Kivy如何实现App自定义事件向子组件的分发
核心结论
register_event_type 注册的事件仅在执行注册操作的对象实例范围内生效,不会自动向子组件做层级分发。你当前的代码无法触发子组件回调,本质是事件注册、绑定的对象完全不匹配。
现有代码的错误点
on_foo事件只在LayoutDemoApp实例上完成了注册,只有直接绑定在该App实例上的回调,会在App调用self.dispatch("on_foo")时被触发,子组件默认完全感知不到这个事件。MyButton中写的self.bind(on_foo=self.on_foo)本身就会运行报错:MyButton类从未注册过on_foo事件类型,Kivy不允许绑定未注册的自定义事件。
适配websocket场景的全局事件实现方案
要实现任意子组件都能监听全局触发的事件,不需要做复杂的自动向下分发逻辑,直接利用App实例全局可访问的特性,把App作为全局事件总线即可,所有需要监听事件的子组件主动绑定App上的对应事件,稳定性最高,也完全适配websocket消息推送的场景。
修正后的可运行代码:
from kivy.app import App from kivy.uix.boxlayout import BoxLayout from kivy.uix.button import Button class MyButton(Button): def __init__(self, **kwargs): super(MyButton, self).__init__(**kwargs) # 获取全局唯一的App实例,绑定App上注册的自定义事件 app = App.get_running_app() app.bind(on_foo=self.on_foo) def on_foo(self, instance, ws_data=None, *args, **kwargs): print("MyButton收到on_foo事件,websocket传来的内容:", ws_data) class LayoutDemoApp(App): def __init__(self, **kwargs): super().__init__(**kwargs) # 所有全局自定义事件统一在App类中注册 self.register_event_type("on_foo") def build(self): root = BoxLayout(orientation='vertical') r1 = BoxLayout() button12 = MyButton(text='点击模拟websocket消息推送') button12.bind(on_press=self.on_bar) r1.add_widget(button12) root.add_widget(r1) return root def on_bar(self, *args, **kwargs): print("模拟websocket收到消息,开始全局分发事件") # dispatch时可直接传入websocket收到的消息体作为参数 self.dispatch("on_foo", ws_data={"type": "tag_update", "content": "新的标签内容"}) # 自定义事件必须定义同名默认回调,即使留空也不能省略 def on_foo(self, *args, **kwargs): pass if __name__ == '__main__': LayoutDemoApp().run()
使用说明
- 后续接入websocket时,只需要在websocket的消息接收回调函数中,调用
App.get_running_app().dispatch("on_foo", ws_data=收到的消息内容),所有提前绑定过该事件的子组件都会自动触发对应回调,不受组件嵌套层级影响。 - 如果需要新增其他全局事件(比如连接状态变更、系统通知推送等),只需要在App的
__init__方法中追加对应的register_event_type调用,子组件按需绑定自己需要监听的事件即可,不会产生多余的性能开销。 - 不要实现递归遍历组件树主动分发事件的逻辑,组件嵌套较深时性能差,还容易因为组件动态创建、销毁出现重复触发、内存泄漏问题,主动绑定的方案是Kivy生态中通用的全局事件实现方式。
内容的提问来源于stack exchange,提问作者Kjell-Bear
相关产品推荐
相关产品推荐

