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()
Related
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()
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
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
I am trying to run this type of code on android but when i tap mobile back button, inspite of coming to the previous screen app is closed,
i used this piece of code to stop the App for being closed:
def __init__(self, **kwargs):
super(MyApp, self).__init__(**kwargs)
Window.bind(on_keyboard=self.BackButton)
def BackButton(self, window, key, *args):
if key==27:
return True
But how to return to the previous screen. and how to use ActionPrevious Button to come at previous screen?
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string("""
<MyAppGUI>:
orientation:'vertical'
id: box
ActionBar:
ActionView:
ActionPrevious:
title: 'How to go back using this button???'
with_previous: True
ScreenManager:
id: screenmanger
Screen:
name: 's1'
Button:
text: 'goto screen 2'
on_release: screenmanger.current='s2'
Screen:
name: 's2'
Button:
text: 'goto screen 3'
on_release: screenmanger.current='s3'
Screen:
name: 's3'
Button:
text: 'goto main screen'
on_release: screenmanger.current='s1'
""")
class MyAppGUI(BoxLayout):
pass
class MyApp(App):
def build(self):
return MyAppGUI()
if __name__=='__main__':
MyApp().run()
To return to the previous window using ActionPrevious button you just need to bind its on_release event to a function that uses current property to set the previous screen using the name returned by ScreenManager.previous () method or use a list that serves as a history of the windows visited. To use the Back key on Android, your code is correct in principle, at least on my device it works without problems:
from kivy.app import App
from kivy.core.window import Window
from kivy.lang import Builder
from kivy.properties import ObjectProperty
from kivy.uix.boxlayout import BoxLayout
Builder.load_string("""
<MyAppGUI>:
sm: screenmanger
orientation:'vertical'
id: box
ActionBar:
ActionView:
ActionPrevious:
title: 'How to go back using this button???'
with_previous: True
on_release: root.set_previous_screen()
ScreenManager:
id: screenmanger
Screen:
name: 's1'
Button:
text: 'goto screen 2'
on_release:
screenmanger.transition.direction = 'right'
screenmanger.current='s2'
Screen:
name: 's2'
Button:
text: 'goto screen 3'
on_release:
screenmanger.transition.direction = 'right'
screenmanger.current='s3'
Screen:
name: 's3'
Button:
text: 'goto main screen'
on_release:
screenmanger.transition.direction = 'right'
screenmanger.current='s1'
""")
class MyAppGUI(BoxLayout):
sm = ObjectProperty()
def __init__(self, **kwargs):
super(MyAppGUI, self).__init__(**kwargs)
Window.bind(on_keyboard=self._key_handler)
def _key_handler(self, instance, key, *args):
if key is 27:
self.set_previous_screen()
return True
def set_previous_screen(self):
if self.sm.current != 's1':
self.sm.transition.direction = 'left'
self.sm.current = self.sm.previous()
class MyApp(App):
def build(self):
return MyAppGUI()
if __name__ == '__main__':
MyApp().run()
Hello I am facing problem when I am using a GUI in python using Kivy. I am using TabbedPanel.
TabbedPanelItem:
text: 'apple'
BoxLayout:
Label:
text: 'Label1'
Entry:
text: 'Entry1'
CheckBox:
text: 'CheckBox1'
Button:
text: 'Button1'
TabbedPanelItem:
text: 'Grape'
BoxLayout:
Label:
text: 'Label1'
Button:
text: 'Button1'
Several things:
you can have only one build method
return in the second build method, is incorrectly indented, should be same as for
you can always only have one App class not class AccordionApp(App): and class KivyGuiApp(App):
Here is a smaller version of your app from which you should be able to implement more from
'''
TabbedPanel
============
Test of the widget TabbedPanel.
'''
from kivy.app import App
from kivy.uix.tabbedpanel import TabbedPanel, TabbedPanelItem
from kivy.lang import Builder
from kivy.uix.checkbox import CheckBox
from kivy.uix.accordion import Accordion, AccordionItem
from kivy.uix.button import Button
from kivy.app import App
Builder.load_string("""
<Test>:
TabbedPanelItem:
text: 'apple'
BoxLayout:
Label:
text: 'Label1'
Label:
text: 'Entry1'
CheckBox:
text: 'CheckBox1'
Button:
text: 'Button1'
""")
class Test(TabbedPanel):
pass
class KivyGuiApp(App):
def build(self):
test = Test()
acc = Accordion()
for x in range(5):
item = AccordionItem(title='Table %d' % x)
item.add_widget(Button(text='apple\n'))
item.add_widget(Button(text='Grape\n'))
item.add_widget(Button(text='Lemon\n'))
acc.add_widget(item)
panel = TabbedPanelItem()
panel.add_widget(acc)
test.add_widget(panel)
return test
if __name__ == '__main__':
KivyGuiApp().run()