Kivy 2.1.0使用自定义类型如何保留完整自动绑定功能?
问题
如何在Kivy 2.1.0中使用自定义类型,同时保留全部自动绑定能力?核心需求如下:
mything.foo发生变更时触发对应的属性事件- 维持KV语言对
mything.foo的绑定关系,无论修改的是mything的内部属性,还是整体替换mything对象,绑定都能正常生效
下方代码包含两种常见的实现变体,注释标注了不同写法的输出差异,测试时注释掉其中一种写法即可验证效果:
Python文件代码
import kivy kivy.require('2.1.0') from kivy.app import App from kivy.event import EventDispatcher from kivy.properties import ObjectProperty, StringProperty from kivy.uix.boxlayout import BoxLayout from kivy.logger import Logger class MyType(EventDispatcher): foo = StringProperty("New") class Consumer(BoxLayout): mything = ObjectProperty(MyType()) def on_mything(self, *args, **kwargs): Logger.info(f"Consumer: Changed {self.mything=}") def click(self, *args, **kwargs): # 写法1:直接修改内部属性 # on_mything executes: False # button text updates: True self.mything.foo = "Clicked" # 写法2:整体替换对象 # on_mything executes: True # button text updates: False # self.mything = MyType(foo="Clicked") class AliasApp(App): def build(self): return Consumer() if __name__ == "__main__": AliasApp().run()
KV文件代码
#:kivy 2.1.0 <Consumer>: foo: "initialized in kv" orientation: 'vertical' Button: text: self.parent.mything.foo on_touch_down: self.parent.click()
上述两种写法均为官方文档记载的预期行为,但都会导致部分功能失效,存在可兼顾所有功能的实现方案。
理想实现方案
希望能在MyType中添加类似如下的定义:
Property.custom_property(observe=['foo', 'bar'])
之后在Consumer中创建属性时可直接按如下方式编写,即可获得完整绑定能力:
mything = CustomProperty()
已考虑的实现方向
- AliasProperty:查阅过相关文档与示例,现有示例均演示如何在Consumer内部创建供自身使用的属性,无法满足消费外部属性的实现需求。
- 手动绑定:可以在MyType中实现更新事件处理器,手动订阅更新事件,但该方案仅在修改mything的内部属性、不整体替换mything对象时生效。
- 子类化Property:尝试查找kivy.properties.Property子类化的相关资料但未获取有效信息,该类似乎属于框架内部实现,不面向普通开发者开放使用。
可行实现方案
通过子类化ObjectProperty即可实现需求,只需要重写属性赋值的核心逻辑,自动处理新旧对象的子属性绑定迁移即可,实现代码如下:
from kivy.properties import ObjectProperty from kivy.event import EventDispatcher class TrackedObjectProperty(ObjectProperty): def __init__(self, defaultvalue=None, observed_props=None, **kwargs): self.observed_props = observed_props or [] self._bound_callbacks = {} super().__init__(defaultvalue, **kwargs) def set(self, instance, value, *args): # 解绑旧对象上注册的子属性监听 old_val = self.get(instance) if old_val is not None and isinstance(old_val, EventDispatcher): for prop in self.observed_props: bind_key = f"{self.name}_{prop}" if bind_key in self._bound_callbacks: old_val.unbind(**{prop: self._bound_callbacks.pop(bind_key)}) # 为新对象绑定子属性监听 if value is not None and isinstance(value, EventDispatcher): for prop in self.observed_props: def _notify_update(*_, target_prop=prop): # 子属性变化时主动派发属性变更事件,通知所有绑定方刷新 instance.dispatch(self.name, self.get(instance)) value.fbind(prop, _notify_update) self._bound_callbacks[f"{self.name}_{prop}"] = _notify_update super().set(instance, value, *args)
使用方式和原生Property完全一致,只需要在声明属性时传入需要观察的子属性列表即可:
class Consumer(BoxLayout): mything = TrackedObjectProperty(MyType(), observed_props=['foo'])
最终实现效果:
- 直接修改
self.mything.foo时,会自动触发on_mything事件,KV侧绑定正常更新 - 整体替换
self.mything对象时,会自动完成旧绑定解绑、新对象绑定的迁移,KV侧文本正常刷新,不会出现绑定丢失 - 业务层不需要编写额外的手动绑定逻辑,和原生Kivy Property的使用体验完全一致
内容的提问来源于stack exchange,提问作者Ardemus
相关产品推荐
相关产品推荐

