Kivy语言能够访问继承的布局和小部件吗?

10
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout

Builder.load_string('''
<SimpleBar>:
    canvas.before:
        Color:
            rgba: 0, 0.5, 0.5, 1
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        id: my_layout
        Label:
            text: "hi"

<NewBar>:
    Label:
        text: "2"
''')

class SimpleBar(BoxLayout):
    def log(self, value):
        print(value)

class NewBar(SimpleBar):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        print(dir(self))

class GeneralApp(App):
    def build(self):
        return NewBar()

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

以下是我的基本运行小部件。
我希望NewBar的“2”标签位于SimpleBar的“hi”标签之前,如下所示。
<NewBar>:
     BoxLayout:
         id: my_layout
         Label:
             text: "2"
         Label:
             text: "hi"

我知道“-”可以取消样式。不过,<-NewBar>会移除我所有的样式。

在kivy语言中有没有其他方法可以实现取消样式?


做了一点小修改,现在它甚至支持偶数索引 ^^ - Peter Badida
3个回答

6

一个有趣的事情:你不需要在kv lang本身中指定所有使用的类 - 你也可以在以后的代码中使用Factory.register方法添加它们。以下是一个示例:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory

from functools import partial

Builder.load_string('''

<MyWidget>:
    Foo
    Bar
''')

class MyWidget(BoxLayout):
    pass

class MyApp(App):
    def build(self):
        Factory.register('Foo', cls=partial(Label, text='foo'))
        Factory.register('Bar', cls=partial(Label, text='bar'))
        return MyWidget()

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

让我们使用它来创建一个模板基础小部件,稍后再用各种内容填充。我们使用一个占位符,然后用另一个小部件替换它:

<BaseWidget>:
    orientation: 'vertical'
    Label:
        size_hint: None, 0.1
        text: 'title'
    Placeholder

