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

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.

Related

When creating a widget with a button, how to specify an ID?

So I already make an expanding list when pressing a button, the problem is that I can´t find I way to add an icon in the right part(as the sample one) of the ListItem
Code:
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.core.window import Window
from kivymd.uix.screen import MDScreen
from kivymd.uix.list import OneLineRightIconListItem
from kivymd.uix.list.list import IconRightWidget
Window.size = (300, 500)
navigation_helper = """
<DietScreen>:
Screen:
MDBoxLayout:
orientation: 'vertical'
padding: ("5dp", "65dp" , "5dp", "5dp")
pos_hint: {"top": 1}
MDScrollView:
MDList:
id: box
OneLineRightIconListItem:
id: hola
text: 'testitem'
on_press:
IconRightWidget:
icon: "dots-vertical"
MDRaisedButton:
text: "Nueva Dieta"
md_bg_color: "white"
text_color: "black"
font_size: 16.2
pos_hint: {"right": 1, "bottom": 1}
on_press: root.buttonClicked()
Screen:
MDNavigationLayout:
ScreenManager:
id: scr
DietScreen:
"""
class DietScreen(MDScreen):
def buttonClicked(self):
newButt = OneLineRightIconListItem(text='Button')
self.ids.box.add_widget(newButt)
class DemoApp(MDApp):
def build(self):
screen = Builder.load_string(navigation_helper)
return screen
DemoApp().run()
I tried to specify an id to the ListItem created, and in the same method after addind the ListItem, add an IconRightWidget with an icon determined, but it results in an error.
Replace your:
newButt = OneLineRightIconListItem(text='Button')
With:
newButt = OneLineRightIconListItem(IconRightWidget(icon='dots-vertical'), text="Button")

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

How do I keep KivyMD bottom app toolbar on multiple screens

I am trying to keep the KivyMD toolbar on multiple screens, I have searched on stackoverflow before, and found out I may need to create a class and inherit it. Question: Is this right, if yes I would be very grateful is you could make a sample of the class, and if no I would equally be grateful for your opinion.. Thanks.
Here's my .py file
from kivymd.app import MDApp
from kivy.uix.screenmanager import Screen, ScreenManager, NoTransition
from kivy.core.window import Window
Window.size = (300, 500)
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class TwoScreenApp(MDApp):
def build(self):
self.theme_cls.primary_palette = 'Gray'
self.sm = ScreenManager(transition=NoTransition())
self.sm.add_widget(Screen1(name='screen1'))
self.sm.add_widget(Screen2(name='screen2'))
return self.sm
def change_screen(self, screen):
self.sm.current = screen
"""I am using this self.sm method to link
icon to screen2 until I find a better way"""
TwoScreenApp().run()
and Here's my .kv file
ScreenManager:
Screen1:
Screen2:
<Screen1>
name: 'screen1'
MDBoxLayout:
md_bg_color: (240/255, 240/255, 240/255, 1)
orientation: 'vertical'
MDLabel:
halign: 'center'
text:
"""With the production of the Model T automobile,
Henry Ford had an unforeseen and tremendous
impact on American life. He became regarded
as an apt symbol of the transition from an
agricultural to an industrial America."""
MDBottomAppBar:
MDToolbar:
icon: "account-circle"
type: "bottom"
left_action_items: [["arrow-right", lambda x: app.change_screen("screen2")], ["alpha-x-circle", lambda x: x]]
right_action_items: [["alpha-y-circle", lambda x: x], ["arrow-left", lambda x: app.change_screen("screen1")]]
elevation: 10
<Screen2>
name: 'screen2'
md_bg_color: (240/255, 240/255, 240/255, 1)
MDBoxLayout:
md_bg_color: (255/255, 255/255, 255/255, 1)
orientation: 'vertical'
MDLabel:
halign: 'center'
text:
"""The development of mass-production techniques, which
enabled the company eventually to turn out a Model T
every 24 seconds; the frequent reductions in the price
of the car made possible by economies of scale; and
the payment of a living wage that raised workers
above subsistence and made them potential customers
for, among other things, automobiles—these innovations
changed the very structure of society."""
MDBottomAppBar:
MDToolbar:
title: "Title"
icon: "account-circle"
type: "bottom"
left_action_items: [["menu", lambda x: app.change_screen("screen1")]]

