How do I use the same widget within kv file - kivy

I would like to define the form of a widget (in this case a spinner) just once in the kv file, but use it from multiple screens. Different input screens (menu1, etc) are called from other screens, many of them have spinners that I want to look identical. In this case I am using the simple spinner "color_spin."
I open menu1 from another window and this seems to work:
<menu1>:
name: "menu1"
GridLayout:
cols:1
Label:
text: "Properties"
size_hint: 0.6,0.2
pos_hint: {"x":0.2,"top":1}
Spinner:
id: color
size_hint_max_y: 50
text: 'Color'
values: 'White','Purple','Blue','Green','Yellow','Orange','Red','Black','Grey'
# Callback
on_text: [do my thing]
But I would like to have only 1 "color_spin" called from many different menus, so I would like to do something like this:
<menu1>:
name: "menu1"
GridLayout:
cols:1
Label:
text: "Properties"
size_hint: 0.6,0.2
pos_hint: {"x":0.2,"top":1}
Spinner:
[use spinner "color_spin", but how?]
<color_spin>:
Spinner:
id: color
size_hint_max_y: 50
text: 'Color'
values: 'White','Purple','Blue','Green','Yellow','Orange','Red','Black','Grey'
# Callback
on_text: [do my thing]
I am very new to kivy so I appreciate any input you can give.

First create your ColorSpinner:
<ColorSpinner#Spinner>:
size_hint_max_y: 50
text: 'Color'
values: 'White','Purple','Blue','Green','Yellow','Orange','Red','Black','Grey'
and then add it wherever you like:
<menu1>:
name: "menu1"
GridLayout:
cols:1
Label:
text: "Properties"
size_hint: 0.6,0.2
pos_hint: {"x":0.2,"top":1}
ColorSpinner:
id: menu1_color
# Callback
on_text: [do my thing]
<color_spin>:
ColorSpinner:
id: color
# Callback
on_text: [do my thing]

Related

How to disable a widget when a button from a different class is pressed?

Im making a Button which disables a TopAppBar menu form my user interface, the problem is that the Button is on one class an the TopAppBar in another, because one belongs to the screen and the other is shared between multiple screens.
code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.screen import MDScreen
Window.size = (300, 500)
navigation_helper = """
<UserScreen>
name: 'user_screen'
MDBoxLayout:
orientation: "vertical"
pos_hint: {"top": 1}
adaptive_height: True
padding: (0, "80dp" , 0, 0)
FloatLayout:
MDRaisedButton:
id: save_btn_user
text: "Guardar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"left":1, "bottom": 1}
size_hint: (.2,.2)
disabled: True
on_press: root.save_btn_press()
MDRaisedButton:
id: edit_btn_user
text: "Editar"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"right":1, "bottom": 1}
size_hint: (.2,.2)
disabled: False
on_press:
root.disable_top_bar()
<MainScreen>:
name: "main_screen"
BoxLayout:
orientation: 'vertical'
MDTopAppBar:
id: title_bar
title: 'Dietas'
md_bg_color: 0.08,0.07,0.45
specific_text_color: 1,1,1
left_action_items: [["menu", lambda x: nav_drawer.set_state('toggle')]]
Widget:
MDNavigationLayout:
ScreenManager:
id: scr
UserScreen:
MDNavigationDrawer:
id: nav_drawer
BoxLayout:
orientation: 'vertical'
spacing: '8dp'
ScrollView:
MDList:
OneLineIconListItem:
text: 'Usuario'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
IconLeftWidgetWithoutTouch:
icon: 'account'
on_press:
scr.current= 'user_screen'
title_bar.title = "Usuario"
nav_drawer.set_state('close')
MainScreen:
"""
class UserScreen(MDScreen):
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
class MainScreen(MDScreen):
def disable_top_barr(self):
self.ids.title_bar.disabled = True
print("testmsg")
class DemoApp(MDApp):
def build(self):
self.theme_cls.theme_style = ("Dark")
screen = Builder.load_string(navigation_helper)
return screen
DemoApp().run()
As you may have seen, I tried creating two method, one in the TopAppBar class disabling it and printing a test message(that its shown) and the other in the Button class calling that function.
With the following approach
def disable_top_bar(self):
self.a=MainScreen()
self.a.disable_top_barr()
you will see no change in your GUI because each time you call that method, it will create a new instance of MainScreen (which is not related to your GUI) and call the asked method on it.
In order to access that specific instance you can use the method get_running_app of MDApp as follows,
def disable_top_bar(self):
# First access the running app instance.
app = MDApp.get_running_app()
# Access the target screen that you set as the root widget.
main_screen = app.root
# Call the method.
main_screen.disable_top_barr()
As a note avoid using predefined names like a, b, g etc. when working with KivyMD widgets.

How to get text from a KivyMD text field?

