Search code examples
pythonlayoutkivykivy-languagekivymd

Kivy MD: How to switch between different layouts using MD Tabs


I'm trying to design a user interface in Python / Kivy MD. The layout should include a MDTab with some icons on top, a button at the bottom and some widgets in the middle. By clicking on the tab icons, the widget in the middle should change accordingly. For example modifying the text of a label (see code). For this purpose, I'm trying to use a ScreenManager defined in the Designer.kv file. Anyway, I'm not sure is the most suitable object.

MDBoxLayout:
    screen_manager: screen_manager

    orientation: "vertical"
    padding: 10, 0, 10, 10
        
    MDTabs:
        id: tabs
        on_tab_switch: app.on_tab_switch(*args)
        
        
    MDScreenManager:
        id: screen_manager
        Screen:
            name: 'screen1'
            MDBoxLayout:
                MDCheckbox:
                MDLabel:
                    text: 'TAB 1'        
        
        Screen:
            name: 'screen2'
            MDBoxLayout:
                MDCheckbox:
                MDLabel:
                    text: 'TAB 2'        
            
        
    MDRaisedButton:
        text: 'CONFIGURE'
        size_hint_x: 1
        pos_hint: {"center_y":0.5}

I recall the screen_manager object in the .py file using the ObjectProperty.

from kivy.lang import Builder
from kivymd.app import MDApp
from kivymd.uix.tab import MDTabsBase
from kivymd.uix.floatlayout import MDFloatLayout
from kivy.properties import ObjectProperty


class Tab(MDFloatLayout, MDTabsBase):
    '''Class implementing content for a tab.'''
   

class MainApp(MDApp):
    screen_manager = ObjectProperty()
    icons = ["clock", "video-3d"]


    def build(self):
        return Builder.load_file('Designer.kv')
    
    
    def on_start(self):
        for tab_name in self.icons:
            self.root.ids.tabs.add_widget(Tab(icon=tab_name))


    def on_tab_switch(
        self, instance_tabs, instance_tab, instance_tab_label, tab_text
    ):
        '''
        Called when switching tabs.

        :type instance_tabs: <kivymd.uix.tab.MDTabs object>;
        :param instance_tab: <__main__.Tab object>;
        :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
        :param tab_text: text or name icon of tab;
        '''
        count_icon = instance_tab.icon
        
        if count_icon == self.icons[0]:
            self.screen_manager.current = 'screen1'
        elif count_icon == self.icons[1]:
            self.screen_manager.current = 'screen2'
        
        
if __name__ == '__main__': 
    MainApp().run()

When I run the code I get this error: AttributeError: 'NoneType' object has no attribute 'current'.

How can I fix it? Any alternative ideas to switch between screen/layouts by clicking on Tab elements?


Solution

  • There were several things to patch up - the code below should work. screen_manager is not an object property, this is the MDScreenManager and it is given a type-hint to help your IDE indicate this. The application has a top-level widget MyProject which inherits a box layout. This Box Layout contains your tabs and the Screen Manager which, I think is the intention you have.

    I added a function to your button and show two different levels where you could deploy it - within the top level widget or within the app. I also moved creation of the tabs to be done sooner, when the top level widget is initialized. The other code is commented away and it will also work; in case you choose to do it this way.

    #!/usr/bin/env python3
    from kivymd.uix.screenmanager import MDScreenManager
    from kivy.lang import Builder
    from kivymd.app import MDApp
    from kivymd.uix.tab import MDTabsBase
    from kivymd.uix.boxlayout import MDBoxLayout
    from kivymd.uix.floatlayout import MDFloatLayout
    
    kv = Builder.load_string('''
    <MyProject>:
        screen_manager: screen_manager
        _tabs: _tabs
        orientation: "vertical"
        padding: 10, 0, 10, 10
            
        MDTabs:
            id: _tabs
            on_tab_switch: app.on_tab_switch(*args)
        MDScreenManager:
            id: screen_manager
            Screen:
                name: 'screen1'
                MDBoxLayout:
                    MDCheckbox:
                    MDLabel:
                        text: 'TAB 1'        
            
            Screen:
                name: 'screen2'
                MDBoxLayout:
                    MDCheckbox:
                    MDLabel:
                        text: 'TAB 2'        
    
        MDRaisedButton:
            text: 'CONFIGURE'
            size_hint_x: 1
            pos_hint: {"center_y":0.5}
            on_release: app.test_1(self)
            on_press: root.test_2(self)
    ''')
    
    
    class Tab(MDFloatLayout, MDTabsBase):
        # Class implementing content for a tab.
        pass
    
    
    class MyProject(MDBoxLayout):
        # type-hints
        screen_manager: MDScreenManager
        _tabs: Tab
    
        # optional to build the icons in this widget
        icons = ["clock", "video-3d"]
    
        def test_2(self, my_button):
            print(f"Top Level(root) widget{my_button.text}\t{self}")
    
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            print(f"{self.screen_manager}\t{self._tabs}")
    
            for tab_name in self.icons:
                self._tabs.add_widget(Tab(icon=tab_name))
    
    
    class MainApp(MDApp):
    
        icons = ["clock", "video-3d"]
    
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self._main = MyProject()
    
        def test_1(self, my_button):
            print(f"MainAPP {my_button.text}\t{self}")
    
        def build(self):
    
            return self._main
    
        def on_start(self):
            pass
            # this is one way, but another way is shown above
            # for tab_name in self.icons:
            #     self.root.ids._tabs.add_widget(Tab(icon=tab_name))
    
        def on_tab_switch(
                self, instance_tabs, instance_tab, instance_tab_label, tab_text
        ):
            """
            Called when switching tabs
            :type instance_tabs: <kivymd.uix.tab.MDTabs object>;
            :param instance_tab: <__main__.Tab object>;
            :param instance_tab_label: <kivymd.uix.tab.MDTabsLabel object>;
            :param tab_text: text or name icon of tab;
            """
            print(f"{instance_tabs} {instance_tab_label} {tab_text}")
            count_icon = instance_tab.icon
    
            if count_icon == self.icons[0]:
                self.root.screen_manager.current = 'screen1'
            elif count_icon == self.icons[1]:
                self.root.screen_manager.current = 'screen2'
    
    
    if __name__ == '__main__':
        MainApp().run()