Accessing values of widgets created by Python loop

215 views
Skip to first unread message

ST

unread,
Aug 6, 2020, 9:03:34 PM8/6/20
to Kivy users support
Dear experienced users,

I'm having trouble accessing values from ToggleButtons that were created iteratively by a loop.

I want my program to work like this:
  1. The user sets the grid size in main window
  2. A grid of toggle buttons is created in a popup
  3. The user edits the states of toggle buttons
  4. A list of 'up' buttons is printed as output
When I try to run my code, I get the following error:

AttributeError: 'super' object has no attribute '__getattr__'

After a bit of searching, I'm pretty sure this is caused by assigning the id as a string in the loop.

However, because I don't yet have a good understanding of Python and kivy,
I am currently stumped on finding a workaround.

What is a good way to achieve this goal?
I would appreciate any help. Thanks!

Below is the code:

buttonGrid.py

from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.togglebutton import ToggleButton

Builder.load_string('''

#:kivy 1.11.1

<NumberInput@TextInput>:

    pos_hint: {'center_x': .5, 'center_y': .5}
    padding: [6, 0.9*(self.height-self.line_height)/2]
    input_filter: 'int'
    multiline: False
    text: '10'

<GridButton@ToggleButton>:
    id: self.id
    state: 'down'
    text: self.id.replace('b','')
    size_hint: None, None
    size: 30, 30

<MainScreen>:

    grid_size_x: grid_size_x
    grid_size_y: grid_size_y

    BoxLayout:
        orientation: 'vertical'
        
        GridLayout:
            cols:2
            
            Label:
                text: 'Grid size (X)'
                
            NumberInput:
                id: grid_size_x
                
            Label:
                text: 'Grid size (Y)'
                
            NumberInput:
                id: grid_size_y
                
        Button:
            text: 'Make grid'
            on_release: root.open_popup()

<PopupScreen>:

    orientation: 'vertical'
    size: root.size
    pos: root.pos
    
    Label:
        text: 'This is a grid'
        size_hint_y: None
        height:30
    
    Button:
        text: 'Print'
        on_release: root.print_output()
        size_hint_y: None
        height:30
    
    Button:
        text: 'Cancel'
        on_release: root.cancel()
        size_hint_y: None
        height:30
        
''')

class GridButton(ToggleButton):
    
    id = StringProperty('') # <- likely cause
    pass

class PopupScreen(BoxLayout):

    print_output = ObjectProperty(None)
    cancel = ObjectProperty(None)

class MainScreen(FloatLayout):

    grid_size_x = ObjectProperty(None)
    grid_size_y = ObjectProperty(None)
    
    def dismiss_popup(self):
    
        self._popup.dismiss()
        
        # return window to adjusted size
        Window.size = self.current_size

    def get_values(self):

        # print grid size

        print(f"x size: {self.grid_size_x.text}" + '\n' +
              f"y size: {self.grid_size_y.text}")
        
        ###########################################
        ### get states of grid buttons          ###
        ### for example,                        ###
        ### print(state of button with id: b1)  ###
        ### or get list of 'up' buttons         ###
        ###########################################
        
        print(self._popup.ids.b1.state) # <- doesn't work...

    def open_popup(self):
    
        # get grid sizes
        x_grid = int(self.grid_size_x.text)
        y_grid = int(self.grid_size_y.text)

        # remember main screen window size
        self.current_size = Window.size
        
        # set popup window size
        Window.size = 100+x_grid*30, 200+y_grid*30
        
        # set content of popup
        content = PopupScreen(print_output=self.get_values,
                              cancel=self.dismiss_popup)
        
        # create grid
        grid = GridLayout(cols=x_grid,
                          size_hint=(None,None),
                          size=(x_grid*30, y_grid*30))
        
        # add buttons to grid
        for i in range(x_grid * y_grid):
            grid.add_widget(GridButton(id='b'+str(i+1))) # <- likely cause

        # center grid with AnchorLayout
        grid_wid = AnchorLayout(anchor_x='center',
                                anchor_y='center')
        grid_wid.add_widget(grid)

        # add grid to popup
        content.add_widget(grid_wid, 2)

        # open popup
        self._popup = Popup(title="Popup", content=content)
        self._popup.open()

# define Base Class of Kivy App
class TestApp(App):

    def build(self):    
        Window.size = 200, 150
        return MainScreen()

# run program
if __name__ == '__main__': 
    TestApp().run()
buttonGrid.py

Elliot Garbus

unread,
Aug 6, 2020, 10:40:14 PM8/6/20
to kivy-...@googlegroups.com

