0

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

gicanzo
  • 69
  • 8
  • Must the newly added row be exactly beneath the row where the `Button` is pressed? Or can the new row just be added at the end of the list, but above the `Save` `Button`? – John Anderson May 05 '20 at 13:03
  • I guess that does not really matter, as long as it goes below it (in fact, it is a recipe ingredients list, so there is not really a reson to add a new row if the actual one is still empty). Only requirement is that Save button is at the bottom (was actually thinking of taking this out of the stacklayout and anchoring it to the bottom of the screen. Good idea?) – gicanzo May 05 '20 at 13:21

2 Answers2

0

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)
John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • I am now trying to do the same using a kv file. While it is obvious how to add 'ing1' and 'quant1' (the two textinputs that show by default), how could I define the dynamically added rows in kv, so that they appear only when pressing the '+' button? – gicanzo May 12 '20 at 10:07
  • I think that should be posted as a new question. – John Anderson May 12 '20 at 12:35
0

Here is a quick and dirty approach , where your problems should be fixed due to a remove and add of the save button. Between those to actions you add a new row before you finally add the save button again. Hope it helps you to adjust your code. The key part is the custom on_press method of the AddButton.

from kivy.app import App
from kivy.uix.stacklayout import StackLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.core.window import Window


class AddButton(Button):
    def __init__(self, **kwargs):
        super(AddButton, self).__init__(**kwargs)

    def on_press(self):
        self.parent.remove_widget(self.parent.save_btn)
        self.parent.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - self.parent.padding[1], Window.height * 0.1)))
        self.parent.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))
        self.parent.add_widget(self.parent.save_btn)


class MyApp(App):
    def build(self):
        Window.size = (400, 600)
        layout = StackLayout(orientation="lr-tb", padding=(20, 40))
        layout.save_btn = Button(text="Save", size_hint=(None, None),
                               size=(Window.width - layout.padding[1], Window.height * 0.1))

        layout.add_widget(TextInput(size_hint=(None, None), size=(Window.width * 0.8 - layout.padding[1], Window.height * 0.1)))
        layout.add_widget(AddButton(text="+", size_hint=(None, None), size=(Window.width * 0.2, Window.height * 0.1)))

        layout.add_widget(layout.save_btn)

        return layout

MyApp().run()
MisterNox
  • 1,445
  • 2
  • 8
  • 22
  • Thank you, this also looks like a good solution, but I found John's answer to better suit my needs – gicanzo May 05 '20 at 15:18