Search code examples
pythonlistkivy

How to automatically create Kivy widgets based on user input


FYI: I'm really new to coding - one of my first time using Stack Overflow so be patient please.

Aim: I am trying to take in user input on Screen1, create an instance of the Player class, store those instances in a list, then iterate over that list to create a MyRowWidget for each 'player' in that list.

The problem: the automatic creation of rows doesn't seem to be working based of the list of players from user input. It may have something to do with passing information between screens

Please feel free to ask any clarifying questions and I will amend. Thank you in advance for your help.

My code (i've reduced this as much as possible, I believe).

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.screenmanager import ScreenManager, Screen

class Player:
   def __init__(self, name):
       self.name = name
       self.stats = {
           "2-pt FG MADE": 0,
           "2-pt FG Missed": 0,
       }

class HeaderRowWidget(GridLayout):
   def __init__(self, **kwargs):
       super(HeaderRowWidget, self).__init__(**kwargs)
       self.cols = 4

       self.add_widget(Label(text="Player Name"))
       self.add_widget(Label(text="Shirt Number"))
       self.add_widget(Label(text="2-pt FG MADE"))
       self.add_widget(Label(text="2-pt FG Missed"))

class MyRowWidget(GridLayout):
   def __init__(self, player, **kwargs):
       super(MyRowWidget, self).__init__(**kwargs)
       self.cols = 4
       self.player = player
       self.name_label = Label(text=player.name)
       self.shirt_number_label = Label(text="default")

       self.add_widget(self.name_label)
       self.add_widget(self.shirt_number_label)

       # Add buttons to the layout (add your buttons here)
       button_labels = ["2-pt FG MADE", "2-pt FG Missed"]
       self.buttons = {}

       for label in button_labels:
           button = Button(text="+")
           self.buttons[label] = button
           self.add_widget(button)
class MyScreenManager(ScreenManager):
   def __init__(self, **kwargs):
       super(MyScreenManager, self).__init__(**kwargs)
       self.screen1 = Screen1(name="screen1")
       self.add_widget(self.screen1)

       # Create Screen2 with the 'players' argument
       self.screen2 = Screen2(name="screen2", players=self.screen1.players)
       self.add_widget(self.screen2)

class Screen1(Screen):
   def __init__(self, **kwargs):
       super(Screen1, self).__init__(**kwargs)
       self.players = []  # List to store player objects

       # Create a layout for the screen
       layout = BoxLayout(orientation='vertical', spacing=10)

       # Create widgets and add them to the layout
       self.player_input = TextInput(hint_text="Enter player name")

       add_button = Button(text="Add Players")
       add_button.bind(on_press=self.add_players_and_teams)

       # add button to go to next screen
       next_screen_button = Button(text="Next screen")
       next_screen_button.bind(on_press=self.switch_screen2)

       layout.add_widget(self.player_input)
       layout.add_widget(add_button)
       layout.add_widget(next_screen_button)

       # add layout to the screen
       self.add_widget(layout)
   def add_players_and_teams(self, instance):
       player_name = self.player_input.text.strip()
       if player_name:
           player = Player(player_name)
           self.players.append(player)
           self.player_input.text = ""

   def switch_screen2(self, instance):
       # Access the ScreenManager and change the current screen to Screen2
       self.manager.current = "screen2"

#code to test 'for player in xyz' logic of Screen2
player1 = Player("Wes")
player2 = Player("James")
player_list = [player1, player2]
class Screen2(Screen):
   def __init__(self, players, **kwargs):
       super(Screen2, self).__init__(**kwargs)

       layout = BoxLayout(orientation='vertical', spacing=10)
       header_widget = HeaderRowWidget()
       layout.add_widget(header_widget)

       for player in players:
           player_widget = MyRowWidget(player=player)
           layout.add_widget(player_widget)

       self.add_widget(layout)
class MyApp(App):
   def build(self):
       return MyScreenManager()

if __name__ == '__main__':
   MyApp().run()

I would expect in Screen 2, automatic rows with buttons to popup for each player that the user has inputted.

For example: if you substitute a random list of players based on the Player class, it seems to work perfectly. For example, pass 'player_list' (located just above 'screen2' class - a list I created myself for testing purposes,) to the 'for player in [insert player_list]' loop in Screen2 and it works as intended.

What I've tried: I've tried working with ChatGPT in a million variations to see if it's something to do with how information is passed between screens, or if it's to do with the way the list is created from user input (using print statements) but nothing seems to give a clear reason. I was hoping someone with Kivy experience might have come across a similar issue before


Solution

  • The problem is that self.screen2 = Screen2(name="screen2", players=self.screen1.players) is called inside the __init__() method of MyScreenManager. At that point, there are no items in the players list of Screen1, so Screen2 is created with no players. You must add the players to Screen2 after the user inputs them. To do this you can modify the Screen2 class to provide a method for adding players:

    class Screen2(Screen):
       def __init__(self, players, **kwargs):
           super(Screen2, self).__init__(**kwargs)
    
           self.layout = BoxLayout(orientation='vertical', spacing=10)
           header_widget = HeaderRowWidget()
           self.layout.add_widget(header_widget)
    
           for player in players:
               player_widget = MyRowWidget(player=player)
               self.layout.add_widget(player_widget)
    
           self.add_widget(self.layout)
    
       def add_player(self, player):
           self.layout.add_widget(MyRowWidget(player=player))
    

    Then that new method can be used to add players to Screen2, for example, in the switch_screen2():

       def switch_screen2(self, instance):
           # Access the ScreenManager and change the current screen to Screen2
           self.manager.current = "screen2"
           scr2 = self.manager.get_screen('screen2')
           for player in self.players:
               scr2.add_player(player)
    

    Of course, there are details to be handled, but this should give you a starting point.