I am trying to get text from a text field, but I can't seem to get it right. I want to get the text from the MDTextField in the LoginScreen. This is my .kv file:
ScreenManager:
LoginScreen:
VaultScreen:
<LoginScreen>:
id: login
name: 'login'
MDTextField:
id: master_password
hint_text: "Master Password"
size_hint_x: None
width: 200
font_size: 18
pos_hint: {"center_x": 0.5, "center_y": 0.5}
password: True
MDRectangleFlatButton:
text: "Unlock"
font_size: 12
pos_hint: {"center_x": 0.5, "center_y": 0.4}
on_release: app.verify()
<VaultScreen>:
name: 'vault'
MDLabel:
text: 'Vault'
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x': 0.5, 'center_y': 0.1}
on_press: root.manager.current = 'login'
And this is my .py file:
from kivymd.app import MDApp
from kivy.lang.builder import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
class ScMn(ScreenManager):
pass
class LoginScreen(Screen):
pass
class VaultScreen(Screen):
pass
class App(MDApp):
def build(self):
screen = Builder.load_file('passr.kv')
return screen
def verify(self):
# this is where I want to get the text
master = self.root.ids.login.master_password.text
pass
App().run()
When I try getting the contents of self.root.ids I get an empty dictionary.
Your code:
self.root.ids.login.master_password.text
is trying to access the ids of self.root, but self.root is the ScreenManager, and it has no ids. The ids defined in kv are stored in the ids dictionary of the widget that is the the root of the rule that contains the id definition. Since you define some ids in the <LoginScreen> rule, the ids will be in the LoginScreen object. So you must get a reference to the LoginScreen, and you can do that by using the get_screen() method, like this:
self.root.get_screen('login').ids.master_password.text

kivy menu bar with screen manager and new menu on separate screens

This answer shows how to make a menu that switches between screens. Kivy with Menubar
But now that I am on screen two, I need to put two buttons on that screen
An Ok and a Cancel button.
OK takes you to a screen that says the information was processed,
Cancel takes me back to screen one per the example.
I have tried putting in a second screenmanager but it doesn't like that. How is this handled?
<Display>:
BoxLayout:
orientation: "vertical"
BoxLayout:
size_hint: 1, None
height: '48dp'
Button:
text: 'One'
on_release:
sm.current = 'screen_one' #<<<<<<<<<<<<<<<<
sm.transition.direction = "left"
Button:
text: 'Two'
on_release:
sm.current = 'screen_two' #<<<<<<<<<<<<<<<<
sm.transition.direction = "right"
Button:
text: 'Three'
on_release:
sm.current = 'screen_three' #<<<<<<<<<<<<<<<<
sm.transition.direction = "right"
ScreenManager:
id: sm
Screen_One:
Screen_Two:
Screen_Three:
<Screen_One>:
name: 'screen_one' #<<<<<<<<<<<<<<<<
Button:
text: 'One'
<Screen_Two>:
name: 'screen_two' #<<<<<<<<<<<<<<<<
BoxLayout:
size_hint: 1, None
height: '48dp'
Button:
text: 'Cancel'
on_release:
sm1.current = 'screen_one' #<<<<<<<<<<<<<<<<
sm1.transition.direction = "left"
Button:
text: 'Ok'
on_release:
sm1.current = 'screen_three' #<<<<<<<<<<<<<<<<
sm1.transition.direction = "right"
ScreenManager:
id: sm1
Screen_One:
Screen_Two:
Screen_Three:
<Screen_Three>:
name: 'screen_three' #<<<<<<<<<<<<<<<<
Button:
text: 'Three'
Each instance of Screen has a manager property, which is a reference to the ScreenManager that is managing it. So, in your kv, you can replace sm1 with root.manager like this:
root.manager.current = 'screen_three' #<<<<<<<<<<<<<<<<

Kivy how to create dropdownlist within a GridLayout

