I found this code that showed me how to use a spinner to change screens. It works well in allowing me to switch screens, but I found that I also need to switch screens by using buttons on an individual screen.
Here is an example of what I mean:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
KV = """
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Edit'
on_press: root.goEdit()
Label:
text: 'Main Screen'
<HelpScreen>:
Label:
text: 'Help Screen'
<SettingsScreen>:
Label:
text: 'Settings Screen'
<EditScreen>:
name: 'edit_Screen'
text: 'Edit Screen'
<ScreenMenu>:
text: 'main'
values: ('main', 'help', 'settings')
size_hint: None, None
size: 200, 44
"""
class MainScreen(FloatLayout):
def goEdit(self):
MyApp.build.screen_layout.remove_widget(MyApp.screen)
screen = EditScreen()
MyApp.screen = screen
MyApp.screen_layout.add_widget(MyApp.screen)
class HelpScreen(FloatLayout):
pass
class SettingsScreen(FloatLayout):
pass
class EditScreen(FloatLayout):
pass
class ScreenMenu(Spinner):
pass
class MyApp(App):
def build(self):
Builder.load_string(KV)
self.screen = None
self.root = FloatLayout()
self.screen_layout = FloatLayout()
self.menu = ScreenMenu()
self.root.add_widget(self.screen_layout)
self.root.add_widget(self.menu)
self.menu.bind(text=self.select_screen)
self.show('main')
return self.root
def select_screen(self, *args):
self.show(self.menu.text)
def show(self, name='main'):
if self.screen is not None:
self.screen_layout.remove_widget(self.screen)
self.screen = None
if name == 'main':
screen = MainScreen()
elif name == 'help':
screen = HelpScreen()
elif name == 'settings':
screen = SettingsScreen()
else:
raise Exception('Invalid screen name')
self.screen = screen
self.screen_layout.add_widget(screen)
if __name__ == "__main__":
MyApp().run()
As you can see, this is almost the exact code as found in the link above, the only difference is that here, I create an "Edit" button that when clicked, refers to the goEdit() method of the "MainScreen" class.
on_press: root.goEdit()
My problem is I don't know how to create the goEdit() method so that, when called, it goes to the "EditScreen" while also having the spinner work ( it goes to the "MainScreen", "HelpScreen" and "SettingScreen" screens). The code I tried in goEdit() clearly doesn't work.
I have also tried changing the inherited classes from FloatLayout to Screen:
class MainScreen(Screen):
def goEdit(self):
self.parent.current = 'edit_Screen'
class HelpScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class EditScreen(Screen):
pass
class ScreenMenu(Spinner):
pass
Here I tried to switch screens using this code:
self.parent.current = 'edit_Screen'
but when clicking the 'edit' button, nothing happens, I don't even get error messages.
Essentially what I want is for the spinner to work as it does in the example link, but I also need the 'edit' button to also change screens. Any help would be greatly appreciated.
Solution
Please refer to the explanations, example and output for details.
kv String
Replace root.edit() with app.show(name='edit')
Replace name: 'edit_screen' with Label:
Indent text: 'Edit Screen'
Python Script
Replace all the codings in MainScreen with pass
Add elif name == 'edit': screen = EditScreen() statement in method show
Example
main.py
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.spinner import Spinner
KV = """
<MainScreen>:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Edit'
on_press: app.show(name='edit')
Label:
text: 'Main Screen'
<HelpScreen>:
Label:
text: 'Help Screen'
<SettingsScreen>:
Label:
text: 'Settings Screen'
<EditScreen>:
Label:
text: 'Edit Screen'
<ScreenMenu>:
text: 'main'
values: ('main', 'help', 'settings')
size_hint: None, None
size: 200, 44
"""
class MainScreen(FloatLayout):
pass
class HelpScreen(FloatLayout):
pass
class SettingsScreen(FloatLayout):
pass
class EditScreen(FloatLayout):
pass
class ScreenMenu(Spinner):
pass
class MyApp(App):
def build(self):
Builder.load_string(KV)
self.screen = None
self.root = FloatLayout()
self.screen_layout = FloatLayout()
self.menu = ScreenMenu()
self.root.add_widget(self.screen_layout)
self.root.add_widget(self.menu)
self.menu.bind(text=self.select_screen)
self.show('main')
return self.root
def select_screen(self, *args):
self.show(self.menu.text)
def show(self, name='main'):
if self.screen is not None:
self.screen_layout.remove_widget(self.screen)
self.screen = None
if name == 'main':
screen = MainScreen()
elif name == 'help':
screen = HelpScreen()
elif name == 'settings':
screen = SettingsScreen()
elif name == 'edit':
screen = EditScreen()
else:
raise Exception('Invalid screen name')
self.screen = screen
self.screen_layout.add_widget(screen)
if __name__ == "__main__":
MyApp().run()
Output
Here is a very simple example using the ScreenManager:
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):
try:
if value!='Choose a Value...':
self.current = value
except:
print('No screen named ' + 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'
Spinner:
text: 'Choose a Value...'
values: ['screen2', 'screen3']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
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'
Spinner:
text: 'Choose a Value...'
values: ['screen1', 'screen3']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
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'
Spinner:
text: 'Choose a Value...'
values: ['screen1', 'screen2']
on_text:
root.manager.changescreen(self.text)
self.text = 'Choose a Value...'
Label:
text: 'You are on ' + root.name
Related
I have an Kivy app that has 2 screens. Screen 1 (ProblemWindow) will get the user input and screen 2 (StepsWindow) will show some images based on screen1 input. However, I need to pass one of the values (Spinner id: problem_id) from screen 1 (ProblemWindow) into screen 2 (StepsWindow) and also use the value in the python file for additional logic.
I can pass the value between the screens but I am not able to use it in python. I need to get the updated Label Text of the StepsWindow into my python code every time I change it in my ProblemWindow and press the button "ShowSteps".
Could anyone please guide me how to achieve this?
This is my python code:
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.ids.click_label.text = f'selected problem: {value}'
return value
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
This is my kv file for reference:
WindowManager:
id: window_manager
ProblemWindow:
id: pw
StepsWindow:
id: sw
<ProblemWindow>:
name: "problem_window"
GridLayout:
rows: 4
Label:
text: "TEST"
font_size: 24
Label:
id: click_label
text: "Select a problem"
Spinner:
id: problem_id
text: "Problems List (Click here)"
font_size: 24
values: ["Problem_1", "Problem_2"]
on_text: root.selected_problem(problem_id.text)
size_hint: 0.1, 0.1
width: 300
Button:
text: "Show Steps"
font_size: 28
size_hint: 0.2, 0.2
pos_hint: {"center_x": 0.5, "center_y": 0.5}
on_release:
app.root.current = "steps_window"
root.manager.transition.direction = "left"
<StepsWindow>:
name: "steps_window"
GridLayout:
rows: 3
size: root.width, root.height
Label:
id: problem_name
text: root.manager.ids.pw.ids.problem_id.text
font_size: '42'
size_hint_y: None
height: 50
Button:
text: "Back"
on_release:
app.root.current = "problem_window"
root.manager.transition.direction = 'right'
Something like this?
# importing dependencies
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
class WindowManager(ScreenManager):
pass
class ProblemWindow(Screen):
def selected_problem(self, value):
self.value = value
self.ids.click_label.text = f'selected problem: {value}'
return value
#logic here
def logic_here(self):
if self.value == "Problem_1":
print(f'The solution for "Problem_1" is:')
else:
print(f'The solution for "Problem_2" is:')
class StepsWindow(Screen):
def __init__(self, **kwargs):
super().__init__(**kwargs)
# self.get_problem_name()
def get_problem_name(self, *args):
self.problem_name = self.ids.problem_name.text
print(self.problem_name)
kv = Builder.load_file('main.kv')
class main(App):
def build(self):
return kv
if __name__ == '__main__':
main().run()
And for the <ProblemWindow> button in the .kv file add this:
root.logic_here()
main.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class ScreenHome(Screen):
pass
class ScreenNew(Screen):
pass
class Performer(App):
def build(self):
sm = ScreenManager()
sm.add_widget(ScreenHome(name="home"))
sm.add_widget(ScreenNew(name="new"))
return sm
def test(self):
text = self.root.get_screen('<ScreenNew>').mytext
label = self.root.get_screen('<ScreenNew>').mylabel
label.text = text.text
pass
if __name__ == '__main__':
Performer().run()
kv file
<ScreenHome>:
Button:
text: 'Home'
on_press: app.root.current = 'new'
<ScreenNew>:
GridLayout:
rows: 4
Label:
id: mylabel
TextInput:
id: mytext
Button:
on_press: app.test()
text: "adauga"
font_size: 50
Button:
text: 'Home'
on_press: app.root.current = 'home'
There are multiple screens in my kivy app:
1. main screen, screen1, screen2, screen3 and screen4.
main screen has main-dropdown and sub-dropdown list. main-dropdown shows groups and on selecting a particular group, sub-dropdown shows screens associated with that group.
I want to navigate to a particular screen when selected from sub-dropdown list. My code doesn't show any error but I am not able to navigate to the selected screen. Please help.
doca.py
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ListProperty, DictProperty
class windowManager(ScreenManager):
pass
class MainScreen(Screen):
values_dict = {'Group1':['Screen1', 'Screen2'],
'Group2':['Screen3', 'Screen4']}
sub_values = ListProperty()
def values_update(self,text):
self.sub_values = self.values_dict[text]
if text != 'Select group type':
self.ids.sub_drop.text = 'select ' + text + ' screen'
return 'select ' + text + ' screen'
def open_screen(self, text):
if text != 'select ' + self.ids.main_drop.text + ' screen':
sm = windowManager()
sm.current = text
class Screen1(Screen):
pass
class Screen2(Screen):
pass
class Screen3(Screen):
pass
class Screen4(Screen):
pass
class appln(App):
def build(self):
return windowManager()
if __name__=="__main__":
appln().run()
appln.kv
<WindowManager>:
MainScreen:
Screen1:
Screen2:
Screen3:
Screen4:
<MainScreen>:
name: 'main'
GridLayout:
cols:2
Spinner:
id: main_drop
size_hint: None, None
size: 200, 100
pos_hint:{'top': 1}
text: 'Select group type'
values: root.values_dict.keys()
on_text:
root.values_update(self.text)
Spinner:
id: sub_drop
size_hint: None, None
size: 230, 100
pos_hint:{'top': 1}
values: root.sub_values
on_text: root.open_screen(self.text)
<Screen1>:
name: 'Screen1'
Label:
text: 'This is screen 1'
<Screen2>:
name: 'Screen2'
Label:
text: 'This is screen 2'
<Screen3>:
name: 'Screen3'
Label:
text: 'This is screen 3'
<Screen4>:
name: 'Screen4'
Label:
text: 'This is screen 4'
In your MainScreen code, the line:
sm = windowManager()
is creating a new instance of windowManager, so any use of that instance will have no effect on the windowManager that is actually managing your Screens. To fix this, just use a reference to the correct windowManager by replacing that line with:
sm = self.manager
I want to clear a TextInput's text: when I click on it.
Sample Code:
from kivy.app import App
from kivy.lang import Builder
kv_string = """
ScreenManager:
id: manager
Screen:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Why does it clear multiple inputs? And why do they get cleared after touch_up?'
TextInput:
text: 'Write Your Name'
on_touch_down:
self.text = ''
TextInput:
text: 'Write Your Last Name'
on_focus:
self.text = ''
TextInput:
text: 'Write Your Phone Number'
on_touch_down:
self.text = ''
"""
class MyApp(App):
def build(self):
root_widget = Builder.load_string(kv_string)
return root_widget
if __name__ == "__main__":
MyApp().run()
Neither on_touch_down: or on_focus erases JUST the text input that is currently focused. Instead, both get cleared when I touch anywhere on the screen. I would want them cleared individually once the cursor is on a text input. I also tried on_cursor but that didn't work either. What am I missing? Thank you in advance!
The on_touch_down event is received by all the widgets until one returns True telling the event-loop that it is using it and thus not send it to other widgets as indicated by the docs:
on_touch_down(touch) Added in 1.0.0
Receive a touch down event.
Parameters:
touch: MotionEvent class Touch received.
The touch is in parent coordinates. See relativelayout for a discussion on coordinate
systems.
Returns:
bool If True, the dispatching of the touch event will stop.
If False, the event will continue to be dispatched to the rest of the
widget tree.
The classic use of on_touch_down is in python since kv language is limited in the overwriting of methods:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.textinput import TextInput
class MyTextInput(TextInput):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.text = ""
return True
return super(MyTextInput, self).on_touch_down(touch)
kv_string = """
ScreenManager:
id: manager
Screen:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Why does it clear multiple inputs? And why do they get cleared after touch_up?'
MyTextInput:
text: 'Write Your Name'
MyTextInput:
text: 'Write Your Last Name'
MyTextInput:
text: 'Write Your Phone Number'
"""
class MyApp(App):
def build(self):
root_widget = Builder.load_string(kv_string)
return root_widget
if __name__ == "__main__":
MyApp().run()
Or something equivalent in .kv but the devestaja is that you can not return True.
kv_string = """
ScreenManager:
id: manager
Screen:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Why does it clear multiple inputs? And why do they get cleared after touch_up?'
TextInput:
text: 'Write Your Name'
on_touch_down: if self.collide_point(*args[1].pos): self.text = ""
TextInput:
text: 'Write Your Last Name'
on_touch_down: if self.collide_point(*args[1].pos): self.text = ""
TextInput:
text: 'Write Your Phone Number'
on_touch_down: if self.collide_point(*args[1].pos): self.text = ""
"""
So you should use on_focus which is an event associated to FocusBehavior that overwrites on_touch_down verifying using self.collide_point(*touch.pos).
from kivy.app import App
from kivy.lang import Builder
kv_string = """
ScreenManager:
id: manager
Screen:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Why does it clear multiple inputs? And why do they get cleared after touch_up?'
TextInput:
text: 'Write Your Name'
on_focus: self.text = ""
TextInput:
text: 'Write Your Last Name'
on_focus: self.text = ""
TextInput:
text: 'Write Your Phone Number'
on_focus: self.text = ""
"""
class MyApp(App):
def build(self):
root_widget = Builder.load_string(kv_string)
return root_widget
if __name__ == "__main__":
MyApp().run()
All you need to do is to just add this:
on_focus: self.text = '' if args[1] else self.text
on_focus is a function that gets called when the TextInput is selected or unselected, and the function gives you two arguments instance which is the thing got selected or unselected and value which is if that instance got selected or unselected, these two arguments get put in a list called args, and since we're doing this in the widget itself in the kv file we don't have to worry about instance, so we check if the value is True, and if it is that means that the TextInput got clicked, so we clear it, otherwise we set it to itself.
So this is how the script would look like:
from kivy.app import App
from kivy.lang import Builder
kv_string = """
ScreenManager:
id: manager
Screen:
BoxLayout:
orientation: 'vertical'
Button:
text: 'Why does it clear multiple inputs? And why do they get cleared after touch_up?'
TextInput:
text: 'Write Your Name'
on_focus: self.text = '' if args[1] else self.text
TextInput:
text: 'Write Your Last Name'
on_focus: self.text = '' if args[1] else self.text
TextInput:
text: 'Write Your Phone Number'
on_focus: self.text = '' if args[1] else self.text
"""
class MyApp(App):
def build(self):
root_widget = Builder.load_string(kv_string)
return root_widget
if __name__ == "__main__":
MyApp().run()
In my RootWidget I have a label and two buttons. I want to dynamically change the text of the label whenever one of the buttons is clicked.
Here's a minimal working example of how I do it at the moment.
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
Button:
text: '1'
on_press: label.text = self.text
Button:
text: '2'
on_press: label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()
Obviously I want to refactor the line on_press: label.text = self.text. My first tries ended in
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
<MyButton>:
on_press: label.text = self.text
But obviously the MyButton-class doesn't know the property label of the RootWidget-class. And class rules inside class rules is also not allowed.
Is there a way of accomplishing binding the on_press-action dynamically?
You can refer to the Label like this:
<MyButton#Button>:
on_press: self.parent.ids.label.text = self.text
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
MyButton:
text: '1'
MyButton:
text: '2'
It's quite simple actually, because through kv you can access parent of a widget/rule easily with self.parent, so your code would look like:
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
w = Builder.load_string('''
<RootWidget>:
id: root_widget
Label:
id: label
text: 'Push a button'
But:
text: '1'
But:
text: '2'
<But>:
on_press: self.parent.label.text = self.text
''')
class RootWidget(BoxLayout):
pass
class MainApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
MainApp().run()