pythonkivyscrollviewheight

Kivy ScrollView minimum_height no longer dynamic with nested layouts in python


Currently battling with Kivy nested layotus and scorll view. I dynamically add or remove from a table with some buttons. As the page grows I expect self.minimum_height of the parent CreatePly layout to change but it does not.

Is there a neat way to dynamically change the height of my scorll view or will I have to do it Caveman style and increase the it manually after the creation of every widget?

Python Code:

#This file contains the graphical user interface logic of the DIS creator

from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.metrics import dp
from kivy.uix.widget import Widget
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.uix.togglebutton import ToggleButton

class DIS3(App):
    pass

class CreatePly(GridLayout):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

#Dimensional control
        self.spacing = dp(2)
        self.padding = dp(10)
        self.cols = 1
        self.size_hint = (1, None)
        self.height = self.minimum_height #I expect this to dynamically change with the nested layouts. It does not.

#Varibales
        self.constituent_name = []
        self.constituent_areaweight = []
        self.structural_toggle = []

#Add and remove consituents
#Header
        self.add_remove_header = BoxLayout()
        self.add_remove_header.size_hint = (1, None)
        self.add_remove_header.height = dp(40)

        label = Label(text="Add constituents to create a ply")
        self.add_remove_header.add_widget(label)
        self.add_widget(self.add_remove_header)

#Add remove buttons
        self.add_remove_buttons = GridLayout()
        self.add_remove_buttons.cols = 4
        self.add_remove_buttons.size_hint = (1, None)
        self.add_remove_buttons.height = dp(40)
        self.add_remove_buttons.add_widget(Widget())
        button = Button(text="+", size_hint=(None, None), width=dp(40), height=dp(40))
        button.bind(on_press = lambda x: self.add_constituent_press())
        self.add_remove_buttons.add_widget(button)
        button = Button(text="-", size_hint=(None, None), width=dp(40), height=dp(40))
        button.bind(on_press = lambda x: self.remove_constituent_press())
        self.add_remove_buttons.add_widget(button)
        self.add_remove_buttons.add_widget(Widget())
        self.add_widget(self.add_remove_buttons)

#Constituent table
        self.constituent_table = GridLayout()
        self.constituent_table.cols = 3

        label = Label(text="Consituent name", size_hint=(0.55, None), height=dp(20))
        self.constituent_table.add_widget(label)
        label = Label(text="Areal weight (g/m2)", size_hint=(0.3, None), height=dp(20))
        self.constituent_table.add_widget(label)
        label = Label(text="Structural?", size_hint=(0.15, None), height=dp(20))
        self.constituent_table.add_widget(label)

        textinput = TextInput(size_hint=(0.55, None), height=dp(40))
        self.constituent_name.append(textinput)
        self.constituent_table.add_widget(textinput)
        textinput = TextInput(size_hint=(0.3, None), height=dp(40))
        self.constituent_areaweight.append(textinput)
        self.constituent_table.add_widget(textinput)
        toggle = ToggleButton(text="No", size_hint=(0.15, None), height=(dp(40)))
        toggle.bind(state=(lambda self, x: CreatePly.structural_constituent_toggle(self, toggle)))
        self.structural_toggle.append(toggle)
        self.constituent_table.add_widget(toggle)
        self.add_widget(self.constituent_table)

#Build ply button
        self.footer = GridLayout()
        self.footer.cols = 3

        self.footer.size_hint = (1, None)
        self.footer.height = dp(40)
        self.footer.add_widget(Widget())
        button = Button(text="Create ply", size_hint=(None, None), width=dp(120), height=dp(40))
        button.bind(on_press = lambda x: self.create_ply())
        self.footer.add_widget(button)
        self.footer.add_widget(Widget())
        self.add_widget(self.footer)

#Functions
    def structural_constituent_toggle(self, toggle):
        if toggle.state == "normal":
            toggle.text = "No"
        else:
            toggle.text = "Yes"

    def add_constituent_press(self):
        textinput = TextInput(size_hint=(0.55, None), height=(dp(40)))
        self.constituent_name.append(textinput)
        self.constituent_table.add_widget(textinput)

        textinput = TextInput(size_hint=(0.3, None), height=(dp(40)))
        self.constituent_areaweight.append(textinput)
        self.constituent_table.add_widget(textinput)

        toggle = ToggleButton(text="No", size_hint=(0.15, None), height=(dp(40)))
        toggle.bind(state=(lambda self, x: CreatePly.structural_constituent_toggle(self, toggle)))
        self.structural_toggle.append(toggle)
        self.constituent_table.add_widget(toggle)

    def remove_constituent_press(self):

        if len(self.constituent_name) == 1:
            pass
        
        else:
            self.constituent_table.remove_widget(self.constituent_name[-1])
            del self.constituent_name[-1]

            self.constituent_table.remove_widget(self.constituent_areaweight[-1])
            del self.constituent_areaweight[-1]

            self.constituent_table.remove_widget(self.structural_toggle[-1])
            del self.structural_toggle[-1]

    def create_ply(self):
        pass

#Run loop
DIS3().run()

KV file code:

#This file contains the graphical user interface elements of the DIS creator app

#This is the layout of the entire screen
MainLayout:

<MainLayout@BoxLayout>:
    #Background colour
    canvas.before:
        Color:
            rgba:(.3,.3,.3,1)
        Rectangle:
            pos: self.pos
            size: self.size

    padding: '10dp'
    spacing: '10dp'
    MenuLayout:
    FocusFrame:

#This is the layout for the menu
<MenuLayout@StackLayout>:
    #Background colour
    canvas.before:
        Color:
            rgba:(0,0,0,1)
        Rectangle:
            pos: self.pos
            size: self.size

    #Dimension control
    size_hint: None, 1
    width: "160dp"
    spacing: "2dp"
    padding: "10dp"

    Button:
        text: "Database"
        size_hint: 1, None
        height: "40dp"

    Button:
        text: "Create ply"
        size_hint: 1, None
        height: "40dp"

    Button:
        text: "Create preform"
        size_hint: 1, None
        height: "40dp"

    Button:
        text: "Plan infusion"
        size_hint: 1, None
        height: "40dp"

    Button:
        text: "Write DIS"
        size_hint: 1, None
        height: "40dp"

    Button:
        text: "Review DIS"
        size_hint: 1, None
        height: "40dp"

#This is the layout for the scrollable focus frame
<FocusFrame@ScrollView>:
    canvas.before:
        Color:
            rgba:(0,0,0,1)
        Rectangle:
            pos: self.pos
            size: self.size

    CreatePly:

Thanks in advance!


Solution

  • Using:

    self.height = self.minimum_height
    

    in your __init__() method sets the height to the value of minimum_height at the time when the __init__() method executes. It will have no effect on the height after that.

    You want to bind the minimum_height to the height, so that the height will change when minimum_height changes. Two ways to do that are to use:

    height: self.minimum_height
    

    inside a kv file (The Builder will create the needed binding for you). or in your python code:

    self.constituent_table.bind(minimum_height=self.constituent_table.setter('height'))
    

    which explicitly creates the needed binding. See the documentation.

    So, try making the following changes to your code: In your kv file:

    CreatePly:
        height: self.minimum_height
    

    and in your python code:

    comment out the line in the __init__() method:

    # self.height = self.minimum_height
    

    and add the lines:

        self.constituent_table.size_hint_y = None
        self.constituent_table.bind(minimum_height=self.constituent_table.setter('height'))