如何在__init__()中从"self"访问Kivy属性

0 投票
1 回答
53 浏览
提问于 2025-04-14 15:21

我怎样才能在一个小部件的 __init__() 函数中访问 Kivy 属性呢?

我写了一个自定义的 Kivy 小部件。我需要在 Kivy 的一个 网格 上显示几千个这个小部件的实例。这样做会导致系统崩溃,所以我把网格包裹在一个 回收视图 中。这样做可以立即渲染,而且没有任何延迟,太棒了!

之前,我在自定义小部件的 __init__() 函数中传递了一些位置参数,用来设置一些实例字段,这些字段决定了小部件中显示的内容(以及显示的方式)。不幸的是,回收视图让我不得不把位置参数换成 Kivy 属性。而我似乎无法在对象的 __init__() 函数中访问这些属性的值。

为了简单起见,我们来看一个最小的例子,取自 Kivy 邮件列表上的 这个问题

#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty

kv = '''
<TwoButtons>:
# This class is used as the viewclass in the RecycleView
# The means this widget will be instanced to view one element of data from the data list.
# The RecycleView data list is a list of dictionaries.  The keys in the dictionary specify the 
# attributes of the widget.
    Button:
        text: root.left_text
        on_release: print(f'Button {self.text} pressed') 
    Button:
        text: root.right_text
        on_release: print(f'Button {self.text} pressed') 

BoxLayout:
    orientation: 'vertical'
    Button:
        size_hint_y: None
        height: 48
        text: 'Add widget to RV list'
        on_release: rv.add()

    RV:                          # A Reycleview
        id: rv
        viewclass: 'TwoButtons'  # The view class is TwoButtons, defined above.
        scroll_type: ['bars', 'content']
        bar_width: 10
        RecycleBoxLayout:        
            # This layout is used to hold the Recycle widgets
            default_size: None, dp(48)   # This sets the height of the BoxLayout that holds a TwoButtons instance.


            default_size_hint: 1, None
            size_hint_y: None

            height: self.minimum_height   # To scroll you need to set the layout height.
            orientation: 'vertical'
'''


class TwoButtons(BoxLayout):  # The viewclass definitions, and property definitions.
    left_text = StringProperty()
    right_text = StringProperty()

    print( "TwoButtons top" )

    def __init__(self, **kwargs):
        print( "self.left_text:|" +str(self.left_text)+ "|" )
        super().__init__(**kwargs)
        print( "self.left_text:|" +str(self.left_text)+ "|" )

class RV(RecycleView):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.data = [{'left_text': f'Left {i}', 'right_text': f'Right {i}'} for i in range(2)]

    
    def add(self):
        l = len(self.data)
        self.data.extend(
         [{'left_text': f'Added Left {i}', 'right_text': f'Added Right {i}'} for i in range(l, l + 1)]
        )

class RVTwoApp(App):

    def build(self):
        return Builder.load_string(kv)

RVTwoApp().run()

在上面的代码片段中,我们使用 RecycleView 实例化的自定义小部件叫做 TwoButtons

显示上述代码执行的 CLI 终端和一个包含按钮小部件数组的 GUI 窗口的截图
GUI 中的按钮显示了文本 Left 0Left 1,正如预期的那样——但尝试在 __init__() 中访问它们的值时却得到空字符串 (self.left_text:||)

TwoButtons 类有两个 Kivy 属性:

  1. 字符串属性 left_text
  2. 字符串属性 right_text

如果你运行这个应用,你会清楚地看到 RecycleView 能够将数据传递到 TwoButtons 实例的属性中,因为按钮上的文本如预期那样显示。

问题是,在 __init__() 中,left_text 属性的值是空字符串

考虑以下程序的执行结果:

user@buskill:~/tmp/rv$ python3 rv.py 
[INFO   ] [Logger      ] Record log in /home/user/.kivy/logs/kivy_24-03-17_23.txt
[INFO   ] [Kivy        ] v2.1.0
[INFO   ] [Kivy        ] Installed at "/home/user/.local/lib/python3.9/site-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110]
...
[INFO   ] [Text        ] Provider: sdl2
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
self.left_text:||
self.left_text:||
self.left_text:||
self.left_text:||

