Попытка следовать этому руководству: https://kivy.org/docs/guide/lang.html#accessing-widgets-defined-inside-kv-lang-in-your-python-code

Я пытаюсь получить доступ к виджету, используя определение идентификатора. Это хорошо работает внутри корневого виджета, но, похоже, не работает за его пределами. В качестве примера вот минимальный код, представляющий мою проблему:

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). На моем другом виджете это как будто идентификатор не создается.

1
Flabou 24 Фев 2018 в 17:17

3 ответа

Лучший ответ

Согласно документам:

Символ @ используется для отделения имени вашего класса от классов, которые вы хотите подклассировать. [ ... ]

Из того, что делается вывод, что это эквивалентный способ сделать наследование в .kv похожим на python, поэтому вы должны выбрать только один из этих способов. Это приводит к тому, что PlotBox из .py никогда не вызывается.

Согласно другой документации, я делаю еще одну ошибку: не знаю, если это ваша ошибка, но .kv должен быть gui.kv, с нижним регистром.

Дочерние элементы не загружаются непосредственно после выполнения конструктора родителя, поэтому добавление его в конструктор может вызвать проблемы. Рекомендация и очень распространенная практика в 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()

Выход:

enter image description here

0
eyllanesc 24 Фев 2018 в 16:32

Я работаю в предположении, что вы хотите, чтобы этот код запускался при создании приложения, не позднее.

Кв.

<PlotBox>:
    BoxLayout:
        id:graph2_id

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

Ру

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

Во-первых, вам не нужны свойства объекта для доступа к идентификаторам, вы можете сделать это через идентификаторы или дочерние элементы:

self.ids.IDGOESHERE

Или

self.children[INDEXOFIDGOESHERE]

Что касается этой строки:

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

Root имеет экземпляр класса plotbox с идентификатором plot. Класс Plot (и, следовательно, все экземпляры класса plot) имеют экземпляр BoxLayout с графом id, к которому мы можем получить доступ.

Итак, что мы делаем:

Корень -> Сюжет -> График2

Если бы мы добавили еще один сюжет с идентификатором «big_plot», то мы могли бы сделать либо то, что мы делали раньше, чтобы получить один Graph2, либо мы могли бы получить другой graph2, потому что он принадлежит другому экземпляру plotbox.

Что мы делали раньше

Корень -> Сюжет -> График2

Другой идентификатор, следовательно, другой виджет.

Root -> big_plot -> Graph2

Если вы не вызываете super, вам редко нужно использовать метод init в классе Kivy Widget (или, по моему опыту, так или иначе).

Редактировать:

Если вы собираетесь обращаться к супер длинным адресам неоднократно, вы можете заключить их в функцию, чтобы получить их.

Пример:

Не хорошо:

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
0
AlexAndDraw 26 Фев 2018 в 16:10

Я думаю, что self.graph2 просто еще не был установлен во время __init__ - __init__ должен вернуться до того, как будут добавлены какие-либо дочерние элементы.

Вы можете обойти это, сделав что-то вроде Clock.schedule_once(function_that_adds_the_button, 0).

0
inclement 24 Фев 2018 в 16:37