【问题标题】:Binding Kivy ObjectProperty to a child widget doesn't seem to work outside of root widget将 Kivy ObjectProperty 绑定到子小部件似乎在根小部件之外不起作用
【发布时间】:2018-02-24 14:17:57
【问题描述】:

尝试遵循本指南:https://kivy.org/docs/guide/lang.html#accessing-widgets-defined-inside-kv-lang-in-your-python-code

我正在尝试使用 id 定义访问小部件。这在根小部件内部运行良好,但在它之外似乎不起作用。 例如,这是代表我的问题的最低限度代码:

GUI.kv 文件:

<PlotBox@BoxLayout>:
graph2:graph2_id
BoxLayout:
    id:graph2_id

<RootWidget@BoxLayout>:
    graph:graph_id
    BoxLayout:
        id:graph_id
    PlotBox:

python 文件:

#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty

class PlotBox(BoxLayout):
    graph2 = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(PlotBox,self).__init__(**kwargs)
        self.graph2.add_widget(Button(text="This doesn't work"))

class RootWidget(BoxLayout):
    graph = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(RootWidget,self).__init__(**kwargs)
        self.graph.add_widget(Button(text='This works'))

class GUIApp(App):
    def build(self):
        self.root = RootWidget()
        return self.root

if __name__ == "__main__":
    GUIApp().run()

我得到错误:

AttributeError: 'NoneType' object has no attribute 'add_widget'

在 RootWidget 上,即使我不使用 graph = ObjectProperty(None),它也可以工作。 在我的另一个小部件上,好像没有创建 id。

【问题讨论】:

  • 这里有一个问题,您是否试图影响 PlotBox 的 CLASS 以便所有未来的 PlotBox 都将添加一个 Button 或只是根小部件上的 PlotBox 的一个实例?
  • @AlexAndDraw 只是根小部件上 PlotBox 的一个实例。我最初有另一个带有另一个小部件的 PlotBox 实例,但现在没有了。我添加的小部件实际上是一个“FigureCanvasKivyAgg”(来自 matplotlib garden 模块),我认为不能通过 kv 添加,但我在示例中使用了 Button,因为它更清晰,最终结果相同。

标签: python kivy kivy-language


【解决方案1】:

根据docs

@ 字符用于将您的类名与您想要子类化的类分开。 [...]

从得出的结论来看,这是在 .kv 中进行继承的等效方式,类似于 python,因此您应该只选择其中一种方式。这会导致 .py 中的 PlotBox 永远不会被调用。

另一个错误,根据docs,我不知道是不是你的错误,但.kv必须是gui.kv,小写。

子级在执行父级的构造函数后不会直接加载,因此将其添加到构造函数中会产生问题,在 kivy 中的推荐和非常常见的做法是使用Clock

以上所有我都在以下代码中实现:

gui.kv

<PlotBox>:
    graph2:graph2_id
    BoxLayout:
        id:graph2_id

<RootWidget>:
    graph:graph_id
    BoxLayout:
        id:graph_id
    PlotBox:

ma​​in.py

#kivy imports
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
from kivy.clock import Clock

class PlotBox(BoxLayout):
    graph2 = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(PlotBox,self).__init__(**kwargs)
        Clock.schedule_once(lambda dt: self.graph2.add_widget(Button(text="This now works")))

class RootWidget(BoxLayout):
    graph = ObjectProperty(None)
    def __init__(self,**kwargs):
        super(RootWidget,self).__init__(**kwargs)
        self.graph.add_widget(Button(text='This works'))

class GUIApp(App):
    def build(self):
        root = RootWidget()
        return root

if __name__ == "__main__":
    GUIApp().run()

输出:

【讨论】:

  • 添加时钟修复了它!谢谢!至于大写的kv,它似乎可以工作,但我还是重命名了它,而对于继承,它在python中似乎是强制性的(否则它会运行,但子小部件没有正确添加)和kv文件中的可选(它可以工作当它在两者中时)。
【解决方案2】:

我认为 self.graph2__init__ 期间尚未设置 - __init__ 必须返回才能添加任何孩子。

您可以通过执行Clock.schedule_once(function_that_adds_the_button, 0) 之类的操作来解决此问题。

【讨论】:

    【解决方案3】:

    我的工作假设您希望在创建应用程序时运行此代码,而不是稍后。

    千伏。

    <PlotBox>:
        BoxLayout:
            id:graph2_id
    
    <RootWidget>:
        BoxLayout:
            id:graph_id
        PlotBox:
            id: plot
    

    py

    from kivy.app import App
    from kivy.uix.boxlayout import BoxLayout
    from kivy.uix.button import Button
    
    class PlotBox(BoxLayout):
        pass
    
    class RootWidget(BoxLayout):
        pass
    
    class GUIApp(App):
        def build(self):
            root = RootWidget()
            # We can add things to the Root during build before we return it
            # This means we add this information before the user sees anything
            root.ids.graph_id.add_widget(Button(text='This works'))
            # See end of answer for more about this
            root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
            return root
    
    if __name__ == "__main__":
        GUIApp().run()
    

    首先,你不需要 Object Properties 来访问 ids,你可以通过 ids 或 children 来完成:

    self.ids.IDGOESHERE
    

    self.children[INDEXOFIDGOESHERE]
    

    至于这一行:

        root.ids.plot.ids.graph2_id.add_widget(Button(text='This works!'))
    

    Root 有一个 ID 为“plot”的 plotbox 类的instance。 Plot 类(以及所有 plot 类的实例)有一个 BoxLayout 的实例,它带有我们可以访问的 id 图。

    所以我们正在做的是:

    根 -> 绘图 -> Graph2

    如果我们要添加另一个 id 为“big_plot”的绘图框,那么我们可以执行我们之前所做的获取一个 Graph2 或者我们可以获取一个不同的 graph2,因为它属于绘图框的不同实例。

    我们之前做了什么

    根 -> 绘图 -> Graph2

    不同的 id,因此不同的小部件。

    根 -> big_plot -> Graph2

    除非您调用 super,否则您很少需要在 Kivy Widget 类中使用 init 方法(至少根据我的经验)。

    编辑:

    如果您要重复访问超长地址,您可以将它们包装在一个函数中以获取它们。

    示例:

    不太好:

    def func_one(self):
        newtext = 'new'
        self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
    
    def func_two(self):
        newtext = 'newtwo'
        self.ids.IDONE.ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
    
    def func_three(self):
        newtext = 'newthree'
        self.ids.IDSONE.Ids.IDTWO.ids.IDTHREE.ids.IDFOUR.text = newtext
    

    更好:

    def long_address(self):
        return self.ids.IDSONE.ids.IDSTWO.ids.IDTHREE.ids.IDFOUR
    
    def func_one(self):
        newtext = 'new'
        self.long_address().text = newtext
    
    def func_two(self):
       newtext = 'newtwo'
       self.long_address().text = newtext
    
    def func_three(self):
        newtext = 'newthree'
        self.long_address().text = newtext
    

    【讨论】:

    • 谢谢,很高兴知道这样也可以,但是当小部件嵌套得更多时,这会变得很麻烦(因为这需要更多的 .widget.ids)
    • 将您经常使用的长地址包装在一个方法中,以便您可以通过它调用它们。有关示例,请参阅我的答案底部的编辑。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-02
    • 2012-08-30
    • 2015-07-04
    相关资源
    最近更新 更多