I have 2 files one of the py and the other kv, well as the title says, I do not know how to add a dropdown.
---> main.py
from kivy.app import App
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
---> layouts.kv
<MyGridLayout>:
rows: 2
id: main_win
Dropdown:
id: dropdown
Button:
id: btn1
text: 'option 1'
Button:
id: btn2
text: 'option 2'
BoxLayout:
BoxLayout:
when compiling, it generates error for that part. How is the correct way to make the call for dropdown-list?
Question 3
call the Button id: btn2, how would I do it? - I mean:class
MyGridLayout(GridLayout):
Solution 3
Python script
Add import statements: from kivy.uix.dropdown import DropDown; from kivy.properties import ObjectProperty
Implement class CustomDropDown(DropDown):
In class MyGridLayout, declare an ObjectProperty at class level (e.g. dropdown = ObjectProperty(None); implement a method dropdown_open(self):; instantiate the class CustomDropDown and assigned it to self.dropdown, etc. Please refer to snippets for detail.
kv file
Remove self.dismiss()
Replace dynamic class, <CustomDropdown#DropDown>: with class rule, <CustomDropDown>:
Replace rows: 2 with rows: 3
Replace Factory.CustomDropdown().open(self) with root.dropdown_open(self)
Example 3
main.py - Snippets
from kivy.uix.dropdown import DropDown
from kivy.properties import ObjectProperty
class CustomDropDown(DropDown):
pass
class MyGridLayout(GridLayout):
dropdown = ObjectProperty(None)
def dropdown_open(self, instance):
self.dropdown = CustomDropDown()
self.dropdown.open(instance)
print(self.dropdown.ids.btn2.text)
layouts.kv - Snippets
<CustomDropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
...
<MyGridLayout>:
rows: 3
id: main_win
Button:
id: btn
text: 'Press'
size_hint_y: None
height: '48dp'
on_release:
root.dropdown_open(self)
Output 3
Question 2
when I added these commands, it does not work: BoxLayout: orientation:
'vertical' size_hint: (.9,.9) BoxLayout: orientation: 'vertical'
size_hint_y: 0.5
Note
You might need to increase the rows from 2 to 3.
Example #2
Added the config for the two BoxLayout
For illustration, added colour to the canvas for both BoxLayout
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
orientation: 'vertical'
size_hint: (.9,.9)
canvas.before:
Color:
rgba: 1, 0, 0, 1 # red
Rectangle:
pos: self.pos
size: self.size
BoxLayout:
orientation: 'vertical'
size_hint_y: 0.5
canvas.before:
Color:
rgba: 0, 0, 1, 1 # blue
Rectangle:
pos: self.pos
size: self.size
Output #2
Solution #1
DropDown widget is like Popup widget i.e. they are special widget.
Popup / DropDown is a special widget. Don’t try to add it as a child to any other
widget. If you do, Popup / DropDown will be handled like an ordinary widget and
won’t be created hidden in the background.
Therefore, create a dynamic class with inheritance of DropDown widget, and use Factory to instantiate the class.
Example
main.py
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
class MyGridLayout(GridLayout):
pass
class LayoutsApp(App):
def build(self):
return MyGridLayout()
if __name__ == '__main__':
LayoutsApp().run()
layouts.kv
#:kivy 1.10.1
#:import Factory kivy.factory.Factory
<CustomDropdown#DropDown>:
id: dropdown
on_select:
app.root.ids.btn.text = '{}'.format(args[1])
self.dismiss()
Button:
id: btn1
text: 'option 1'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn1.text)
Button:
id: btn2
text: 'option 2'
size_hint_y: None
height: '48dp'
on_release:
dropdown.select(btn2.text)
<MyGridLayout>:
rows: 2
id: main_win
Button:
id: btn
text: 'Press'
on_release: Factory.CustomDropdown().open(self)
size_hint_y: None
height: '48dp'
BoxLayout:
BoxLayout:
Output

How to display ContextMenuTextItem over other components in Kivy

now i'm creating a GUI for application by kivy, and i have a problem as following:
When i click onto Title "Job", there is a menu, the problem is the menu is overlapped by other components (textinput...). How could i display this menu over textinput ? Please see picture for more details.
Problem
#: import main todo
#: import ListAdapter kivy.adapters.listadapter.ListAdapter
#: import ListItemButton kivy.uix.listview.ListItemButton
Root:
task_input: task_input_view
task_list: tasks_list_view
text_input: text_input
BoxLayout:
orientation: 'vertical'
id: layout
AppMenu:
id: app_menu
top: root.height
cancel_handler_widget: layout
AppMenuTextItem:
text: "Job"
ContextMenu:
ContextMenuTextItem:
text: "Open"
on_release: root.show_load()
ContextMenuTextItem:
text: "Save"
on_release: root.show_save()
ContextMenuTextItem:
text: "SaveAs"
ContextMenuDivider:
ContextMenuTextItem:
text: "Exec"
BoxLayout:
size_hint_y: None
height: "40dp"
TextInput:
id: task_input_view
size_hint_x: 70
Button:
text: "Add"
size_hint_x: 15
on_press: root.add_task()
Button:
text: "Del"
size_hint_x: 15
on_press: root.del_task()
ListView:
id: tasks_list_view
adapter:
ListAdapter(data=[], cls=main.TaskButton)
TextInput:
id: text_input
text: ''
How to make this type of menu has more than one solution, but basically you need something that will be displayed on the top of each widget (or the widgets you want in background). This could be achieved in BoxLayout, but it's not efficient. Rather than Box, make it FloatLayout and add the widget that handles your menu(that panel) as the last widget - this way it'll be on top of every widget added before it and menu should behave like desired.

Resources