python-3.xlayoutkivykivy-language

How can I make my Boxlayout stick to the top, within another Boxlayout?


I am making an App in kivy, that has a menu to navigate between screens. The menu is a vertical BoxLayout with Buttons added, which will later make the screens switch. Also, it's added to a horizontal BoxLayout, as well as the Screen Manger. What I'm currently trying to achieve is to make that vertical BoxLayout stick to the top. I have researched, that pos_hint: {"top": 1} should do the trick, but it doesn't behave as I expected it to do. Did I understand it wrong? Or does it not work for a BoxLayout in a BoxLayout?

My script looks like this:

from kivy.lang import Builder
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen

class Menu(BoxLayout):
    pass

class ArtistTab(Screen):
    pass

class SongTab(Screen):
    pass

class AlbumTab(Screen):
    pass

class MuDoApp(App):
    def build(self):
        interface = Builder.load_file('main.kv')
        sm = ScreenManager()
        sng_tab = SongTab(name="song")
        art_tab = ArtistTab(name="artist")
        alb_tab = AlbumTab(name="album")
        sm.add_widget(art_tab)
        sm.add_widget(alb_tab)
        sm.add_widget(sng_tab)
        interface.add_widget(sm)
        print(interface.ids["menu"].height)
        print(interface.ids["menu"].minimum_height)
        return interface
    
if __name__ == '__main__':
    app = MuDoApp()
    app.run()

and my .kv file:

BoxLayout:
    Menu:
        orientation: "vertical"
        pos_hint: {"y":1}
        id: menu
        
        Label:
            size_hint: (None,None)
            size: 500,100
            text: "Download by ..."
        Button:
            size_hint: (None,None)
            size: 300,100
            text: "songtitel"
        Button:
            size_hint: (None,None)
            size: 300,100
            text: "artist"
        Button
            size_hint: (None,None)
            size: 300,100
            text: "album"
        
        
<ArtistTab>:
    BoxLayout:
        id: artist_tab
        TextInput:
            size_hint: (None,None)
            width: 500
        Button:
            size_hint: (None,None)
            width: 300
            text: "download"

I've played around with the pos_hint parameter and had these results:

pos_hint: {"top":1} "top":1

pos_hint: {"top":0} "top":0

pos_hint: {"top":.7} "top":.7

pos_hint: {"y":1} "y":1

pos_hint: {"y":0} "y":0

pos_hint: {"y":.7} "y":.7

Also, print(interface.ids["menu"].height) printed 100 and print(interface.ids["menu"].minimum_height) printed 0, which also surprised me, since the widgets in my menu add to a height of 400.


Solution

  • Your print statements are being executed before the actual size of the Menu is set, so you are just seeing the default values there.

    Using the pos_hint: {"top":1} is the right thing to get what you want, but the problem is that the BoxLayout assigns its entire vertical space to each of its children. The Menu ends up getting the full height, so the pos_hint has no effect. Considering both these facts, you can accomplish your goal using this code:

        pos_hint: {"top":1}
        size_hint_y: None
        height: self.minimum_height
    

    for the Menu. This sets the Menu height to its minimum value, so that the pos_hint can actually do something.