Kivy - Screen Manager - Accessing attribute in other class - python

Using the Kivy Screen Manager, I create two Screens. Whilst being in screen 1, i want to change a label in screen two. I highlight the problematic area in my code:
my test.ky:
#: import ScreenManager kivy.uix.screenmanager.ScreenManager
#: import Screen kivy.uix.screenmanager.ScreenManager
#: import SettingsScreen screen
ScreenManager:
MenuScreen:
SettingsScreen:
<MenuScreen>:
name: 'MenuScreen'
BoxLayout:
Button:
text: 'Goto nn'
on_press:
root.manager.current = 'SettingsScreen'
root.change_text()
<SettingsScreen>:
name: 'SettingsScreen'
label_id: label_field
BoxLayout:
Label:
id: label_field
text: "to_be_changed"
and my screen.py
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
def change_text(self):
pass
# HERE: something like
# root.SettingsScreen.label_field.text = 'new text'
class SettingsScreen(Screen):
pass
class TestApp(App):
pass
TestApp().run()
Any help is greatly appreciated!
Thanks, Nico

How about this:
When you press the button on MenuScreen, it sets an attribute on itself containing the text you want to put in the SettingsScreen Label. Then the MenuScreen is assigned an id value in the kv file, which is used to reference this attribute. Example:
main.py
class MenuScreen(Screen):
text = StringProperty('')
def change_text(self):
self.text = "The text you want to set"
self.manager.current = "SettingsScreen"
class SettingsScreen(Screen):
label_text = StringProperty('')
kv file
ScreenManager:
id: screen_manager
MenuScreen:
id: menu_screen
name: 'MenuScreen'
manager: screen_manager
SettingsScreen:
name: 'SettingsScreen'
manager: screen_manager
label_text: menu_screen.text
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto nn'
on_press:
root.change_text()
<SettingsScreen>:
BoxLayout:
Label:
text: root.label_text
As you can see, I set the names and id of the screens under ScreenManager itself in the kv file, as this is what I would usually do to make this work.

Related

Python Kivy Overwritting method

I have tried to minimize the example as much as possible but it seems empty and the problem is not exactly the same after.
I think i misunderstand things about class variables and instance variables, but Kivy doesn't help me on this : we create a lot of class but never instantiate them !
Of course I want the "Screenx instance has overwritten question" message appears and I just see "Not overwritten".
calendrier.py
from kivy.properties import StringProperty
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.clock import Clock
class Menu(Screen):
pass
class CalculGenerique(Screen):
question = StringProperty()
def __init__(self, **kwargs):
super(CalculGenerique, self).__init__(**kwargs)
Clock.schedule_once(self.late_init, 0)
def late_init(self, *largs):
self.renew_question()
def renew_question(self):
self.question = "Not overwritten"
class Screen1(CalculGenerique):
def renew_question(self):
self.question = "Screen1 instance has overwritten question"
class Screen2(CalculGenerique):
def renew_question(self):
self.question = "Screen2 instance has overwritten question"
class MyScreenManager(ScreenManager):
pass
class CalendarApp(App):
def build(self):
root_widget = Builder.load_file("calendrier.kv")
return root_widget
if __name__ == "__main__":
app = CalendarApp()
app.run()
calendrier.kv
#:import App kivy.app.App
MyScreenManager:
Menu:
CalculGenerique:
name: 'Screen1'
CalculGenerique:
name: 'Screen2'
<Menu>:
name: 'Menu'
BoxLayout:
orientation: 'vertical'
Button:
text: 'Screen1'
on_press : app.root.current = 'Screen1'
Button:
text: 'Screen2'
on_press : app.root.current = 'Screen2'
Button:
text: 'Quitter l app'
on_press : App.get_running_app().stop()
<CalculGenerique#Screen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Back to Menu'
on_press : app.root.current = 'Menu'
Label:
text: root.question
Your 'kv' rule:
MyScreenManager:
Menu:
CalculGenerique:
name: 'Screen1'
CalculGenerique:
name: 'Screen2'
creates two instances of the CalculGenerique class, but no instances of Screen1 or Screen2. I think you just need to change that rule to:
MyScreenManager:
Menu:
Screen1:
name: 'Screen1'
Screen2:
name: 'Screen2'
The name property of a Screen is just used to identify the Screen, and does not specify the class.

