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:

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)