【问题标题】:Kivy: How to initialize the viewclass of the RecycleView dynamically?Kivy:如何动态初始化RecycleView的viewclass?
【发布时间】:2018-04-20 06:16:47
【问题描述】:

最近我一直在努力在 kivy 中使用 RecycleView。我做了很多测试,有点困惑。

我的目标 我想创建一个回收视图,其中每个Row 包含很少的Label(即列),但列数是动态设置的(当然所有行都相同)。通常,我有一个数据库,我想显示表,但我想动态更改我显示的列。

有类似的问题,但它们都处理固定数量的值来设置。

什么有效

  • 如果列数固定,则可以使用属性链接值
  • 我可以使用add_widget 动态添加小部件

什么不起作用

  • 如果动态添加单元格,我无法将 kv 小部件中的 value 内容链接到 python 变量
  • 如果 Label 小部件的数量是固定的,我可以动态更新内容(如示例),但不能直接初始化。

这是我尝试过的所有不同可能性的示例:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ObjectProperty
from kivy.uix.label import Label
from kivy.uix.recycleview import RecycleView

kv = """
<Row>:
    id: row
    canvas.before:
        Color:
            rgba: 0.5, 0.5, 0.5, 1
        Rectangle:
            size: self.size
            pos: self.pos
    value1: ''
    value2: ''
    value3: ''
    value4: ''
    value5: ''
    Label: #Label 1
        text: root.value1


<Test>:
    canvas:
        Color:
            rgba: 0.3, 0.3, 0.3, 1
        Rectangle:
            size: self.size
            pos: self.pos
    orientation: 'vertical'
    GridLayout:
        cols: 3
        rows: 2
        size_hint_y: None
        height: dp(108)
        padding: dp(8)
        spacing: dp(16)
        Button:
            text: 'Update list'
            on_press: root.update()
    RecycleView:
        id: rvlist
        scroll_type: ['bars', 'content']
        scroll_wheel_distance: dp(114)
        bar_width: dp(10)
        viewclass: 'Row'
        RecycleBoxLayout:
            default_size: None, dp(56)
            default_size_hint: 1, None
            size_hint_y: None
            height: self.minimum_height
            orientation: 'vertical'
            spacing: dp(2)
"""

Builder.load_string(kv)

class Row(BoxLayout):
    constant_value = 'constant content from class data'
    value1 = StringProperty('default 1')
    value2 = StringProperty('default 2')
    value3 = StringProperty('default 3')
    value4 = StringProperty('default 4')
    value5 = StringProperty('default 5')
#  value5,....

    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.add_widget(Label(text = 'constant content')) # Label 2
        self.add_widget(Label(text = self.constant_value)) #Label 3
        self.add_widget(Label(text = self.value4)) # Label 4
        self.add_widget(Label(text = self.value5)) # Label 5
        for x in kwargs:
            self.add_widget(Label(text = 'content from **kwargs'))# Label 6


class Test(BoxLayout):

    def __init__(self,**kwargs):
        super().__init__(**kwargs)
        self.ids.rvlist.data = [{'value1': 'init content from parent class',
        'value4': 'init content from parent class'} for x in range(15)]

    def update(self):
        self.ids.rvlist.data = [{'value1': 'updated content from parent class dynamic widget',
        'value4': 'updated content from parent class dynamic widget',
        'value5': 'updated content from parent class static widget'} for x in range(15)]


class TestApp(App):
    def build(self):
        return Test()


if __name__ == '__main__':
    TestApp().run()

行为如下:

  • Label1:与文档中的定义相同。这很好用,但我需要在 Row 类中定义与列的最大可能值一样多的值,并且只使用其中的几个。它看起来有点骇人听闻...
  • Label2:按预期工作。这是一个恒定的内容。但这不是我需要的(我想用动态生成的内容填充这些行)
  • Label3:Label2 的变体,不是我需要的。但有效。
  • Label4:这里的标签是在初始化时设置的。 初始化和更新不起作用,可能是因为尚未创建小部件,因此 self.value4 尚不存在。有解决方法吗?
  • Label5:标签在更新时设置。 初始化和更新都不起作用。与 4 类似。
  • Label6甚至没有创建。我原以为data 将是传递给类进行初始化的参数之一,这样我就可以为每个数据创建一个标签。我猜这将是最理想的情况。