正如你所看到的,TwoButtons__init__() 函数中的 print() 语句返回了 left_text 的空字符串(尽管 GUI 中按钮的实际 left_text 显示为 Left 0Left 1,如预期)。

而且,如果你点击 添加小部件到 RV 列表 按钮,添加一行按钮,文本为 Added Left 2Added Right 2,那么以下新行会再次被 print()

self.left_text:||
self.left_text:||

我怎样才能在对象的 __init__() 函数中实际访问给定对象的属性值呢?

1 个回答

0

简要说明

你可以通过 bind() 函数或者内置的 on_<属性名>() 函数 来访问 Kivy 属性。

        def __init__(self, **kwargs):
                super().__init__(**kwargs)

        def on_left_text( self, instance, value ):
                print( "self.left_text:|" +str(self.left_text)+ "|" )

on_<属性名>()

我不太清楚 Kivy 是怎么创建它的控件属性对象的,但你说得对,__init__() 里面是无法直接访问这些值的。

不过,如果你能等几秒钟再进行设置,可以在每个属性设置好之后,使用 on_<属性名>() 函数来做,这个函数会在 <属性名> 的值变化时被调用。

根据文档:

使用 ‘on_<属性名>’ 进行观察

如果你自己定义了类,可以使用 ‘on_’ 回调:

class MyClass(EventDispatcher):
    a = NumericProperty(1)

    def on_a(self, instance, value):
        print('My property a changed to', value)

代码

我对原作者的程序进行了更新,修改如下:

#!/usr/bin/env python3
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty

kv = '''
<TwoButtons>:
# This class is used as the viewclass in the RecycleView
# The means this widget will be instanced to view one element of data from the data list.
# The RecycleView data list is a list of dictionaries.  The keys in the dictionary specify the 
# attributes of the widget.
    Button:
        text: root.left_text
        on_release: print(f'Button {self.text} pressed') 
    Button:
        text: root.right_text
        on_release: print(f'Button {self.text} pressed') 

BoxLayout:
    orientation: 'vertical'
    Button:
        size_hint_y: None
        height: 48
        text: 'Add widget to RV list'
        on_release: rv.add()

    RV:                       # A Reycleview
        id: rv
        viewclass: 'TwoButtons'  # The view class is TwoButtons, defined above.
        scroll_type: ['bars', 'content']
        bar_width: 10
        RecycleBoxLayout:       
            # This layout is used to hold the Recycle widgets
            default_size: None, dp(48)   # This sets the height of the BoxLayout that holds a TwoButtons instance.


            default_size_hint: 1, None
            size_hint_y: None

            height: self.minimum_height   # To scroll you need to set the layout height.
            orientation: 'vertical'
'''


class TwoButtons(BoxLayout):  # The viewclass definitions, and property definitions.
    left_text = StringProperty()
    right_text = StringProperty()

    print( "TwoButtons top" )

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def on_left_text( self, instance, value ):
        print( "self.left_text:|" +str(self.left_text)+ "|" )

class RV(RecycleView):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.data = [{'left_text': f'Left {i}', 'right_text': f'Right {i}'} for i in range(2)]

    
    def add(self):
        l = len(self.data)
        self.data.extend(
         [{'left_text': f'Added Left {i}', 'right_text': f'Added Right {i}'} for i in range(l, l + 1)]
        )

class RVTwoApp(App):

    def build(self):
        return Builder.load_string(kv)

RVTwoApp().run()

执行示例

上述程序的一个执行示例正确输出了 left_text 的值。

user@buskill:~/tmp/rv$ python3 rv2.py 
[INFO   ] [Logger      ] Record log in /home/user/.kivy/logs/kivy_24-03-17_27.txt
[INFO   ] [Kivy        ] v2.1.0
[INFO   ] [Kivy        ] Installed at "/home/user/.local/lib/python3.9/site-packages/kivy/__init__.py"
[INFO   ] [Python      ] v3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110]
[INFO   ] [Python      ] Interpreter at "/usr/bin/python3"
...
[INFO   ] [Base        ] Start application main loop
[INFO   ] [GL          ] NPOT texture support is available
self.left_text:|Left 0|
self.left_text:|Left 1|
self.left_text:|Left 0|
self.left_text:|Left 1|

撰写回答