Kivy screenmanager: switching screen after timeout with signal - python

Goal is to move to a settings screen when no button is pressed, text is entered or whatever for a certain time.
In fact, functionality is like a screensaver of some sorts.
code version 1
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
sm.switch_to(setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return sm
if __name__ == "__main__":
wiscApp().run()
and the .kv
<MenuScreen>:
BoxLayout:
orientation: 'vertical'
BoxLayout:
Button:
text: "resettimeout"
on_press: app.resetscreensavertimeout()
Button:
text: "do other things"
Button:
text: 'settings'
on_press: root.manager.current = 'settings'
<SettingsScreen>:
BoxLayout:
Button:
text: "stop app"
on_press: app.stop()
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
This works perfectly up until the calling of sm.switch_to(setscreen) in the setscreensaver function.
I tried the following:
code version 2
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
sm = ScreenManager()
setscreen = SettingsScreen(name='settings')
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
But then the settings screen is BLANK!
In the first version of the code, I understand by it doens't work: both sm and setscreen are undefined variables in that function.
In the second version, I don't understand why the settings screen is blank.
edit
*** version 3 of the code***
import signal
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
class MenuScreen(Screen):
pass
class SettingsScreen(Screen):
pass
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
# --> here I need to switch to the settings screen
# but this doens't work, bnoth sm and setscreen are not known here
self.sm.switch_to(self.setscreen)
def resetscreensavertimeout(self):
print("resetting screensaver timer")
signal.alarm(10) # just 5 seconds for debugging
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
signal.signal(signal.SIGALRM, self.setscreensaver)
self.resetscreensavertimeout()
return self.sm
if __name__ == "__main__":
wiscApp().run()
In this version 3, transitioning to the settings screen with the signal works fine, but if I then click the menu button, I get this error (this error does not appear in the other versions of the code):
kivy.uix.screenmanager.ScreenManagerException: No Screen with name "menu".
So, I have several questions
how do I reset the timer every time a button is pressed, text is
entered of whatever, other than defining callbacks for every event
(e.g on_press: app.resetscreensavertimeout()) in the .kv code)?
How do I switch to the settings screen in the first version of the
code?
Why is the settings screen blank in code version 2?
Why does the error occur in version 3?
Is there another (better) way to code this?
thanks a lot!

Here is a modified version of your code that uses Clock.schedule_once() instead of signal:
class wiscApp(App):
def setscreensaver(self, *args):
print("switching to settings")
self.resetscreensavertimeout()
self.sm.current = 'settings'
def resetscreensavertimeout(self, *args):
print("resetting screensaver timer")
self.resetEvent.cancel()
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
def build(self):
self.sm = ScreenManager()
self.setscreen = SettingsScreen(name='settings')
self.sm.add_widget(MenuScreen(name='menu'))
self.sm.add_widget(self.setscreen)
self.resetEvent = Clock.schedule_once(self.setscreensaver, 5)
Window.bind(on_touch_down=self.resetscreensavertimeout)
Window.bind(on_key_down=self.resetscreensavertimeout)
return self.sm
This also uses Window.bind() to trigger the reset of the timeout whenever a button is pressed or a key is pressed.

I took this one step further and defined a general-purpose function to add this functionality to any app.
from typing import Callable
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
def add_inactivity_timeout(app: App, do_timeout: Callable, inactivity_minutes=60):
"""
Sets up an inactivity timeout for the given Kivy app. "Activity" meaning
a touch (a button click) or a key press.
IMPORTANT: Call this from within the `build()` method, not `__init__()`.
This adds a `reset_timeout()` method to the app which can be called
directly by any other activity that needs to be considered (e.g.
periodically within a long-running computation, or while accessing an
external resource).
Alternatively, call `cancel_timeout()` to disable the timeout on the
way in to the long-running activity, and then call `reset_timeout()`
afterwards to re-enable it.
:param app: The kivy app to enhance.
:param do_timeout: The method to call if a timeout occurs.
:param inactivity_minutes: The timeout length in minutes. Defaults to 60.
"""
app.inactivity_minutes = inactivity_minutes
app.reset_event = None
def cancel_timeout(*args):
if app.reset_event:
app.reset_event.cancel()
def reset_timeout(*args):
cancel_timeout()
app.reset_event = Clock.schedule_once(do_timeout, app.inactivity_minutes * 60)
app.cancel_timeout = cancel_timeout
app.reset_timeout = reset_timeout
reset_timeout()
Window.bind(on_touch_down=reset_timeout)
Window.bind(on_key_down=reset_timeout)
To use it, create a callback method for the timeout:
def inactive(self, *args):
print("Timed out -- Inactive!")
self.stop()
and then call
add_inactivity_timeout(self, self.inactive, inactivity_minutes=15)
from within build().

