In Kivy, if you have a ScreenManager with multiple screens already added, you can not change screens with the switch_to function, as that also tries to first add the Screen to the Screenmanager, before switching to it.
Instead, you switch screens simply by setting the current property, like this:
screenmanager.current = "new_screen_name"
The problem is, I wish my SplashScreen to automatically transition to MainScreen after a short delay, using Clock.schedule_once(). But that can only take a function as a parameter, so I I have to write a another function just to change screen, like this:
class MyScreenManager(ScreenManager):
def __init__(self, **kwargs):
super(MainScreenManager, self).__init__(**kwargs)
Clock.schedule_once(switch_to_main_screen, 3)
def switch_to_main_screen(self, *args):
self.current = "main_screen
I'm just wondering if there is a more efficient way to do this? e.g can I somhow directly set the "self.current" property in Clock.schedule_once? Or, generally speaking, is there a better way to do this simple task? (i.e have a parameter be set a few seconds in the future)
You can use the setattr() python built-in. Try replacing:
Clock.schedule_once(switch_to_main_screen, 3)
with:
Clock.schedule_once(lambda dt: setattr(self, 'current', 'main_screen'), 3)
See the documentation.
Consider this, its my ready sheet in kivy:
import #what u need
Builder.load_file('the.kv')
class fscreen(Widget):
def __init__(self, **kwargs):
super().__init__(**kwargs)
class secscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class thscreen(Widget):
def __init__(self,**kwargs):
super().__init__(**kwargs)
pass
class theapp(App):
def build(self):
self.screenm = ScreenManager()
self.fscreen = fscreen() ## as default this is my
screen = Screen(name = "first screen") # my first screen
screen.add_widget(self.fscreen) # if i want second screen
self.screenm.add_widget(screen) # as first i move this below
## secscreen its waay
self.secscreen = secscreen() ### efficient
screen = Screen(name = "secondscreen")
screen.add_widget(self.secscreen)
self.screenm.add_widget(screen)
self.thscreen = thscreen()
screen = Screen(name = "thirdscreen")
screen.add_widget(self.thscreen)
self.screenm.add_widget(screen)
return self.screenm
if __name__ == "__main__":
theapp = theapp()
theapp.run()
This way the fscreen is by default the first screen after the presplash
Related
With Kivy, I understand we can use set the text of a label to a StringProperty() object, so whenever that string is updated, the label will automatically show the updated text.
My minimal example code, works fine, will show "apple" then "banana" one second later:
#test.kv
<MyLabel>:
font_size: 30
text: self.examppleStringProperty
#test.py
class MyLabel(Label):
examppleStringProperty = StringProperty("apple")
def on_kv_post(self, base_widget):
Clock.schedule_interval(lambda dt : self.runlater(), 1)
def runlater(self):
self.examppleStringProperty = "banana"
class TestApp(App):
def build(self): return MyLabel()
Question: How do I do exactly the same, but for a Float? i.e have the label automatically update it's next, whenever the value of the Float is changed?
I have a module that updates a Float value with the current room temperature, which I would just like to show on a label in Kivy, but I'm not sure how to bind it "automagically".
I have tried NumericProperty(), but of course I can't set a label.text to a NumericProperty() object, as it is not a string.
For example, the following code does not work, simply never updates the label text to the number 42, because the NumericProperty isn't bound to the label text in anyway.
class MyLabel(Label):
examppleStringProperty = StringProperty("apple")
exampleNumericProperty = NumericProperty(0)
def on_kv_post(self, base_widget):
self.text = str(self.exampleNumericProperty)
Clock.schedule_interval(lambda dt : self.runlater(), 1)
def runlater(self):
self.exampleNumericProperty = 42
class TestApp(App):
def build(self): return MyLabel()
Just looking for any good way to keep a Label automatically updated with the current value of a Float..
I have found a way to do it (Keep a label.text updated with the value of a Float), by using an "on_NumericProperty" function.
But I would appreciate any advice, if this is good or a bad design - or any suggested alternatives.
class MyLabel(Label):
exampleStringProperty = StringProperty("no data yet")
exampleNumericProperty = NumericProperty(0)
def on_exampleNumericProperty(self, *args):
self.exampleStringProperty = str(self.exampleNumericProperty)
def on_kv_post(self, base_widget):
Clock.schedule_interval(lambda dt : self.runlater(), 3)
def runlater(self):
self.exampleNumericProperty = 42
class TestApp(App):
def build(self): return MyLabel()
I got two classes that have two methods that I want to be able to access from different classes, so I inherited from App in both classes to use get_running_app with kivy properties (hope you understand what I'm saying). Now I have a problem because when I try calling App.get_running_app().method(), it only looks for the method in the first class, but the method is in the second class. I know that inheriting from App in two classes isn't really good but that was at the time the easiest way because I didn't have to call get_running_app for both classes. I had like the main class (where the def build is) and one just for main screen and I needed to call a method in main screen class. Now I have that method and the method in the main class.
So my question is, is there any way of distinguishing two classes with get_running_app()? Or could you give me some tips on how to solve this problem in different way if you have some idea?
Here is the code like you asked, just forget what this code does, I simplified it like this just to show the core problem and the error it doesn't matter what does.
from kivy.app import App
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
from kivy.properties import NumericProperty
class GameWidget(Screen, App):
num = NumericProperty(0)
change_value = ObjectProperty(None)
def __init__(self, **kwargs):
super(Screen, self).__init__(**kwargs)
self.num = 0
self.button = Button(text="go back", size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y':0.4},
on_press=lambda x: self.go_main_screen())
self.add_widget(self.button)
self.label = Label(text=str(self.num), size_hint=(0.2, 0.2), pos_hint={'x':0.2, 'y':0.2})
self.add_widget(self.label)
def change_value(self):
self.num += 1
self.label.text = str(self.num)
def go_main_screen(self):
self.manager.transition.direction = 'right'
self.manager.current = 'main'
class Main_screen(Screen):
def __init__(self, **kwargs):
super(Main_screen, self).__init__(**kwargs)
self.button = Button(text='click me', size_hint=(0.2, 0.2), pos_hint={'x': 0.4, 'y': 0.4},
on_release=lambda x: self.call_methods())
self.button2 = Button(text='go to next screen', size_hint=(0.2, 0.2), pos_hint={'x':0.4, 'y':0.6},
on_release=lambda x: self.ch_screen())
self.add_widget(self.button)
self.add_widget(self.button2)
def ch_screen(self):
self.manager.transition.direction = 'left'
self.manager.current = 'game'
def call_methods(self):
App.get_running_app().change_value()
App.get_running_app().print_some_stuff()
class Manager(ScreenManager):
pass
class MyApp(App):
print_some_stuff = ObjectProperty(None)
def build(self):
sm = Manager()
sm.add_widget(Main_screen(name='main'))
sm.add_widget(GameWidget(name='game'))
return sm
def print_some_stuff(self):
print('you clicked')
MyApp().run()
The App.get_running_app() method will always return the same object, which should be the currently running App. Since you can only have one running App, that should always be the MyApp instance in your code. However, I believe you have uncovered a bug in the App code, because App.get_running_app() actually returns the last created App, not the currently running App. So, in your code, it will always return the GameWidget instance. That bug will not affect a correctly structured code.
The following problems/solutions appear in your code:
You should not have more than one App class in your App. Change:
class GameWidget(Screen, App):
to:
class GameWidget(Screen):
You can access Screens using the get_screen() method of the ScreenManager, which is your App root. So you can chaange:
App.get_running_app().change_value()
to:
App.get_running_app().root.get_screen('game').change_value()
I have a dynamic Screen which is generated based on a button you clicked on another screen. Issue is dat every time I enter the Screen, the buttons are regenerated and added to the existing buttons.
The reason is that I use the on_enter method, but I don't know how I can use on_kv_post for example, as these events happen on starting the app.
How can I initialise the screen every time I return to this screen?
class ClientEnvsGrid(Screen):
envProp = StringProperty('')
def __init__(self, **kwargs):
super(ClientEnvsGrid, self).__init__(**kwargs)
def on_enter(self, *args):
clientProp = self.manager.get_screen('clientlist').clientProp
try:
client_filepath = os.path.join('clients', clientProp, "environments.json")
client_file = open(client_filepath)
clientdata = json.loads(client_file.read())
print(clientdata)
self.ids.clientlabel.text = clientdata["clientname"]
for envs in clientdata["environments"]:
print(envs["name"])
envbutton = Button(text=envs["name"])
envbutton.bind(on_press=lambda *args: self.pressed('envbtn', *args))
self.ids.environments.add_widget(envbutton)
except:
print("No client data found")
self.manager.current = 'clientlist'
def pressed(self, instance, *args):
self.envProp = args[0].text
I've managed to fix it to include clear_widgets in the environments GridLayout in the on_leave event.
def on_leave(self, *args):
self.ids.environments.clear_widgets()
I'm trying to figure out how to bind a button that's been laid out using Kivy language to a function. I've seen plenty of answers when laying out buttons in Python language. But what about once everything is in place and you're now referencing via a custom class that inherits from Button?
On press, the code below throws the error TypeError: show() takes 1 positional argument but 2 were given and crashes the program.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.show)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.unbind(on_press=self.hide)
self.bind(on_press=self.show)
The kivy code that calls the function you set in .bind() is passing an argument which your function isn't prepared for. It's been a while since I last used kivy, so I can't be sure, but I think the event details are passed to the function.
As such, your definitions for event handlers should look like this:
def show(self, event):
...
def hide(self, event):
...
If you're curious, you could print(event) within those functions, to see what's being sent in.
The answer is to include the class name, in this case TimerButton, in the function definitions. This is a concept I don't completely understand since the function is defined within the scope of the TimerButton class. But hey, it works.
class TimerButton(ButtonBehavior, Image):
timer_container = ObjectProperty(None)
client_scoreboard = ObjectProperty(None)
def __init__(self, **kwargs):
super(TimerButton, self).__init__(**kwargs)
self.bind(on_press=self.show)
self.bind(on_release=self.changeImage)
def show(self):
print('hi there')
self.source = 'assets/mainViewTimerButtonPressed.png'
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.right - self.timer_container.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.hide)
def changeImage(self):
self.source = 'assets/mainViewTimerButton.png'
def hide(self):
import kivy.animation as Animate
anim = Animate.Animation(
x=self.client_scoreboard.width,
y=self.timer_container.y,
duration=0.25)
anim.start(self.timer_container)
self.bind(on_press=self.show)
got a little problem. So Iam trying to create my own widget, and i have succeed, only except for setting its size and position right(to be same as its parrent).
class Story(App):
def build(self):
return MyWidgets().init()
The app has GridLayout as a holder, into which i want to pass the StoryWidget
class MyWidgets(object):
def init(self):
root = GridLayout(cols=2,rows=1)
root.add_widget(StoryWidget())
root.add_widget(Button())
return root
Story Widget goes as this:
class StoryWidget(Widget):
def __init__(self,**kwargs):
super(StoryWidget, self).__init__(**kwargs)
topLayout=BoxLayout(orientation = "vertical")
topLayout.add_widget(Button(text="first"))
topLayout.add_widget(Button(text="second"))
self.add_widget(topLayout)
If I try to get the background color to it, it works fine:
with self.canvas.before:
Color(.9,.9,1)
self.Backgroud = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.repaint,size=self.repaint)
self.bind(pos=self.resize,size=self.resize)
def repaint(self,*args):
self.Backgroud.pos = self.pos
self.Backgroud.size = self.size
The whole firs column of root(Gridlayout) gets correctly repainted in white,
but the Widget stands on defaut pos(0,0) and default size(100,100).
From what i know, its because Widget cant handle these things. Layout should do it automatically somehow. As can be seen, the root widget of StoryWidget is layout. I do not know why it`s not working. I tried to inherit from the Layout instead of Widget, but still nothing. Any advice? Thanks!
Alright, i have figured it out, turns out i forgot to set appropriate attributes. So Iam now using Gridlayout instead of BoxLayout, in which case it needs cols and rows so it should now look like this:
class StoryWidget(GridLayout):
def __init__(self,**kwargs):
self.cols=1
self.rows=1
super(StoryWidget, self).__init__(**kwargs)
topLayout=BoxLayout(orientation = "vertical")
topLayout.add_widget(Button(text="first"))
topLayout.add_widget(Button(text="second"))
self.add_widget(topLayout)
with self.canvas.before:
Color(.9,.9,1)
self.Backgroud = Rectangle(pos=self.pos,size=self.size)
self.bind(pos=self.repaint,size=self.repaint)
self.bind(pos=self.resize,size=self.resize)