Search code examples
pythonkivystacklayout

Kivy, StackLayout, object order


I have a screen with a StackLayout. The first row of the stack includes a textinput and a "+" button which is meant to add another identical row below the actual one in a loop (i.e. another textinput with another "add" button). Then there is a "Save" button, which is supposed to be always at the end of the stack. The dictionary is supposed to later grab the input from the text field, when pressing the save button, but this should not be relevant to my problem.

There are two problems with my code:

  • First, when I press the "+" button in a row, the button that gets highlighted is not this button itself, but the one in the row below (e.g. if there are three rows and I press the button in the second row, the button in the third row is highlighted)
  • Second and most importantly, starting from the second row onwards, each press of the "+" button adds a new row above the actual one, rather than below it.

I am aware that kivy assigns reverse order to the widgets (i.e. the one added last will be drawn first) and that is why I am manually assigning indexes to the new rows. However, I cannot achieve the desired behavior.

Here is a minimal working version:

from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.textinput import TextInput
from kivy.uix.stacklayout import StackLayout
from kivy.uix.screenmanager import ScreenManager, Screen

class AddWindow(Screen):
    def __init__(self, **kwargs):
        super(AddWindow, self).__init__(**kwargs)

        self.grid = StackLayout()
        self.grid.pos_hint = {"x":0.05,"top":0.8}
        self.grid.size_hint = (0.9,None)
        self.add_widget(self.grid)

        self.i = 1
        self.n = 1
        self.inputs = {}
        self.ing1 = TextInput(size_hint=(0.9,'0.3sp'))
        self.grid.add_widget(self.ing1)
        self.inputs['0'] = 'ing1'

        self.addIng = Button(text="+", size_hint=(0.1,'0.3sp'))
        self.addIng.bind(on_press=self.addIngredient)
        self.grid.add_widget(self.addIng)

        self.doneButton = Button(text="Save")
        self.grid.add_widget(self.doneButton, index=0)

    def addIngredient (self, instance):
        self.ing = TextInput(size_hint=(0.9,'0.3sp'))
        self.inputs[self.i] = 'ing{}'.format(self.i+1)
        self.grid.add_widget(self.ing, index=self.n+1)

        self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
        self.addNext.bind(on_press=self.addIngredient)
        self.grid.add_widget(self.addNext, index=self.n+2)
        self.i += 1
        self.n += 2
        print(self.inputs)        

WMan = ScreenManager() 
WMan.add_widget(AddWindow(name='add'))


class RecipApp(App):
    def build(self):
        return WMan

if __name__ == "__main__":
    RecipApp().run()

What am I missing? Here is a screenshot for better clarity: Screenshot


Solution

  • Here is a brute force method to do what you want by rebuilding the StackLayout each time a + `Button is pressed:

    def addIngredient(self, instance):
        tmp_children_list = self.grid.children[:]
        self.grid.clear_widgets()
        for index in range(len(tmp_children_list)-1, -1, -1):
            child = tmp_children_list[index]
            self.grid.add_widget(child)
            if child == instance:
                # this is the pressed Button, so add new row after it
                self.n += 1
                self.ing = TextInput(size_hint=(0.9,'0.3sp'))
                self.ing.text = str(self.n)  # add text just for identification
                self.grid.add_widget(self.ing)
                self.addNext = Button(text="+", size_hint=(0.1,'0.3sp'))
                self.addNext.bind(on_press=self.addIngredient)
                self.grid.add_widget(self.addNext)