I'm writing a simple app in KivyMD. According to the kivy documentation my .kv structure with MDNavigationDrawer and MDToolbar is right and everything works fine as long as the screens are empty. When I add content to them, the content instead of being under the Toolbar is above it. How can I fix it?
Here is my code:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
KV = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: "Screen 1"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 1"
OneLineListItem:
text: "Screen 2"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 2"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
title: "Test"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
NavigationLayout:
x: toolbar.height
ScreenManager:
Screen:
name: "scr 1"
ScrollView:
BoxLayout:
orientation: "vertical"
Button:
text: "Hello"
Button:
text: "I wish"
Button:
text: "I could"
Button:
text: "Finally get"
Button:
text: "This to work"
Screen:
name: "scr 2"
MDLabel:
text: "Screen 2"
halign: "center"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
Output of the following code:
The Screen class is a RelativeLayout, so you must position its children as you would any RelativeLayout. Every child of the Screen gets the default size_hint of (1,1) and the default pos of (0,0), so you must adjust it if that is not what you want. In your case, the NavigationLayout will completely cover the Screen based on those default values. You can fix that by just adding a size_hint_y, like this:
NavigationLayout:
x: toolbar.height
size_hint_y: 1.0 - toolbar.height/root.height
This sets the size of the NavigationLayout so that it just meets the bottom of the MDToolbar instead of overlpping it.
It's the order of appearance that is the problem:
If the screen has other widgets such as cards and Layouts, all of these must appear first.
i.e. put the Navigation Layout and the respective screen manager at the bottom.
What comes last appears on top of everything else.
That worked for me.
A most elegant solution is to insert the MDToolbar and NavigationLayout into a BoxLayout with orientation: "vertical" so the NavigationLayout start at the bottom of the Toolbar, without calculating the relative position.
Screen:
BoxLayout:
orientation: "vertical"
MDToolbar:
MDNavigationLayout:
Related
Using KivyMD MDNavigationLayout, I'm trying to make a navigation drawer for an app! I did it that way, however my navigation drawer now moves across several screens! I only want it to appear on my HomeScreen, not my MenuScreen!
Thank you so much for what you've done.
KV Code:
MDScreen:
MDNavigationLayout:
ScreenManager:
HomeScreen:
id: home
MDBoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'Navigation Drawer'
left_action_items: [['menu', lambda x: nav_drawer.set_state('toggle')]]
Widget:
MenuScreen:
id: menu
MDNavigationDrawer:
id: nav_drawer
<HomeScreen>:
name: 'home_screen'
MDLabel:
id: label
text: 'Home Screen'
halign: 'center'
MDRaisedButton:
text: 'Menu Screen'
pos_hint: {'center_x':0.5, 'center_y':0.4}
on_press: root.manager.current = 'menu_screen'
<MenuScreen>:
name: 'menu_screen'
MDLabel:
text: f'Menu Screen from Home Screen'
halign: 'center'
MDRaisedButton:
text: 'Home Screen'
pos_hint: {'center_x':0.5, 'center_y':0.4}
on_press: root.manager.current = 'home_screen'
HomeScreen:
MenuScreen:
The NavigationDrawer is a widget that is designed to work across many screens. It will show up across multiple screens. If we take a closer look at you.kv file we can figure out why. Notice how the MDNavigationLayout is above your Screenmanager. This means that all the Screens that come under your ScreenManager(A widget that basically manages your screen and allows you to switch between them easily) will be shown in the NavigationLayout.
There isn't really a way to exclude a Screen from showing the NavigationDrawer as the widget was designed to show itself across all screens that are under it
As can be seen here in the docs examples:
https://kivymd.readthedocs.io/en/0.104.1/components/navigation-drawer/[kivymd docs]1
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
KV = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: "Screen 1"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 1"
OneLineListItem:
text: "Screen 2"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 2"
MDScreen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
MDScreen:
name: "scr 1"
MDLabel:
text: "Screen 1"
halign: "center"
MDScreen:
name: "scr 2"
MDLabel:
text: "Screen 2"
halign: "center"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(MDBoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
https://kivymd.readthedocs.io/en/latest/components/navigationdrawer/#switching-screens-in-the-screenmanager-and-using-the-common-mdtoolbar
When going from the first screen to the second screen, I want to pass a variable as an argument so that kivyMD can update the second screen from text stored in an excel file. The following is a skeleton of my app's functionality:
The user reaches Screen 1 thru the navigation drawer in KivyMD, screen 1 presents the user with two options on two small clickable MDCards:
"Change text to 1"
"Change text to 2"
After clicking on one of these, the app switches to screen 2 with a single big MDCard, the text on this MDCard should change to reflect the option the user chose.
However, kivy is pulling the text that is to be displayed on the big MDCard from an excel file.
The variable that I want to pass from screen 1 to screen 2 is simply a number (1 or 2) that will tell kivy which row in the excel file it should pull the text from
If the user clicks "Change text to 1" then the first screen should pass "1" as the argument row_x to the function def change_text() (see screen 2 .py) so that the text in row 1 of excel can be displayed on the second screen. How can I achieve this?
I have 4 files in total; 3 are .py files (one for the main app, one for screen 1, and one for screen 2), and the excel file
NOTE: in the code below, Screen 1 & 2 are called Element 1 & 2 respectfully
Main.py:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from element_1 import element_1_screen
from element_2 import element_2_screen
MainNavigation = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: 'Go to Element 1'
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "go_to_element_1_screen"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "words_nav_item"
element_1_screen:
name: "go_to_element_1_screen"
element_2_screen:
name: "go_to_element_2_screen"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class mainApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Red"
return Builder.load_string(MainNavigation)
mainApp().run()
Screen 1 / Element 1
from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
element_1_contents = '''
<element_1_screen>:
MDGridLayout:
rows: 2
size: root.width, root.height
pos_hint: {"center_x": .8, "center_y": .2}
spacing: 40
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.manager.current = "go_to_element_2_screen"
MDLabel:
id: LabelTextID
text: "Change Text to 1"
halign: 'center'
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "120dp"
ripple_behavior: True
on_release:
root.manager.current = "go_to_element_2_screen"
MDLabel:
id: LabelTextID
text: "Change Text to 2"
halign: 'center'
'''
class element_1_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Builder.load_string(element_1_contents)
Screen 2 / Element 2
from kivy.lang import Builder
from kivymd.uix.screen import MDScreen
import openpyxl
element_2_contents = '''
<element_2_screen>:
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "360dp"
pos_hint: {"center_x": .5, "center_y": .5}
ripple_behavior: True
focus_behavior: True
on_release: root.manager.current = "go_to_element_1_screen"
MDLabel:
id: TextID
text: "NOTHING HAS CHANGED"
halign: 'center'
MDLabel:
text: "(Click here to return)"
halign: 'center'
'''
class element_2_screen(MDScreen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
path = "data.xlsx"
self.wb_obj = openpyxl.load_workbook(path)
self.sheet_obj = self.wb_obj.active
Builder.load_string(element_2_contents)
def change_text(self, row_x=0):
row_number = self.sheet_obj.cell(row_x, column=1)
self.ids.TextID.text = str(row_number.value)
And the excel file only has two entries in Column A:
Row 1: You have chosen 1
Row 2: You have chosen 2
I found the answer and now it works flawlessly. Someone over on Reddit (u/Username_RANDINT) helped me, this is what they said:
The ScreenManager has a get_screen() method. You could use it to get
the instance of the second screen and call the change_text() method on
that. In the same place where you switch screens, add another line:
on_release:
root.manager.current = "go_to_element_2_screen"
root.manager.get_screen("go_to_element_2_screen").change_text(1)
Then the same for the other card, just pass in 2 instead of 1.
I am trying to navigate through my app, I can get the screens to change thru the navigation drawer, however when I am within one of the screens away from the navigation drawer, I don't know how to change to another screen.
The app below allows the user to click on the "Go to Element 1" button in the navigation drawer which will take them to "Element 1 Screen", this screen is away from the navigation drawer and it has a clickable MDCard.
When the user clicks on that MDCard, it should take them to "Element 2 Screen" but that's where I am stuck. I don't know how to get kivy to change the screen here. How can I get the MdCard in element 1 to switch the screen to element 2?
The app is arranged into 3 .py files:
1 for the main app
1 for element_1
1 for element_2
The flow of the code below goes like this:
Main App --> Element 1 button --> Element 1 screen --> Element 2 screen
PS: The code below is only for training purposes, it represents the general layout of my actual app.
Main App Code:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
from element_1 import element_1_screen
from element_2 import element_2_screen
MainNavigation = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: 'Go to Element 1'
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "go_to_element_1_screen"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "words_nav_item"
element_1_screen:
name: "go_to_element_1_screen"
element_2_screen:
name: "go_to_element_2_screen"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class mainApp(MDApp):
def build(self):
self.theme_cls.primary_palette = "Red"
return Builder.load_string(MainNavigation)
mainApp().run()
Element 1 Screen:
from kivy.lang import Builder
from kivymd.uix.screen import Screen
element_1_contents = '''
MDScreen:
MDCard:
orientation: 'vertical'
size_hint: None, None
size: "360dp", "360dp"
pos_hint: {"center_x": .5, "center_y": .5}
ripple_behavior: True
focus_behavior: True
on_press: root.screen_manager.current = "go_to_element_2_screen"
MDLabel:
text: "Welcome to Element 1"
halign: 'center'
MDLabel:
text: "Click here to go to element 2"
halign: 'center'
'''
class element_1_screen(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.directory = Builder.load_string(element_1_contents)
self.add_widget(self.directory)
Element 2 Screen:
# Element 2 has the same code as element 1 except that for these two lines
MDLabel:
text: "Welcome to Element 2"
MDLabel:
text: "Click here to go to element 3"
One hack to get it working is to change:
root.screen_manager.current =
to:
root.parent.manager.current =
in the element_1.py and element_2.py files.
I am very new with kivymd and I got the layout to the main menu, but I could not get how to create a navigation menu to redirect the user to Login page.
The main idea is to let the user click at the menu button to consult the different categories, but I have not found how to include these categories at the menu icon, can anyone help me on it?
from kivy.lang import Builder
from kivymd.app import MDApp
KV = """
BoxLayout:
# Will always be at the bottom of the screen.
MDBottomAppBar:
MDToolbar:
title: "Title"
icon: "cart-plus"
type: "bottom"
left_action_items: [["menu", lambda x: x]]
mode: "end"
"""
class Shop(MDApp):
def build(self):
return Builder.load_string(KV)
Shop().run()
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
KV = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: "Screen 1"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 1"
OneLineListItem:
text: "Screen 2"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 2"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
NavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "scr 1"
MDLabel:
text: "Screen 1"
halign: "center"
Screen:
name: "scr 2"
MDLabel:
text: "Screen 2"
halign: "center"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ObjectProperty
from kivymd.app import MDApp
KV = '''
<ContentNavigationDrawer>:
ScrollView:
MDList:
OneLineListItem:
text: "Screen 1"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 1"
OneLineListItem:
text: "Screen 2"
on_press:
root.nav_drawer.set_state("close")
root.screen_manager.current = "scr 2"
Screen:
MDToolbar:
id: toolbar
pos_hint: {"top": 1}
elevation: 10
title: "MDNavigationDrawer"
left_action_items: [["menu", lambda x: nav_drawer.set_state("open")]]
MDNavigationLayout:
x: toolbar.height
ScreenManager:
id: screen_manager
Screen:
name: "scr 1"
MDLabel:
text: "Screen 1"
halign: "center"
Screen:
name: "scr 2"
MDLabel:
text: "Screen 2"
halign: "center"
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
'''
class ContentNavigationDrawer(BoxLayout):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
TestNavigationDrawer().run()
for more information see the official documentation ok KivyMD
Hope this Helped you!
I want to open the kivy settings inside a screen from the kivymd navigationdrawer. The default settings only open in a new window that completely ignore the color theme of the app. Any suggestion on how to make the settings a child of the screen manager?
Here is the guiApp.py:
from kivy.app import App
import kivymd
from kivymd.theming import ThemeManager
from kivy.uix.settings import Settings, SettingsWithSidebar
class guiApp(App):
theme_cls = ThemeManager()
theme_cls.primary_palette = 'BlueGray'
theme_cls.theme_style = 'Light'
def build(self):
self.settings_cls = SettingsWithSidebar
guiApp().run()
and the gui.kv:
NavigationLayout:
MDNavigationDrawer:
NavigationDrawerSubheader:
text: 'Operation Menu'
NavigationDrawerIconButton:
icon: 'information-outline'
text: 'Introduction'
on_release: screen_manager.current = 'screen_info'
NavigationDrawerIconButton:
icon: 'settings'
text: 'Settings'
on_release: screen_manager.current = 'screen_settings'
on_release: app.open_settings()
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: 'My GUI'
md_bg_color: app.theme_cls.primary_color
left_action_items: [['menu', lambda x: root.toggle_nav_drawer()]]
ScreenManager:
id: screen_manager
Screen:
name: 'screen_info'
MDLabel:
text: 'This page will be used for information on how to use the App '
theme_text_color : 'Hint'
valign: 'middle'
halign: 'center'
Screen:
name: 'screen_settings'
BoxLayout:
Instead of using app.open_settings(), you can use app.create_settings(), to get the setting widget, that you can directly attach to a Screen.
Add the on_start method to guiApp class
[...]
class guiApp(App):
[...]
def on_start(self):
s = self.create_settings()
self.root.ids.settings_content.add_widget(s)
And give an id to the BoxLayout of your screen_settings
[...]
Screen:
name: 'screen_settings'
BoxLayout:
id: settings_content