Search code examples
pythonpython-3.xkivyattributeerror

AttributeError: 'MyGrid' object has no attribute for my function


The Goal
I am trying to create a simple script that demonstrates how to attach a function to a button that can view (and soon change) properties of another widget. I know this is possible based off of the example script effectwidget.py.


My Methods
At the moment, I'm trying to reverse engineer effectwidget.py because it does a lot of things I'll probably want to do with kivy.

In effectwidget.py, the SpinnerRow class has a function called update_effectwidget() that can view/edit attributes of specific instances of ComparisonWidget (which isn't even a child of SpinnerRow in the widget tree). SpinnerRow has child widgets EffectSpinner which trigger update_effectwidget().

In my script, ButtonHolder plays the role of SpinnerRow and colorChange1() plays the role of update_effect().

My Code
This code was reduced to only show the reproducible error. So I don't intend on using this to change the color of the labels.

#!/usr/bin/env python3
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.label import Label
from kivy.lang import Builder

class ButtonHolder(BoxLayout):
    def colorChange1(self, *args):
        print("this function works")

Builder.load_string("""
<MyGrid>:
  rows: 3
  Label:
    canvas.before:
      Color:
        rgba: 1,0,0,1
      Rectangle:
        pos: self.pos
        size: self.size
    id: toplabel
  Label:
    canvas.before:
      Color:
        rgba:  0,1,0,1
      Rectangle:
        pos: self.pos
        size: self.size
    id: bottomlabel
  ButtonHolder:
    Button:
      effectwidget: toplabel
      on_press: root.colorChange1()
""")

class MyGrid(GridLayout):
    pass

class TheApp(App):
    def build(self):
        return MyGrid()

TheApp().run()

The Problem
I am receiving the following error:
AttributeError: 'MyGrid' object has no attribute 'colorChange1'

My questions to you

Why is my function colorChange1() inside my ButtonHolder not being found when it follows the same structure as effectwidget.py?

For the purposes of scope and management, it's not practical for me to give every class its own functions so they can be called by self.functionName(). And if root.functionName() only calls functions in the very root widget (rather than any parents widgets along the way), wouldn't that mean that the root widget of a large kivy program would have to contain dozens of functions?

Note: The closest questions I could find to this were Kivy 'object has no attribute' Error and AttributeError: 'Button' object has no attribute 'update_label'.
But their cases were too complex and specific to find the answer to my general problem. But I have seen them.


Solution

  • Your main one is the ignorance of the concept of root. To observe it better I have indented better your code:

    <MyGrid>:
        rows: 3
        Label:
            id: toplabel
            canvas.before:
                Color:
                    rgba: 1,0,0,1
                Rectangle:
                    pos: self.pos
                    size: self.size
        Label:
            id: bottomlabel
            canvas.before:
                Color:
                    rgba:  0,1,0,1
                Rectangle:
                    pos: self.pos
                    size: self.size
        ButtonHolder:
            Button:
                effectwidget: toplabel
                on_press: root.colorChange1()
    

    The root is the initial element of the structure, in this case it is MyGrid.

    Does MyGrid have a colorChange1 method? No, that's why you get that error.

    What class does the colorChange1 method belong to? belongs to the ButtonHolder class, then the root must be changed by a reference of an object through an id.

    <MyGrid>:
        rows: 3
        Label:
            id: toplabel
            canvas.before:
                Color:
                    rgba: 1,0,0,1
                Rectangle:
                    pos: self.pos
                    size: self.size
        Label:
            id: bottomlabel
            canvas.before:
                Color:
                    rgba:  0,1,0,1
                Rectangle:
                    pos: self.pos
                    size: self.size
        ButtonHolder:
            id: button_holder # <---
            Button:
                effectwidget: toplabel
                on_press: button_holder.colorChange1() # <---