Related

How to fire one event when we release the click on a kivy switch item?

Goal: when I click then release the click on the switch widget, it fires me an event on the release.
Problem: I used the on_touch_down() Switch method but:
it release 2 event on the release of the click and not only one.
when I release and it shows "off", it says "True" and it shows "False" when it's on "on" !
from kivymd.app import MDApp
from kivy.lang import Builder
from kivy.uix.gridlayout import GridLayout
KV = '''
MyGridLayout:
Switch:
id: switch_ecs
on_touch_up:
root.on_switch("ecs")
'''
class MyGridLayout(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
def on_switch(self,element_id):
print(self.ids["switch_" + element_id].active)
class MyApp(MDApp):
def build(self):
self.screen = Builder.load_string(KV)
return self.screen
if __name__ == '__main__':
MyApp().run()
Kivy has some obscure logic that results in touch up events getting dispatched twice to some widgets (like Buttons and Switches). The fix for your code is to use a different trigger for your on_switch() method. You can use:
on_touch_down:
root.on_switch("ecs")
or:
on_active:
root.on_switch("ecs")

Kivy | Can't set 2 buttons on_press binding in screen [duplicate]

I would like to know how to change screens using an on_press event binded to a button, without using a KV file/KV language.
I have read through the Kivy documentation, but have only been able to find solutions using a KV file.
Example:
on_press: root.manager.current = 'screen2'
I can also change the screen in the main python file using:
screenmanager.current = 'screen2'
But I cant figure out how to achieve the same using a button.
A working example with two screens, no kv file everything done in Python:
import kivy
kivy.require('1.8.0')
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.properties import ObjectProperty
class ScreenOne(Screen):
def __init__ (self,**kwargs):
super (ScreenOne, self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 1", font_size='24dp')
my_button1 = Button(text="Go to screen 2",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen2'
class ScreenTwo(Screen):
def __init__(self,**kwargs):
super (ScreenTwo,self).__init__(**kwargs)
my_box1 = BoxLayout(orientation='vertical')
my_label1 = Label(text="BlaBlaBla on screen 2",font_size='24dp')
my_button1 = Button(text="Go to screen 1",size_hint_y=None, size_y=100)
my_button1.bind(on_press=self.changer)
my_box1.add_widget(my_label1)
my_box1.add_widget(my_button1)
self.add_widget(my_box1)
def changer(self,*args):
self.manager.current = 'screen1'
class TestApp(App):
def build(self):
my_screenmanager = ScreenManager()
screen1 = ScreenOne(name='screen1')
screen2 = ScreenTwo(name='screen2')
my_screenmanager.add_widget(screen1)
my_screenmanager.add_widget(screen2)
return my_screenmanager
if __name__ == '__main__':
TestApp().run()
One simple way to accomplish this is to define your own button subclass:
class ScreenButton(Button):
screenmanager = ObjectProperty()
def on_press(self, *args):
super(ScreenButton, self).on_press(*args)
self.screenmanager.current = 'whatever'
The on_press method is automatically called when the button is pressed, so the screenmanager's current property will be changed.
Then you can have code something like:
sm = ScreenManager()
sc1 = Screen(name='firstscreen')
sc1.add_widget(ScreenButton(screenmanager=sm))
sc2 = Screen(name='whatever')
sc2.add_widget(Label(text='another screen'))
sm.add_widget(sc1)
sm.add_widget(sc2)
Clicking the button should switch the screens as required.
Another way (which is probably how kv language actually does it) would be to manually use the bind method.
def switching_function(*args):
some_screen_manager.current = 'whatever'
some_button.bind(on_press=switching_function)
This would mean that switching_function is called whenever some_button is pressed. Of course there is a lot of flexibility here regarding how and when you define the function, so (for instance) you could do something more general like pass the screenmanager as the first argument to the function.
I didn't test this code and it isn't a complete app, but hopefully the meaning is clear. Either method should work fine, you can choose the way that seems most sensible. I might construct a more complete example later.
Another solution, was to use the setter method of EventDispatcher, to get a reference to the setter function for screen_manager.current
button.bind(on_press=partial(sm.setter('current'), (sm, 'whatever'))
of course, it's not very sexy, that's why kv is often a cleaner solution to these things, but it should work.
ps: in case you don't know about it, partial comes from the functools module, and it's often useful to build these kind of callbacks with a preloaded parameter.

how do you change screens from python file in KIVY

All the tutorials in the internet show how to use the ScreenManager and change different screens from the .kv files.
I am still learning and I can not manage to control it from the .py file.
I want my app first to perform some validation before starting. There fore i want Kivy app to start with a simple "Please wait" screen, and after that to go to the Main screen.
Please help!
Here is a short example of how one can change a screen from the python code
from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.lang import Builder
Builder.load_string("""
<Screen1>:
name: 'screen1'
Button:
on_press: root.on_click()
<Screen2>:
name: 'screen2'
Label:
text: "Cool!"
<MyScreenManager>:
Screen1:
Screen2:
""")
class Screen1(Screen):
def on_click(self):
sm.current = 'screen2'
class Screen2(Screen):
pass
class MyScreenManager(ScreenManager):
pass
class SimpleApp(App):
def build(self):
global sm
sm = MyScreenManager()
return sm
SimpleApp().run()
I have used the button on_press event to call the python code but you can run your validations in another thread and when your are done you can call back to the kivy code using the Clock api

Interaction between Kivy widgets in Python

I'm doing a proyect using kivy but i have a problem with the checkboxes. At first I'm trying to do the program like python coding (I know it is'nt clean, but I understand more) And i have a first screen with this coding:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.textinput import TextInput
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen, FadeTransition
from kivy.uix.checkbox import CheckBox
class MainScreen(GridLayout):
def __init__(self,**kwargs):
e=[]
super(MainScreen, self).__init__(**kwargs)
self.cols=2
def on_checkbox_active(checkbox, value):
if value:
e.append(value)
print e
else:
print('The checkbox', checkbox, 'is inactive')
self.add_widget(Label(text='Inserta assignatures desitjades',font_size=35))
self.add_widget(Label(text=''))
ch1 = CheckBox()
self.add_widget(ch1)
self.add_widget(Label(text='Termotecnia'))
ch2 = CheckBox()
self.add_widget(ch2)
self.add_widget(Label(text='Termotecnia'))
ch3 = CheckBox()
self.add_widget(ch3)
self.add_widget(Label(text='Termotecnia'))
ch4 = CheckBox()
self.add_widget(ch4)
self.add_widget(Label(text='Termotecnia'))
b1=Button(text='Exit',background_color=[0.7,0.7,1,1],font_size=24)
self.add_widget(b1)
b2=Button(text='Next',font_size=24,font_color=[1,3,4,0],background_color=[1,2,3,6])
self.add_widget(b2)
ch1.bind(active=on_checkbox_active)
ch2.bind(active=on_checkbox_active)
b1.bind(on_press=exit)
b2.bind(on_press=reloaded)
...
class SimpleKivy(App):
def build(self):
return MainScreen()
if __name__=='__main__':
SimpleKivy().run()
I want to select two or three options for example, and save it for the next screen, like a type of selection. If anyone knows how to do it and save information for the next screen it woul help me a lot, because i have the code of the next screen for all the options, but i want to preselect in the first screen and then only use which i have selected. Also if anyone can help me, i want to know hoy to do the transition to another class (screen) when the button "Next" is pressed. I know this question are pretty simple but I'm new in kivy programming and some concepts are pretty difficult. Thanks.
What you want is accessing variables in other classes. Sometimes this can be annoying and you can do it either hard way with all __init__() and stuff, or... a simplier way comes along: it's get_running_app().
You can create a dictionary or something else, where you can store any value your other classes need to access. It's similar to using globals and it costs you less lines of code. For example in your case you could use a dictionary(or nested dictionaries, json, ...) to store for example 'checkboxes':'<names of checked ones>' and in each init you can loop over these values to make checkboxes active
Basically all you need is a = App.get_running_app() somewhere and something to access in main - App - class.
Example:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
Builder.load_string('''
<Root>:
MainScreen:
name: 'main'
AnotherScreen:
name: 'another'
<MainScreen>:
BoxLayout:
Button:
text: 'next screen'
on_release: root.parent.current='another'
Button:
text: 'ping!'
on_release: root.ping()
<AnotherScreen>:
BoxLayout:
Button:
text: 'previous screen'
on_release: root.parent.current='main'
Button:
text: 'ping!'
on_release: root.ping()
''')
class MainScreen(Screen):
def __init__(self, **kw):
super(MainScreen, self).__init__(**kw)
self.a = App.get_running_app()
def ping(self):
print self.a.big_dict['hi']
class AnotherScreen(Screen):
def ping(self):
b = App.get_running_app()
print b.big_dict['hi']
class Root(ScreenManager):
pass
class SimpleKivy(App):
big_dict={'hi':'hi there!'}
def build(self):
return Root()
SimpleKivy().run()
You can see there's no need to call __init__(), no need to write more lines of code if you really don't need to.

KeyError stopping App in kivy

I'm trying to get an application I've been building to stop at a certain point, and run a cleanup procedure on stop. This seems like it should be easy, but I keep encountering an error, and I haven't been able to track down a solution.
I use kivy 1.8.0 and Python 3.3. For ease, I've modified some code from the kivy documentation, since my code is based on the same framework, and both give me exactly the same error:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
# Create both screens. Please note the root.manager.current: this is how
# you can control the ScreenManager from kv. Each screen has by default a
# property manager that gives you the instance of the ScreenManager used.
Builder.load_string("""
<MenuScreen>:
BoxLayout:
Button:
text: 'Goto settings'
on_press: root.manager.current = 'settings'
Button:
text: 'Quit'
on_press: root.exit()
<SettingsScreen>:
BoxLayout:
Button:
text: 'My settings button'
Button:
text: 'Back to menu'
on_press: root.manager.current = 'menu'
""")
# Declare both screens
class MenuScreen(Screen):
def exit(self):
App.stop(self)
class SettingsScreen(Screen):
pass
# Create the screen manager
sm = ScreenManager()
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
class TestApp(App):
def build(self):
return sm
def on_stop(self):
print('leaving now') # this is where I'd want to run the end of program procedure
if __name__ == '__main__':
TestApp().run()
When I run this and click on the Quit button, I get the following error:
builtins.KeyError: 'on_stop'
I should also note that if I comment out the on_stop function in class TestApp, the error persists. Any idea what is going wrong?
You're passing an instance of MenuScreen to App.stop(), which is causing your error as it expects an App instance. You can retrieve the running app and stop it like so:
class MenuScreen(Screen):
def exit(self):
App.get_running_app().stop()
Here is a solution for your problem:
# Declare both screens
class MenuScreen(Screen):
def exit(self):
self.manager.app.stop()
class TestApp(App):
def build(self):
# Create the screen manager
sm = ScreenManager()
# monkey patch the screen manager instance to add the app
sm.app = self
sm.add_widget(MenuScreen(name='menu'))
sm.add_widget(SettingsScreen(name='settings'))
return sm
though I do not think this is the canonical solution for your problem, as it is here involving monkey patching.
But what's wrong is that the stop() method you want to call exists only for an app instance, though the way you were handling it, you couldn't have access to the running App instance as it was declared after the code needing it.
So the solution is to move the ScreenManager build up inside the build method. And that's where I think my solution is not a good design: I'm basically adding a member to the ScreenManager by monkeypatching the instance, to give it a link to the App instance.
There you can call the close() function, and close your application gracefully.

Categories

Resources