它提出了以下问题: - 如何动态创建一个 kv 属性并将其链接到另一个属性? (类似于self.add_widget(Label(value5 = parent.value5,text = self.value5))。这将允许在初始化后更新文本(就像我的 label1 不知何故) - 初始化期间如何访问data? - 如何使用self.value来初始化Label?

我希望它足够清楚......我自己也很困惑。

【问题讨论】:

    标签: python kivy kivy-language


    【解决方案1】:

    经过一番摸索,我终于明白了如何实现我想要的,这并不是那么简单。

    在我最初的帖子中有两件事是错误的: - 如前所述,当我分配属性时,小部件尚未初始化。诀窍是使用Clock.schedule_once 将初始化延迟到下一帧。我在另一个问题中看到了这个技巧(对不起,我现在找不到它了)。 - 由此产生的一个问题是小部件已创建但无法更新/访问。为此,我创建了一个回调方法,每次更新视图类中的 data 时都会更新标签。

    代码如下:

    from kivy.app import App
    from kivy.lang import Builder
    from kivy.uix.boxlayout import BoxLayout
    from kivy.properties import ObjectProperty
    from kivy.uix.label import Label
    from kivy.uix.recycleview import RecycleView
    from kivy.clock import Clock
    
    kv = """
    <Row>:
        id: row
        canvas.before:
            Color:
                rgba: 0.5, 0.5, 0.5, 1
            Rectangle:
                size: self.size
                pos: self.pos
    
    
    <Test>:
        canvas:
            Color:
                rgba: 0.3, 0.3, 0.3, 1
            Rectangle:
                size: self.size
                pos: self.pos
        orientation: 'vertical'
        GridLayout:
            cols: 3
            rows: 2
            size_hint_y: None
            height: dp(108)
            padding: dp(8)
            spacing: dp(16)
            Button:
                text: 'Update list'
                on_press: root.update()
        RecycleView:
            id: rvlist
            scroll_type: ['bars', 'content']
            scroll_wheel_distance: dp(114)
            bar_width: dp(10)
            viewclass: 'Row'
            RecycleBoxLayout:
                default_size: None, dp(56)
                default_size_hint: 1, None
                size_hint_y: None
                height: self.minimum_height
                orientation: 'vertical'
                spacing: dp(2)
    """
    
    Builder.load_string(kv)
    
    class Row(BoxLayout):
        row_content = ObjectProperty()
    
    
        def __init__(self,**kwargs):
            super().__init__(**kwargs)
            Clock.schedule_once(self.finish_init,0)
    # it delays the end of the initialization to the next frame, once the widget are already created
    # and the properties properly initialized
    
        def finish_init(self, dt):
            for elt in self.row_content:
                self.add_widget(Label(text = elt))
                # now, this works properly as the widget is already defined
            self.bind(row_content = self.update_row)
    
    
        def update_row(self, *args):
            # right now the update is rough, I delete all the widget and re-add them. Something more subtle
            # like only replacing the label which have changed
            print(args)
            # because of the binding, the value which have changed are passed as a positional argument.
            # I use it to set the new value of the labels.
            self.clear_widgets()
            for elt in args[1]:
                self.add_widget(Label(text = elt))
    
    
    class Test(BoxLayout):
    
        def __init__(self,**kwargs):
            super().__init__(**kwargs)
            self.ids.rvlist.data = [{'row_content': [str(i) for i in range(5)]} for x in range(15)]
    
        def update(self):
            self.ids.rvlist.data[0]['row_content'] =  [str(10*i) for i in range(5)]
            self.ids.rvlist.refresh_from_data()
    
    class TestApp(App):
        def build(self):
            return Test()
    
    if __name__ == '__main__':
        TestApp().run()
    

    使用此代码,我可以创建一个包含多列的表,并使用可用单元格的数量初始化每一行。如果列数发生变化,它会自动改变单元格数(可能需要修改一点更新功能)。

    一个限制:小部件是动态创建的,因此不能直接通过 id 调用它们。如果您需要在每行中更高级的小部件,您可能需要创建一些函数来管理自己已经创建的小部件(通过存储小部件对应的self.children 的位置)。

    如果您发现更好的处理方式,请告诉我。我仍然发现 Clock.schedule_once(self.finish_init,0) 看起来像一个 hack,我觉得很奇怪,在 kivy 框架中没有更直接的实现。

    【讨论】:

      【解决方案2】:

      不确定我是否完全理解您的目标,尤其是您的意思

      “动态改变列数”、“动态设置列数”

      如果只有一小组预定义的列数选项,而您只是想动态地从这些选项中选择一个,那么很简单:为每个选项定义一个类,然后初始化RecycleView后,设置RecycleViewviewclass属性值。

      但是,如果列数有无限(太多)的可能性,并且您想要那种级别的动态灵活性,那么RecycleView 不是最好的方法,因为viewclass 是一个预定义的类,而不是一个可变的东西。

      【讨论】:

        猜你喜欢
        • 2016-01-04
        • 2017-08-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-10-07
        • 1970-01-01
        相关资源
        最近更新 更多