Fail to get kivy widgets by ids

I made this to code to show what I want but fail to do. I get the message: "AttributeError: 'super' object has no attribute 'getattr'" when I try to acces a widget.
Someone please explain how to use widget id:s so that you can acces them from anywhere using python code, i'm sure I'm doing something fundamentally wrong.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager, SlideTransition
from kivy.lang.builder import Builder
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
import random
Builder.load_string('''
<Manager>:
id: screen_manager
SettingScreen:
id: settings
name: 'settings'
manager: 'screen_manager'
MainScreen:
id: mainscreen
name: 'mainscreen'
manager: 'screen_manager'
ThirdScreen:
id: thirdscreen
name: 'thirdscreen'
manager: 'screen_manager'
<SettingScreen>:
BoxLayout:
id: settingbox
orientation: "vertical"
TextInput:
id: getthis
text: "this is the data"
font_size: 40
size_hint: 1,0.1
Button:
text: "NEXT SCREEN"
font_size: 40
size_hint: 1,0.1
on_release:
app.root.current = "mainscreen"
app.root.transition.direction="left"
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Label:
id: changethis
text: "to be changed"
Button:
text: "Push this to make above into the text from TextInput on last screen"
on_release:
app.root.ids.changethis.text = app.root.ids.getthis.text #<<<<<
Button:
text: "Go to third Screen"
on_release:
app.root.current = "thirdscreen"
app.root.transition.direction="left"
<ThirdScreen>
put_label_here: put_label_here
BoxLayout:
orientation: 'horizontal'
Button:
text: 'Make make label'
on_release:
self.parent.parent.makelabel()
BoxLayout:
orientation: 'vertical'
id: put_label_here
''')
class MainScreen(Screen):
def __init__(self, **kwargs):
Screen.__init__(self, **kwargs)
class SettingScreen(Screen):
def __init__(self, **kwargs):
super(SettingScreen, self).__init__(**kwargs)
class ThirdScreen(Screen):
def __init__(self, **kwargs):
super(ThirdScreen, self).__init__(**kwargs)
def makelabel(self): #this should make a Label with text from TextInput on SettingScreen
print('Runs function makelabel')
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
self.put_label_here.add_widget(thelabel)
class Manager(ScreenManager):
pass
sm = ScreenManager(transition=SlideTransition())
sm.add_widget(MainScreen(name='mainscreen'))
sm.add_widget(SettingScreen(name='settings'))
sm.add_widget(SettingScreen(name='thirdscreen'))
class testApp(App):
def build(self):
return Manager()
if __name__ == "__main__":
testApp().run()
Well, I can fix this for you, but honestly I don't think you are making the right use of kv templates. But it will work anyway.
A new template is a rule by itself, it has its own root, so in return the application root (app.root) won't have direct access to children's ids defined inside their own templates. So you have to reach its direct child which makes a template containing the child you want to access then you reach that child by its id.
For instance, in your kv code, you will need to change line 63 (kv) from:
app.root.ids.changethis.text = app.root.ids.getthis.text
to:
app.root.ids.mainscreen.ids.changethis.text = app.root.ids.settings.ids.getthis.text
and line 99 (python) from:
thelabel=Label(text=self.parent.settings.settingbox.getthis.text)
to:
thelabel=Label(text=self.parent.ids.settings.ids.getthis.text)
Why shouldn't root (or any parent widget) access the ids defined inside separate templates ?
Templates, or Dynamic classes are made for re-usability, just like any none-static class.
Consider the following scenario:
BoxLayout:
CustomWidget:
id: wdg1
CustomWidget:
id: wdg2
Button:
text: 'change text of first label'
on_press: root.ids.lbl.text = 'new text'
<CustomWidget#Widget>:
Label:
text: 'some text'
id: lbl
Now The root widget have two instances of CustomWidget as children, thus two children Labels having the id 'lbl'. Which one should be called if the root called lbl directly?
To handle this, you can call lbl from its direct template instance. Thus: root.ids.wdg1.ids.lbl.text for the label inside the first widget, or root.ids.wdg2.ids.lbl.text for the label inside the second one.

Access a kivy screenmanager created in a kv file through the py file