在Python代码中,我们在基础模板类的__init__方法中注册了一个占位符类。
class BaseWidget(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(BaseWidget, self).__init__(**args)

现在让我们定义一个内容类。
<TwoButtonWidget>:
    Button:
        text: 'button 1'
    Button:
        text: 'button 2'

最后创建一个自定义类,使用我们的基类作为模板,并用内容类替换其占位符。这个类没有自己的kivy规则(这些规则被移动到内容类中),因此当我们从基本模板继承时,不会插入额外的小部件。

# content class
class TwoButtonWidget(BoxLayout):
    pass

# Base class subclass
class CustomizedWidget(BaseWidget):
    placeholder = TwoButtonWidget # set contetnt class

一个完整的例子:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.factory import Factory

Builder.load_string('''
<BaseWidget>:
    orientation: 'vertical'
    widget_title: widget_title
    placeholder: placeholder
    Label:
        size_hint: None, 0.1
        id: widget_title
    Placeholder
        id: placeholder

<TwoButtonWidget>:
    button1: button1
    Button:
        text: 'button 1'
        id: button1
    Button:
        text: 'button 2'

<ThreeButtonWidget>:
    orientation: 'vertical'
    Button:
        text: 'button a'
    Button:
        text: 'button b'
    Button:
        text: 'button c'
''')

class BaseWidget(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(BaseWidget, self).__init__(**args)

class TwoButtonWidget(BoxLayout):
    pass

class ThreeButtonWidget(BoxLayout):
    pass

class CustomizedWidget1(BaseWidget):
    placeholder = TwoButtonWidget

class CustomizedWidget2(BaseWidget):
    placeholder = ThreeButtonWidget

class MyApp(App):
    def build(self):
        layout = BoxLayout()
        c1 = CustomizedWidget1()
        # we can access base widget...
        c1.widget_title.text = 'First'
        # we can access placeholder
        c1.placeholder.button1.text = 'This was 1 before'

        c2 = CustomizedWidget2()
        c2.widget_title.text = 'Second'

        layout.add_widget(c1)
        layout.add_widget(c2)
        return layout

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

你可以轻松地扩展它,例如,拥有多个占位符。
将其应用于您的情况:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.lang import Builder
from kivy.factory import Factory

from functools import partial

Builder.load_string('''

<SimpleBar>:
    canvas.before:
        Color:
            rgba: 0, 0.5, 0.5, 1
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        Placeholder
        Label:
            text: "hi"

<NewBarContent>:
    Label:
        text: "2"
''')

class SimpleBar(BoxLayout):
    def __init__(self, **args):
        # unregister if already registered...
        Factory.unregister('Placeholder')
        Factory.register('Placeholder', cls=self.placeholder)
        super(SimpleBar, self).__init__(**args)

class NewBarContent(BoxLayout):
    pass

class NewBar(SimpleBar):
    placeholder = NewBarContent

class MyApp(App):
    def build(self):
        return NewBar()

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

令人惊讶的是,这甚至是可行的,而且实际上没有任何文档记录。说真的,这种技术将Kivy语言推向了一个完整的面向小部件的面向对象语言,包括用户定义的小部件继承规则。话虽如此,您最后的示例有些问题:SimpleBar小部件无法再被实例化,因为它未定义placeholder属性。理想情况下,您应该将placeholder默认为一个不可见的空小部件类。但是,Kivy是否提供了一个不可见的空小部件类呢? - Cecil Curry
@CecilCurry kivy.uix.widget.Widget恰恰就是这个 - 一个默认的空白基础部件。如果你在SimpleBar中添加placeholder = Widget,你就可以直接使用它了。 - Nykakin

3
如果您想创建一个复合小部件,能够接受新的子元素并将它们添加到特定的“容器”小部件中,那么需要编写一些 Python 代码。
基本思路是重写add_widget方法,以便在小部件的基本结构存在时,使用新方法添加新小部件。
假设您有这个 NestingWidget
class NestingWidget(BoxLayout):
     title = StringProperty()

     def activate(self):
         # do something
         pass

使用这个规则
<NestingWidget>:
    Label:
        text: root.title

    BoxLayout:

    Button:
        on_press: root.activate()

你希望像这样使用它:

FloatLayout:
    NestingWidget:
        title: 'first item'
        Image:
            source: '../examples/demo/pictures/images/Ill1.jpg'

这不会立即起作用,因为Image将被添加为NestingWidget的直接子元素,所以它将在Button下面。

然而,您可能已经注意到,在kivy中,一些小部件可以接受新的嵌套小部件,同时它们自己已经很复杂。

要做到这一点的诀窍是 - 如前所述 - 覆盖add_widget

首先,让我们向容器添加一个ID。

<NestingWidget>:
    Label:
        text: root.title

    BoxLayout:
        id: container

    Button:
        on_press: root.activate()

那么,让我们在add_widget中使用它。
class NestingWidget(BoxLayout):
    …
    def add_widget(self, *args, **kwargs):
        if 'container' in self.ids:
            return self.ids.container.add_widget(*args, **kwargs)
        else:
            return super(NestingWidget, self).add_widget(*args, **kwargs)

1
使用简单的kv no,因为如果您在小部件中放置任何内容(例如Label: ...),它将调用<widget>.add_widget()方法,并且当没有额外的参数调用这样的方法时,它默认将小部件放置在先前放置的任何东西之后。因此,您可以通过搜索kivy/lang/parser.py文件并添加这样的功能(PR欢迎),或者在python中执行它,在hm... __init__或其他您想要添加小部件的位置(也许是在某些事件之后)。
要在python中执行此操作,您可以根据文档调用<widget(或self)>.add_widget(<child>, index=<where>)。例如:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty

Builder.load_string('''
#:import Factory kivy.factory.Factory
<Ninja@Label>:

<SimpleBar>:
    BoxLayout:
        id: my_layout
        Label:
            text: "hi"

<ChildWithBenefits>:
    placebefore:
        [(Factory.Label(text='I am the first!'), 0),
        (Factory.Ninja(text='No, I am!'), 2)]
''')

class SimpleBar(BoxLayout):
    def log(self, value):
        print(value)

class ChildWithBenefits(SimpleBar):
    placebefore = ListProperty([])
    def __init__(self, *args, **kwargs):
        super(ChildWithBenefits, self).__init__(*args, **kwargs)
        for child, index in self.placebefore:
            print child.text
            print type(child)
            self.add_widget(child, index=index)
        self.log('Hello!')

class GeneralApp(App):
    def build(self):
        return ChildWithBenefits()

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接