将 Kivy ObjectProperty 绑定到子小部件似乎在根小部件之外不起作用

Binding Kivy ObjectProperty to a child widget doesn't seem to work outside of root widget

正在尝试遵循本指南: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。

根据docs

The @ character is used to separate your class name from the classes you want to subclass. [...]

根据得出的结论,它是在 .kv 中进行继承的等效方法,类似于 python,因此您应该只 select 这些方法中的一种。这会导致 .py 中的 PlotBox 永远不会被调用。

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

children 在执行parent 的构造函数后不会直接加载,所以在constructor 中添加它会产生问题,在kivy 中推荐和一个非常普遍的做法是使用Clock.

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

gui.kv

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

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

main.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()

输出:

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

您可以通过执行 Clock.schedule_once(function_that_adds_the_button, 0).

之类的操作来解决此问题

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

kv.

<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()

首先,您不需要对象属性来访问 id,您可以通过 id 或 children 来实现:

self.ids.IDGOESHERE

self.children[INDEXOFIDGOESHERE]

关于这一行:

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

Root 有一个 plotbox class 的 实例 ,id 为 'plot'。 Plot class(因此所有的 plot class 实例)都有一个 BoxLayout 实例,其中包含我们可以访问的 id 图。

所以我们正在做的是:

根 -> 图 -> Graph2

如果我们要添加另一个 ID 为 'big_plot' 的绘图框,那么我们可以按照之前的操作来获得一个 Graph2,或者我们可以获得一个不同的 graph2,因为它属于绘图盒。

我们之前做了什么

根 -> 图 -> 图 2

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

根 -> big_plot -> Graph2

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

编辑:

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

示例:

不太好:

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