i want to create the ScreenManager in the kv file, but i also need th change the shown screen in the .py file. Thats because i have to create some buttons
dynamically and bind a specific function to them, which will change to a specific (button related) screen. Creating the buttons is way more convient in python. So the main question is: how to access the screenmanager created in a kv file through the py file?
To explain it a bit futher, here is some code:
kv file
#: kivy 1.10.1
ScreenManager:
id: screen_manager
FirstScreen:
id: first_screen
name: 'FirstScreen'
manager: 'screen_manager'
SecondScreen:
id: second_screen
name: 'SecondScreen'
manager: 'screen_manager'
py file
from kivy.modules import console
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.properties import ObjectProperty
class FirstScreen(Screen):
MenuScreen = ObjectProperty(None)
def SwitchToSecond(self):
print(ScreenManagement.current)
ScreenManagement.current = "TestScreen"
class SecondScreen(Screen):
pass
class testApp(App):
pass
if __name__ == "__main__":
testApp().run()
thank you for any guidance in advance
If you want to access the ScreenManager within a Screen you must use its manager attribute, but for this you must not create a property with the same name, in your case you are doing it which is considered a bad practice.
Modifying your code and adding some elements we obtain the following example:
*.kv
#: kivy 1.10.1
ScreenManager:
id: screen_manager
FirstScreen:
id: first_screen
name: 'FirstScreen'
Button:
text: "First"
on_press: first_screen.SwitchToSecond()
SecondScreen:
id: second_screen
name: 'SecondScreen'
Label:
text: "second"
.*py
from kivy.modules import console
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.properties import ObjectProperty
class FirstScreen(Screen):
MenuScreen = ObjectProperty(None)
def SwitchToSecond(self):
self.manager.current = "SecondScreen"
class SecondScreen(Screen):
pass
class testApp(App):
pass
if __name__ == "__main__":
testApp().run()
Here is a simple example using the ScreenManager (I also added a method inside of the MyScreenManager class that accepts a value, which would correspond to the name of the screen you would like to change to, but not necessary for this app to run):
Python
import kivy
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class Screen3(Screen):
pass
class MyScreenManager(ScreenManager):
def changescreen(self, value):
self.current = value
#Main application
class TestApp(App):
def build(self):
self.sm = MyScreenManager()
return self.sm
if __name__ == '__main__':
TestApp().run()
kv
<MyScreenManager>:
Screen1:
name: 'screen1'
Screen2:
name: 'screen2'
Screen3:
name: 'screen3'
<Screen1>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 2'
on_press: root.manager.current = 'screen2'
Button:
text: 'Go to Screen 3'
on_press: root.manager.current = 'screen3'
Label:
text: 'You are on ' + root.name
<Screen2>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 1'
on_press: root.manager.current = 'screen1'
Button:
text: 'Go to Screen 3'
on_press: root.manager.current = 'screen3'
Label:
text: 'You are on ' + root.name
<Screen3>:
GridLayout:
rows: 2
padding: 20
spacing: 20
Button:
text: 'Go to Screen 1'
on_press: root.manager.current = 'screen1'
Button:
text: 'Go to Screen 2'
on_press: root.manager.current = 'screen2'
Label:
text: 'You are on ' + root.name

Why is my Kivy Actionbar gone?