The id only works for id’s defined in kivy.  When kivy is parsed the id’s get put into the ids dictionary.  The ids dict provides a way to access the objects defined in kv.

 

Here you know that all of your toggle buttons are in grid.  I created an instance variable, self.grid to hold the grid.  This lets us access the children of grid, all of the ToggleButtons you added.

 

print(self.grid.children)  # The children in grid
for b in self.grid.children:
   
print(f'text:{b.text}   state:{b.state}')

 

 

Here is the your code with the fixes:

 

from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import ObjectProperty, StringProperty
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.uix.togglebutton import ToggleButton

Builder.load_string(
'''


#:kivy 1.11.1

<NumberInput@TextInput>:

    pos_hint: {'center_x': .5, 'center_y': .5}
    padding: [6, 0.9*(self.height-self.line_height)/2]
    input_filter: 'int'
    multiline: False
    text: '10'

<GridButton>:  # defined in python remove @ToggleButton
)


)

   
def __init__(self, **kwargs):
       
super().__init__(**kwargs)
       
self.grid = None               # use to hold grid

   
def dismiss_popup(self):
       
self._popup.dismiss()

       
# return window to adjusted size
       
Window.size = self.current_size

   
def get_values(self):
       
# print grid size

       
print(f"x size: {self.grid_size_x.text}" + '\n' +
             
f"y size: {self.grid_size_y.text}")

       
###########################################

        ### get states of grid buttons          ###
        ### for example,                        ###
        ### print(state of button with id: b1)  ###
        ### or get list of 'up' buttons         ###
        ###########################################

        print(self.grid.children)  # The children in grid
       
for b in self.grid.children:
           
print(f'text:{b.text}   state:{b.state}')



   
def open_popup(self):
       
# get grid sizes
       
x_grid = int(self.grid_size_x.text)
        y_grid =
int(self.grid_size_y.text)

       
# remember main screen window size
       
self.current_size = Window.size

       
# set popup window size
       
Window.size = 100 + x_grid * 30, 200 + y_grid * 30

       
# set content of popup
       
content = PopupScreen(print_output=self.get_values,
                             
cancel=self.dismiss_popup)

       
# create grid
       
self.grid = GridLayout(cols=x_grid,
                         
size_hint=(None, None),
                         
size=(x_grid * 30, y_grid * 30))

       
# add buttons to grid
       
for i in range(x_grid * y_grid):
           
self.grid.add_widget(GridButton(id='b' + str(i + 1)))  # <- likely cause


        # center grid with AnchorLayout
       
grid_wid = AnchorLayout(anchor_x='center',
                                
anchor_y='center')
        grid_wid.add_widget(
self.grid)

       
# add grid to popup
       
content.add_widget(grid_wid, 2)

       
# open popup
       
self._popup = Popup(title="Popup", content=content)
       
self._popup.open()


--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/9fc8410f-700e-4afe-a2cd-5f932364107ao%40googlegroups.com.

 

ST

unread,
Aug 7, 2020, 12:12:07 AM8/7/20
to Kivy users support
Dear Elliot Garbus,

thank you so much again for the quick and elegant solution.

I now see how useful
super().__init__(**kwargs)
is.

I'll try to learn and master it, since it's like some ancient spell for me right now.

Thank you!

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-...@googlegroups.com.

Elliot Garbus

unread,
Aug 7, 2020, 12:19:46 PM8/7/20
to kivy-...@googlegroups.com

Here is something useful to read while you ponder the magic of super(): https://rhettinger.wordpress.com/2011/05/26/super-considered-super/

 

Here is the what you need to know in short form.

It is convention to declare the instance variables of a class in the __init__() method.

If the class has a parent class, and you are overload __init__(), you also need to call the parents __init__().  The magic line super().__init__(**kwargs), calls the parents __init__().  **kwargs is the dictionary that holds the keyword arguments that were passed in.

 

I like this reference on **kwargs.  https://realpython.com/python-kwargs-and-args/

 

This should help to demystify the magic ancient spells!

1.The user sets the grid size in main window

2.A grid of toggle buttons is created in a popup

3.The user edits the states of toggle buttons

4.A list of 'up' buttons is printed as output

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/e8481eba-a7f0-4c68-ac72-192430e3bddfo%40googlegroups.com.

 

ST

unread,
Aug 8, 2020, 3:04:59 AM8/8/20
to Kivy users support
Thank you for the concise explanation and helpful references.

I could finally understand the basics of how classes and unpacking operators work in Python!
Reply all
Reply to author
Forward
0 new messages