Skip to content
Advertisement

How to stack multi-line labels vertically in BoxLayout

I am trying to create a stack of multiline Labels on Kivy inside a BoxLayout. How do I get the BoxLayout to expand according to its contents? Now the BoxLayout is squeezing the Labels instead.

Desired vs Actual Effect

I do not wish to hardcode the multiline Label size as I want each to be flexible to accommodate varying lines of text.

My demo code is as follows:

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.gridlayout import GridLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.label import Label
from kivy.graphics import Color, Rectangle


class MyApp(App):
    def build(self):
        self.root = GridLayout(rows=1)
        self.root.add_widget(Scroller())
        return self.root


class Scroller(ScrollView):
    def __init__(self):
        ScrollView.__init__(self)
        self.view = GridLayout(cols=1, size_hint=(1, None))
        self.add_widget(self.view)
        self.view.bind(minimum_height=self.view.setter("height"))
        for i in range(20):
            self.view.add_widget(MyWidget(i % 2 is 1))


class MyWidget(BoxLayout):
    def __init__(self, odd, **kwargs):
        super().__init__(**kwargs)
        self.orientation = "vertical"
        self.size_hint = (1, None)
        self.odd = odd
        for i in range(3):
            ll = Label(text=f"I am linenNumber {i + 1}")
            self.add_widget(ll)
        self.bind(pos=self.format_background_color)
        self.bind(size=self.format_background_color)

    def format_background_color(self, *args):
        with self.canvas.before:
            if self.odd:
                Color(0.0, 0.0, 0.2, mode="rgb")
            else:
                Color(0.0, 0.0, 0.8, mode="rgb")
            Rectangle(pos=self.pos, size=self.size)


MyApp().run()

Advertisement

Answer

If the Labels you added to MyWidget have fixed amount of text you can just set a specific value for MyWidget‘s height as self.height = "150dp" (Adjust to your need).

Otherwise if you want the MyWidget‘s height enough to contain its children (here, Labels) then set it to its minimum_height. Also you have to set each Label‘s height to its texture height.

Thus your modified MyWidget class should now look like,

class MyWidget(BoxLayout):
    def __init__(self, odd, **kwargs):
        super().__init__(**kwargs)
        self.orientation = "vertical"
        self.size_hint_y = None
        self.odd = odd
        self.bind(
            pos = self.format_background_color,
            size = self.format_background_color,
            minimum_height = self.setter("height")
        )
        for i in range(3):
            ll = Label(text=f"I am linenNumber {i + 1}")
            ll.bind(texture_size=self.resize_label)
            self.add_widget(ll)

    def resize_label(self, instance, value):
        instance.size_hint_y = None
        instance.height = value[1]
    ...

You can also utilize padding and spacing as,

class MyWidget(BoxLayout):
    def __init__(self, odd, **kwargs):
        super().__init__(**kwargs)
        self.orientation = "vertical"
        self.size_hint_y = None
        self.padding = "10dp"
        self.spacing = "10dp"
        ...

User contributions licensed under: CC BY-SA
3 People found this is helpful
Advertisement