我正在尝试将实例化的导入的外部类的属性绑定到kivy类,但是它不起作用,我在kivy doc或其他地方找不到我的特殊情况。
[我制作了一个示例kivy gui,左侧的标签绑定到kivy类属性,并且当计数器启动左侧的标签时,标签相应地进行更新,但是右侧的标签绑定到其他类的属性在kivy的“ on_start”方法中实例化,当启动右侧的计数器时,您可以在控制台中看到右侧的实例化类的属性正在更改,但右侧的标签未更改。
这里的代码很简单,“ Start counter”按钮调用是一种方法,该方法依次使用线程调用counter方法以避免冻结gui,counter方法使用while循环递增一个数字,因为数字改变了,所以左边的标签。带有“ external”关键字的方法引用右侧的已导入类计数器,因此与左侧的方法相同,右侧的“ start counter”按钮调用“ start_external_counter”方法并在实例化时启动计数器导入的类,导入的类中的number属性增加,但是kivy中的绑定不适用于导入的类属性,这是我希望解决的我的问题,如何从外部类或导入的模块中绑定属性到猕猴桃的环境。
注意:我可以通过使用带有轮询循环的时钟来更新右侧的标签,该轮询循环在每个时间间隔调用外部类属性,但是我认为这不是正确的方法。
谢谢您的帮助。
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import ObjectProperty, NumericProperty
import time
import threading
kv = '''
BoxLayout:
padding: 40
spacing: 50
# -- INTERNAL CLASS COUNTER --#
BoxLayout:
orientation: 'vertical'
spacing: 50
Label:
text: 'Kivy class attribute binding'
font_size: 30
size: self.texture_size
size_hint_y: .2
Label:
id: counter_label
text: '0'
font_size: 200
size: self.texture_size
BoxLayout:
size_hint_y: None
height: 80
Button:
text: 'Start Counter'
font_size: 30
on_release: app.get_running_app().start_counter()
Button:
text: 'Stop Counter'
font_size: 30
on_release: app.get_running_app().stop_counter()
# -- EXTERNAL CLASS COUNTER -- #
BoxLayout:
orientation: 'vertical'
spacing: 50
Label:
text: 'External class attribute binding'
font_size: 30
size: self.texture_size
size_hint_y: .2
Label:
id: external_counter_label
text: '0'
font_size: 200
size: self.texture_size
BoxLayout:
size_hint_y: None
height: 80
Button:
text: 'Start Counter'
font_size: 30
on_release: app.get_running_app().start_external_counter()
Button:
text: 'Stop Counter'
font_size: 30
on_release: app.get_running_app().stop_external_counter()
'''
class MyClass:
number = 0
stop = False
def count(self):
while not self.stop:
self.number += 1
print 'External counter: %s' % self.number
time.sleep(1)
self.stop = False
class main(App):
number = NumericProperty(0)
external_number = NumericProperty(0)
external_counter = ObjectProperty()
stop = False
def build(self, *args):
layout = Builder.load_string(kv)
return layout
def on_start(self):
root = self.root_window
self.layout = root.children[0]
self.counter_label = self.layout.ids['counter_label']
self.bind(number=self.update_label)
## -- Trying to bind a property
## -- from other non kivy class
self.external_counter_label = self.layout.ids['external_counter_label']
self.external_counter = MyClass()
self.external_number = self.external_counter.number
self.bind(external_number=self.update_external_label)
def update_label(self, *args):
self.counter_label.text = str(self.number)
def start_counter(self):
''' using a thread to start counter
without freezing gui
'''
t = threading.Thread(target=self.count)
t.setDaemon(True)
t.start()
def count(self):
while not self.stop:
self.number += 1
time.sleep(1)
self.stop = False
def stop_counter(self):
self.stop = True
## --- CALLING THE EXTERNAL CLASS METHODS -- ##
def update_external_label(self):
self.external_counter_label.text = self.external_number
def start_external_counter(self):
''' using a thread to start counter
without freezing gui
'''
t = threading.Thread(target=self.external_count)
t.setDaemon(True)
t.start()
def external_count(self):
self.external_counter.count()
def stop_external_counter(self):
self.external_counter.stop = True
if __name__ == '__main__':
main().run()
我找到了解决方案,诀窍是使用kivy“ EventDispatcher”。
包含要在kivy中绑定到的属性的模块或类需要具有kivy绑定功能,因此要从中绑定其属性的导入模块或外部类必须继承kivy的“ EventDispatcher”类,但是由于我们不希望以任何方式更改外部类或模块,因此最好的解决方案是使用从外部类和kivy“ EventDispatcher”类继承的自定义类覆盖外部类,因此我们可以使用kivy中的新自定义类,并绑定它的任何属性。
这里我做了一个小的工作例子。
注意:如果有人知道更好的方法,请发表评论并举一个简短的示例,谢谢。
from kivy.app import App
from kivy.lang import Builder
from kivy.properties import NumericProperty, ObjectProperty
from kivy.event import EventDispatcher
import time
import threading
class Count:
""" This is the external class we want to bind
attributes from, this could be an imported
module also.
"""
def counter(self):
while True:
self.increase()
time.sleep(1)
def increase(self):
self.number += 1
class MyClass(Count, EventDispatcher):
""" Custom override class.
This class contains the kivy
bindings functionality
"""
number = NumericProperty(0) # <-- turns number attribute into a kivy object, so it can be binded in kivy
kv = '''
AnchorLayout:
BoxLayout:
spacing: 40
size_hint: None, None
size: 300, 200
orientation: 'vertical'
Label:
id: counter_label
text: '0'
font_size: 100
BoxLayout:
Button:
text: 'Add 1'
on_release: app.get_running_app().change()
Button:
text: 'Start Counter'
on_release: app.get_running_app().start_counter()
'''
class main(App):
''' Updating a label through binding an attribute
from an external class "MyClass".
'''
def build(self):
return Builder.load_string(kv)
def on_start(self):
self.counter_label = self.root.ids['counter_label']
self.my_class = MyClass() # <-- here we instanciate the custom class
self.my_class.bind(number=self.update_label) # <-- here we bind the number attribute from the custom class
def update_label(self, *args):
""" when the "number" attribute from the external
class changes this method is called and updates
the label accordingly.
"""
self.counter_label.text = str(self.my_class.number)
def change(self):
""" Use a thread here to avoid locking the
gui's main loop
"""
t = threading.Thread(target=self.increase)
t.setDaemon(True)
t.start()
def increase(self):
""" Calls the increase method in the custom
class and increases the "number" attribute
"""
self.my_class.increase()
def start_counter(self):
t = threading.Thread(target=self.count)
t.setDaemon(True)
t.start()
def count(self):
self.my_class.counter()
if __name__ == '__main__':
main().run()