I am a beginner in kivymd. I was trying to create bottom-navigation in kivymd. After run the code the it showing active item of Bottom Navigation is 'Home'(name='screen_1). Now I want second item as default active item -> 'Features'(name='screen_2'). This is sample code.
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
Window.size=(350,593)
KV_string = """
Screen:
BoxLayout:
orientation:'vertical'
MDToolbar:
title:'Demo Application'
right_action_items : [["dots-vertical"]]
left_action_items : [["menu", lambda x: app.menu_toggle()]]
right_action_items : [["dots-vertical", lambda x: app.option_fun()]]
elevation:10
MDLabel:
text:'Welcome to good GUI'
halign:'center'
MDBottomNavigation:
MDBottomNavigationItem:
name: 'screen_1'
text: 'Home'
icon: 'home-outline'
on_tab_press: app.Bottom_nav_fun()
MDLabel:
text: 'Home page'
halign: 'center'
MDBottomNavigationItem:
name: 'screen_2'
text: 'Features'
icon: 'feature-search-outline'
MDLabel:
text: 'Features of this app'
halign: 'center'
MDBottomNavigationItem:
name: 'screen_3'
text: 'Developers'
icon: 'account-supervisor'
MDLabel:
text: 'Developers details'
halign: 'center'
"""
class MyApp(MDApp):
def build(self):
self.theme_cls.primary_palette="Blue"
self.theme_cls.theme_style="Light"
self.theme_cls.primary_hue="A700"
self.screen = Builder.load_string(KV_string)
return self.screen
def menu_toggle(self):
print("Menu toggle Working")
def option_fun(self):
print("Option method Working")
def Bottom_nav_fun(self):
print("Bottom nav home")
if __name__=='__main__':
MyApp().run()
Is there any way to implement this?
Use method switch_tab which takes as argument the name of the tab you want to switch to - https://kivymd.readthedocs.io/en/latest/components/bottomnavigation/#how-to-automatically-switch-a-tab
Related
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 M trying to add screen inside the screen manager but giving error of assertion when assigning navigation drawer in a screen manager.
This is the Error i am facing :
self._apply_rule(
File "/home/hp/.local/lib/python3.8/site-packages/kivy/lang/builder.py", line 559, in _apply_rule
assert(rule not in self.rulectx)
AssertionError
This is my Code:
from kivymd.app import MDApp
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
screen_helper = """
ScreenManager:
MenuScreen:
ProfileScreen:
ContentNavigationDrawer:
<MenuScreen>:
name: 'menu'
MDRectangleFlatButton:
text: 'Profile'
pos_hint: {'center_x':0.5,'center_y':0.6}
on_press: root.manager.current = 'profile'
MDRectangleFlatButton:
text: 'Upload'
pos_hint: {'center_x':0.5,'center_y':0.5}
on_press: root.manager.current = 'upload'
<ProfileScreen>:
name: 'profile'
MDLabel:
text: 'Profile'
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x':0.5,'center_y':0.1}
on_press: root.manager.current = 'menu'
<ContentNavigationDrawer>:
name : 'upload'
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 MenuScreen(Screen):
pass
class ProfileScreen(Screen):
pass
class ContentNavigationDrawer(Screen):
screen_manager = ObjectProperty()
nav_drawer = ObjectProperty()
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(ProfileScreen(name='profile'))
sm.add_widget(ContentNavigationDrawer(name='upload'))
class DemoApp(MDApp):
def build(self):
screen = Builder.load_string(screen_helper)
return screen
DemoApp().run()
I believe the problem is that your rule in screen_helper:
<ContentNavigationDrawer>:
includes:
ContentNavigationDrawer:
screen_manager: screen_manager
nav_drawer: nav_drawer
That rule combination tells kivy that whenever it creates a ContentNavigationDrawer it should create another ContentNavigationDrawer as a child of the first. Then it would try to apply the original <ContentNavigationDrawer>: again, creating a third ContentNavigationDrawer, applying the rule again to create a fourth, and so on. This would create an infinite loop. Kivy uses the assertion to avoid such loops. So, you just cannot use a rule that would create such a loop.
I was trying to use a new feature in KivyMD, the MDNavigationRail and wanted to give the icons in it a function. The goal is that the user could change to the desired screen by pressing the icon that represents it. I gave the icon an on_press. But something goes wrong, I get an error; ValueError: MDNavigationRail.state is set to an invalid option 'down'. Must be one of: ['close', 'open']. The rail should be open or closed I guess, isn't it possible to give it a function? Furthermore, I would want to know if it is possible to not break the text. If anyone could help me out, it would be very nice!
My .py file
from kivy.uix.screenmanager import ScreenManager
from kivymd.app import MDApp
from kivymd.uix.screen import MDScreen
class Screen1(MDScreen):
def screen2(self):
self.manager.current = 'screen2'
class Screen2(MDScreen):
def screen1(self):
self.manager.current = 'screen1'
def rail_open(self):
if self.ids.rail.state == "open":
self.ids.rail.state = "close"
else:
self.ids.rail.state = "open"
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MyScreenManager, self).__init__(**kwargs)
class Test(MDApp):
def build(self):
return MyScreenManager()
Test().run()
My kv file
<MyScreenManager>:
Screen1:
id: screen1
name: 'screen1'
Screen2:
id: screen2
name: 'screen2'
<Screen1>:
id: screen1
MDFloatLayout:
MDRectangleFlatButton:
text: "Change to screen 2"
on_press: root.screen2()
pos_hint: {'center_x':0.5, 'center_y':0.5}
<Screen2>:
id: screen2
MDBoxLayout:
orientation: "vertical"
MDToolbar:
left_action_items: [["menu", lambda x: root.rail_open()]]
MDBoxLayout:
MDNavigationRail:
id: rail
elevation: 1
use_resizeable: True
MDNavigationRailItem:
icon: "home"
text: "homepage"
on_press: root.screen1()
MDNavigationRailItem:
icon: ""
text: ""
MDFloatLayout:
MDTextField:
id: field1
hint_text: "Enter something:"
size_hint_x: 0.4
pos_hint: {'center_x':0.25,'top':0.8}
It was a bug. Already fixed - https://github.com/kivymd/KivyMD/commit/8a31b0f3ccad9c2d9ad35d80953f7396f2dc78f2
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