Good evening,
I'm trying to combine kivy's actionbar with a screenmanager. I've gotten to the point where I can switch through screens, but am not able to get my actionbar to show. I've followed alot of examples, but none that can help me with my problem. I'm fairly new to kivy so I haven't been working with it for very long.
I was kind of wondering if someone would be able to point out where my problem lies, because I'm trying to build my very own GUI with an action bar that lets me switch through screens.
here is my main.py:
#!/usr/bin/env python3
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class Menu(BoxLayout):
pass
class ScreenThermo(Screen):
pass
class ScreenLight(Screen):
pass
class ScreenEnergy(Screen):
pass
class ScreenWeather(Screen):
pass
class Manager(ScreenManager):
screen_thermo = ObjectProperty(None)
screen_light = ObjectProperty(None)
screen_energy = ObjectProperty(None)
screen_weather = ObjectProperty(None)
class MenuApp(App):
def thermostaat(self):
print("Thermostaat")
def verlichting(self):
print("Verlichting")
def energie(self):
print("Energie")
def weer(self):
print("Het Weer")
def build(self):
return Manager()
if __name__ == '__main__':
MenuApp().run()
And here's my menu.kv file:
#:kivy 1.0.9
<Menu>:
orientation: "vertical"
ActionBar:
ActionView:
ActionPrevious:
ActionButton:
text: "Thermostaat"
on_release: app.thermostaat()
ActionButton:
text: "Verlichting"
#I want my screens to switch when clicking on this actionbar button
on_press: root.manager.current= 'light'
on_release: app.verlichting()
ActionButton:
text: "Energieverbruik"
on_release: app.energie()
ActionButton:
text: "Het Weer"
on_release: app.weer()
Button:
text: "Nothing"
background_color: 1, 1, 1, 0.6
background_normal: ""
<ScreenThermo>:
Button:
text: "stuff1"
#this is a test to see if i can switch through screens
on_press: root.manager.current= 'light'
<ScreenLight>:
Button:
text: "stuff2"
<ScreenEnergy>:
Button:
text: "stuff3"
<ScreenWeather>:
Button:
text: "stuff4"
<Manager>:
id: screen_manager
screen_thermo: screen_thermo
screen_light: screen_light
screen_energy: screen_energy
screen_weather: screen_weather
ScreenThermo:
id: screen_thermo
name: 'thermo'
manager: screen_manager
ScreenLight:
id: screen_light
name: 'light'
manager: screen_manager
ScreenEnergy:
id: screen_energy
name: 'energy'
manager: screen_manager
ScreenWeather:
id: screen_weather
name: 'weather'
manager: screen_manager
As you can see, I'm trying to have my screens switch on a actionbar button click, but somehow when i launch it, my actionbar is gone.
If anyone could help me with this issue, that would be amazing.
In the following example we will replace the root widget, Manager with Menu, the detail solution is as follow:
main.py - Python Script
1. build method
Make the class Menu the root widget by replacing:
def build(self):
return Manager()
with:
def build(self):
return Menu()
2. Menu classs
Declare a variable, manager of type ObjectProperty which we will hook it up to the ScreenManager.
class Menu(BoxLayout):
manager = ObjectProperty(None)
menu.kv - kv File
3. Hook up the ObjectProperty to id
Here we hook up the ObjectProperty, manager to the ScreenManager's id, screen_manager so that we can reference it e.g. root.manager.current.
<Menu>:
manager: screen_manager
4. Define the height of ActionBar
We set the height of the ActionBar to 10% (0.1) of the parent's height using size_hint_y = 0.1
ActionBar:
size_hint_y: 0.1
ActionView:
5. Define the height of the Button
We set the height of the Button to 10% (0.1) of the parent's height using
size_hint_y = 0.1
Button:
size_hint_y: 0.1
text: "Nothing"
6. Add ScreenManager as children of Menu
We add Manager as children of Menu after Button. Since we did not specify the height, it defaults to the remaining available height i.e. 0.8 (80% of parent's height).
Manager:
id: screen_manager
Example
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager,Screen
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
class Menu(BoxLayout):
manager = ObjectProperty(None)
class ScreenThermo(Screen):
pass
class ScreenLight(Screen):
pass
class ScreenEnergy(Screen):
pass
class ScreenWeather(Screen):
pass
class Manager(ScreenManager):
screen_thermo = ObjectProperty(None)
screen_light = ObjectProperty(None)
screen_energy = ObjectProperty(None)
screen_weather = ObjectProperty(None)
class MenuApp(App):
def thermostaat(self):
print("Thermostaat")
def verlichting(self):
print("Verlichting")
def energie(self):
print("Energie")
def weer(self):
print("Het Weer")
def build(self):
return Menu()
if __name__ == '__main__':
MenuApp().run()
menu.kv
#:kivy 1.10.0
<Menu>:
manager: screen_manager
orientation: "vertical"
ActionBar:
size_hint_y: 0.1
ActionView:
ActionPrevious:
ActionButton:
text: "Thermostaat"
on_release: app.thermostaat()
ActionButton:
text: "Verlichting"
#I want my screens to switch when clicking on this actionbar button
on_press: root.manager.current= 'light'
on_release: app.verlichting()
ActionButton:
text: "Energieverbruik"
on_release: app.energie()
ActionButton:
text: "Het Weer"
on_release: app.weer()
Button:
size_hint_y: 0.1
text: "Nothing"
background_color: 1, 1, 1, 0.6
background_normal: ""
Manager:
id: screen_manager
<ScreenThermo>:
Button:
text: "stuff1"
#this is a test to see if i can switch through screens
on_press: root.manager.current= 'light'
<ScreenLight>:
Button:
text: "stuff2"
<ScreenEnergy>:
Button:
text: "stuff3"
<ScreenWeather>:
Button:
text: "stuff4"
<Manager>:
id: screen_manager
screen_thermo: screen_thermo
screen_light: screen_light
screen_energy: screen_energy
screen_weather: screen_weather
ScreenThermo:
id: screen_thermo
name: 'thermo'
manager: screen_manager
ScreenLight:
id: screen_light
name: 'light'
manager: screen_manager
ScreenEnergy:
id: screen_energy
name: 'energy'
manager: screen_manager
ScreenWeather:
id: screen_weather
name: 'weather'
manager: screen_manager
Output
you never add the menu to your app also you need to add a manager property in your menu. Try something like this:
in the .py:
...
class Menu(BoxLayout):
manager = ObjectProperty(None)
...
class MenuApp(App):
def thermostaat(self):
print("Thermostaat")
def verlichting(self):
print("Verlichting")
def energie(self):
print("Energie")
def weer(self):
print("Het Weer")
...
Notice that I have removed the build method
In your kv add this block of code at the end:
...
BoxLayout: #use a box layout or whatever you want
orientation: 'vertical'
Menu:
size_hint_y: .1
manager: manager
Manager:
size_hint_y: .9
id: manager

Changing variables from one screen to other using python in kivy

I have a two screen kivy app and I want to pass a variable from one screen to a second screen using python.
python file
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import StringProperty,ObjectProperty
from kivy.uix.label import Label
class MenuScreen(Screen):
label1=StringProperty()
label2=StringProperty()
def __init__(self,**kwargs):
super(MenuScreen, self).__init__(**kwargs)
self.label1="hello"
self.label2="world"
def change_text(self):
lbl1=self.label1+ " and "
lbl2= "A beautiful "+self.label2
chg=SettingsScreen()
chg.UpdateSettings(lbl1,lbl2)
# HERE: something like
class SettingsScreen(Screen):
label3=StringProperty()
label4=StringProperty()
def __init__(self,**kwargs):
super(SettingsScreen, self).__init__(**kwargs)
#some default texts
self.label3="Nothing"
self.label4="Nothing"
def UpdateSettings(self,lbl1,lbl2):
print(lbl1,lbl2)
self.label3=lbl1
self.label4=lbl2
class TestScreenManager(ScreenManager):
menu_screen=ObjectProperty(None)
settings_screen=ObjectProperty(None)
class TestApp(App):
def build(self):
return TestScreenManager()
TestApp().run()
kvfile
#: import ScreenManager kivy.uix.screenmanager.ScreenManager
#: import Screen kivy.uix.screenmanager.ScreenManager
#: import SettingsScreen screen
<TestScreenManager>:
id: screen_manager
menu_screen: menu_screen
settings_screen: settings_screen
MenuScreen:
id: menu_screen
name: 'menu'
manager: screen_manager
SettingsScreen:
id: settings_screen
name: 'settings_screen'
manager: screen_manager
<MenuScreen>:
name: 'MenuScreen'
BoxLayout:
Label:
text:root.label1
Label:
text:root.label2
Button:
text: 'Goto nn'
size_hint_y:0.2
on_press:
root.manager.current = 'settings_screen'
root.change_text()
<SettingsScreen>:
#name: 'SettingsScreen'
label_id: label_field
BoxLayout:
Label:
text:root.label3
Label:
text:root.label4
Label:
id: label_field
text: "some text"
It runs without an error but it is not changing the variables of the second screen. For debugging I added print(lbl1,lbl2) inside UpdateSetting functions and it prints out passing lbl1 and lbl2 variables but its not updating the labels.
When you do chg=SettingsScreen(), you are creating a new instance of the SettingsScreen class and you modify the labels of that instance. You need to access the object used by your ScreenManager, not create a new one.
You can use the manager property of your current Screen to get the instance reference of the other screen:
def change_text(self):
lbl1=self.label1 + " and "
lbl2= "A beautiful "+ self.label2
chg = self.manager.settings_screen #<<<<<<<<<<<<<<
chg.UpdateSettings(lbl1,lbl2)

Categories