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.
Related
I am new to python and kivymd and I am trying to develop a program for data entry. However, when I create a drop-down menu for a selection, an error occurred and I can't update the value of the text field after I select an item.
Here is the python code:
from kivymd.app import MDApp
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.clock import Clock
from kivy.properties import ObjectProperty
from kivymd.uix.menu import MDDropdownMenu
class AMRMenu(Screen):
def GIbutton(self):
sm.current = 'GI'
class GIWindow(Screen):
weather = ObjectProperty(None)
menu_weather_items = [{"text":"Sunny"},{"text":"Cloudy"},{"text":"Raining"}]
menu_FeedResponse_items=[{"text":"High"},{"text":"Medium"},{"text":"Low"}]
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.menu = MDDropdownMenu(
items=self.menu_weather_items,
width_mult=4,
caller = self.weather,
callback=self.set_item)
def set_item(self, instance):
def set_item(interval):
self.weather.text = instance.text
self.menu.dismiss()
Clock.schedule_once(set_item, 0.5)
class WindowManager(ScreenManager):
pass
sm = WindowManager()
class MainApp(MDApp):
def __init__(self, **kwargs):
super().__init__(**kwargs)
kv = Builder.load_file("FR.kv")
def build(self):
screens = [AMRMenu(name = "menu"), GIWindow(name = "GI")]
for screen in screens:
sm.add_widget(screen)
sm.current = "menu"
return sm
if __name__ == "__main__":
MainApp().run()
And here is the kv. file:
<AMRMenu>:
name:"menu"
BoxLayout:
orientation: "vertical"
MDToolbar:
title: "Main Menu"
MDList:
OneLineListItem:
text: "General Information"
on_press: root.GIbutton()
OneLineListItem:
text: "Water Temperature"
OneLineListItem
text: "Feeding Amount"
OneLineListItem:
text: "Residue and Feeding response"
OneLineListItem:
text: "Dead fish"
OneLineListItem:
text: "Sell/Use"
<GIWindow>
name: "GI"
weather: weather
ScrollView:
id: screen
MDBoxLayout:
orientation: 'vertical'
adaptive_height: True
MDTextField:
id: weather
pos_hint: {'center_x': .5, 'center_y': .5}
hint_text: "Weather"
icon_right: "arrow-down-drop-circle-outline"
input_filter: lambda text, from_undo: text[:5 - len(self.text)]
on_focus: root.menu.open()
Here is the error message:
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/kivy/core/window/__init__.py", line 1297, in add_widget
(widget, widget.parent)
kivy.uix.widget.WidgetException: Cannot add <kivymd.uix.menu.MDDropdownMenu object at 0x7fd2fa31a6e0> to window, it already has a parent <kivy.core.window.window_sdl2.WindowSDL object at 0x7fd2f65826e0>
I don't know why I made this happen. It helps a lot if someone figures it out. Thank you for reading this question
The problem is that your kv line:
on_focus: root.menu.open()
is opening the menu every time the focus changes. So, it tries to open the menu even when the focus becomes false. An easy fix is to just open the menu when focus is True`:
on_focus: if self.focus: root.menu.open()
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.
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
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
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.