How to Update KivyMD Label in the first screen with on_enter function?

Give below is the kivyMD screen manager program from https://github.com/attreyabhatt/KivyMD-Basics/blob/master/13%20-%20Switching%20Screens/main.py
and I have added it to the official navigation drawer program https://kivymd.readthedocs.io/en/latest/components/navigation-drawer/index.html of KivyMD.
This program works perfectly, but I want to change the text in the MenuScreen with the on_enter function.
I have commented the def on_enter inside class MenuScreen.
If we run it uncommented shows this error:
Traceback (most recent call last):
File "kivy\properties.pyx", line 861, in kivy.properties.ObservableDict.__getattr__
KeyError: 'label_id'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "C:/Users/Administrator/PycharmProjects/kivy_prj1/kivy_mainmd/nav_screen.py", line 170, in <module>
sm.add_widget(MenuScreen(name='menu'))
File "C:\Users\Administrator\pyenv\lt_kivyvirt\lib\site-packages\kivy\uix\screenmanager.py", line 997, in add_widget
self.current = screen.name
File "kivy\properties.pyx", line 498, in kivy.properties.Property.__set__
File "kivy\properties.pyx", line 545, in kivy.properties.Property.set
File "kivy\properties.pyx", line 600, in kivy.properties.Property.dispatch
File "kivy\_event.pyx", line 1248, in kivy._event.EventObservers.dispatch
File "kivy\_event.pyx", line 1154, in kivy._event.EventObservers._dispatch
File "C:\Users\Administrator\pyenv\lt_kivyvirt\lib\site-packages\kivy\uix\screenmanager.py", line 1062, in on_current
screen.dispatch('on_enter')
File "kivy\_event.pyx", line 709, in kivy._event.EventDispatcher.dispatch
File "C:/Users/Administrator/PycharmProjects/kivy_prj1/kivy_mainmd/nav_screen.py", line 156, in on_enter
self.ids.label_id.text = "Label text Updated"
File "kivy\properties.pyx", line 864, in kivy.properties.ObservableDict.__getattr__
AttributeError: 'super' object has no attribute '__getattr__'
'label_id' is the id of my label that is to be changed.
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty, ListProperty
from kivymd.app import MDApp
from kivymd.theming import ThemableBehavior
from kivymd.uix.list import OneLineIconListItem, MDList
from kivy.uix.screenmanager import ScreenManager, Screen
KV = '''
# Menu item in the DrawerList list.
<ItemDrawer>:
theme_text_color: "Custom"
on_release: self.parent.set_color_item(self)
IconLeftWidget:
id: icon
icon: root.icon
theme_text_color: "Custom"
text_color: root.text_color
<ContentNavigationDrawer>:
orientation: "vertical"
padding: "8dp"
spacing: "8dp"
AnchorLayout:
anchor_x: "left"
size_hint_y: None
height: avatar.height
Image:
id: avatar
size_hint: None, None
size: "56dp", "56dp"
source: "data/logo/kivy-icon-256.png"
MDLabel:
text: "KivyMD library"
font_style: "Button"
size_hint_y: None
height: self.texture_size[1]
MDLabel:
text: "kivydevelopment#gmail.com"
font_style: "Caption"
size_hint_y: None
height: self.texture_size[1]
ScrollView:
DrawerList:
id: md_list
Screen:
ScreenManager:
MenuScreen:
ProfileScreen:
UploadScreen:
NavigationLayout:
ScreenManager:
Screen:
BoxLayout:
orientation: 'vertical'
MDToolbar:
title: "Navigation Drawer"
elevation: 10
left_action_items: [['menu', lambda x: nav_drawer.toggle_nav_drawer()]]
Widget:
MDNavigationDrawer:
id: nav_drawer
ContentNavigationDrawer:
id: content_drawer
<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'
MDLabel:
id:label_id
text: 'update this at on enter'
pos_hint: {'center_x':0.5,'center_y':0.4}
halign: 'center'
<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'
<UploadScreen>:
name: 'upload'
MDLabel:
text: 'Upload'
halign: 'center'
MDRectangleFlatButton:
text: 'Back'
pos_hint: {'center_x':0.5,'center_y':0.1}
on_press: root.manager.current = 'menu'
'''
class ContentNavigationDrawer(BoxLayout):
pass
class ItemDrawer(OneLineIconListItem):
icon = StringProperty()
text_color = ListProperty((0, 0, 0, 1))
"""Instead of just giving MDList in kv file give DrawerList for adding the ThemableBehavior with it and created color
changing fn """
class DrawerList(ThemableBehavior, MDList):
def set_color_item(self, instance_item):
"""Called when tap on a menu item."""
# Set the color of the icon and text for the menu item.
for item in self.children:
if item.text_color == self.theme_cls.primary_color:
item.text_color = self.theme_cls.text_color
break
instance_item.text_color = self.theme_cls.primary_color
# print(f"inst:{instance_item.text_color}", f"thm_txt{self.theme_cls.text_color}",
# f"thm_prm{self.theme_cls.primary_color}")
class MenuScreen(Screen):
pass
# def on_enter(self, *args):
# self.ids.label_id.text = "Label text Updated"
class ProfileScreen(Screen):
pass
class UploadScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(ProfileScreen(name='profile'))
sm.add_widget(UploadScreen(name='upload'))
class TestNavigationDrawer(MDApp):
def build(self):
return Builder.load_string(KV)
def on_start(self):
icons_item = {
"folder": "My files",
"account-multiple": "Shared with me",
"star": "Starred",
"history": "Recent",
"checkbox-marked": "Shared with me",
"upload": "Upload",
}
for icon_name in icons_item.keys():
self.root.ids.content_drawer.ids.md_list.add_widget(
ItemDrawer(icon=icon_name, text=icons_item[icon_name])
)
TestNavigationDrawer().run()
So anybody knows how to change the first screens label or anything from on_enter in KivyMD?
Note: Uncomment the # def on_enter(self, *args): # self.ids.label_id.text = "Label text Updated" this.
It will show the error.
I am having the same problem.
It would seems like the error starts here : sm.add_widget(MenuScreen(name='menu'))
while adding the class MenuScreen to the screen manager the on_enter is evoked.
This would usually solved by adding the Builder at the top, but for KivyMD i don't think it is not possible.
I haven't exactly find an answer, but found a way around(have to run app somehow).
Step 1: Remove these lines:
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(ProfileScreen(name='profile'))
sm.add_widget(UploadScreen(name='upload'))
edit the kv-program like this:
Screen:
ScreenManager:
MenuScreen:
name:"menu"
ProfileScreen:
name:"profile"
UploadScreen:
name:"upload"
You can use self.parent.current = "screen_name" inside any Screen manager class in the python program instead of sm.current = "screen_name.
And if you needed to change screen inside of a layout which is inside of other and so.. instead of using self.parent.parent... you can use MDApp.get_running_app().root.ids.screen_manger.current = "screen_name"
Step2: To avoid on_enter problem for the first screen create a logo screen that show up only at the start of application like this:
Add this in python program
class HomeScreen(Screen):
pass
and edit Kv-program like this:
Screen:
ScreenManager:
HomeScreen:
name:"home"
MenuScreen:
name:"menu"
ProfileScreen:
name:"profile"
UploadScreen:
name:"upload"
and add this to the kv-program
<HomeScreen>:
BoxLayout:
Button:
size:self.size
on_press:
root.manager.current = "menu"
You can add image or animation on the button.

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

Resources