kivy - change things twice with one button click - python

wonder if anyone can help me out.
I am stuck with a problem. I want to change a buttons background_normal TWICE by clicking another button.
So for example first change to yellow than wait 2 seconds (time.sleep(2)) and then change to red.
I tried to make a simple functions that does this. But the problem is that it does not update the first change of the background_normal. It only displays the latest change.
Tried to split it into two functions, one changes it to the first color, the other one changes it to the second color. But still it does not display anything but the latest change.
Is there any possibility to solve this?
Update:
My code is ~2k lines so i will post a short form of my problem
class Game(FloatLayout):
def firstaction(self):
#change button1 to yellow and wait 2 seconds
def secondaction(self):
#change button1 to red
class GameApp(App):
def build(self):
return Game
kv file looks something like
button2:
on_press: Game.firstaction
on_press: Game.secondaction

You talk about colors so you should use background_color instead of background_normal. Going to the problem, the cause is time.sleep() because it blocks the event loop, the event loop helps the GUI to update due to OS or user events. The solution is to use Clock.
from kivy.app import App
from kivy.uix.button import Button
from kivy.clock import Clock
class MyButton(Button):
def on_press(self):
self.background_color = (1,1,0,1)
fn = lambda dt: setattr(self, "background_color", (1,0,0,1))
Clock.schedule_once(fn, 2) # <--- 2 seconds
class MyApp(App):
def build(self):
button = MyButton(text='Hello World')
return button
if __name__ == '__main__':
MyApp().run()

Related

Why does the button print twice? (Kivy Python)

I'm trying to make a button and I'm doing this print as a test, I'm not why it prints twice?
I'm new to Kivy so I'm not too sure on what I'm doing, any help is appreciated thank you.
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
def bannedBooksMain(self, instance):
print('The button test is being pressed')
class mainApp(GridLayout):
def __init__(self, **kwargs):
super(mainApp, self).__init__(**kwargs)
self.cols = 2
btn1 = Button(text='Home Page')
btn1.bind(state=bannedBooksMain)
self.add_widget(btn1)
class MyApp(App):
def build(self):
return mainApp()
MyApp().run()
btn1.bind(state=bannedBooksMain)
This (state=) will initiate a callback whenever the state property changes, this happens for both button press and release, which is why you're seeing it twice
You should use on_press= or on_release= instead, depending on when you want the action fired.
Personally, I prefer the on_release= combined with always_release being false. That way, if I inadvertently press the button, I can move the mouse point away from the button before releasing, and there will be no callback.
See here for further detail.

generating random numbers on a kivy label

I want to show randomly generated numbers on a kivy label which is similar to a countdown animation.
prototype.py (python file):
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
import random
import time
class RootWidget(BoxLayout):
def do(self):
a = 0
total = 5
user = self.ids.name
while a <= 10:
randnum = random.randrange(total)
user.text = str(randnum)
a+=1
class prototypeApp(App):
def build(self):
return RootWidget()
if __name__ == "__main__":
no=prototypeApp()
no.run()
prototype.kv (kv file):
<RootWidget>:
orientation:'vertical'
Label:
id: name
text:''
font_size:70
height:70
Button:
text:'click me'
on_release:app.root.do()
size_hint_y:.1
There was no error but on the label, it displays only the last number
of the randomly generated numbers.
But I expected the numbers in the loop to flow smoothly on the label not show me only the last number in the loop.
Your loop in the do() method is run on the main thread, which is the same thread that updates the GUI. Since it is a single thread, the GUI cannot be updated until your do() method completes, which means that only the last random number gets displayed in the GUI.
To fix that, run that loop in a separate thread (see threading). And you will need to execute the self.ids.name.text = str(randnum) back on the main thread (consider using Clock to accomplish that).
You will probably want to add a sleep in the loop, otherwise it will probably happen too fast to see the intermediate numbers.

Text based game Kivy- text not updating

I am currently making a text based game using Python, and I have been wanting to use Kivy to make a Graphical Interface for it. I have been unable to get it to work so far though.
The reasoning is that I have changed from what was Print and Input to self.label1 = Label(text='Hello world') etc (There is multiple variables- label2, 3 and 4.) and then for the input, a text input box, which the input is used by a function when the button is pressed (Currently the textbox isn't involved, since I am first just testing whether the button works.). The problem is, I need a way to update the text displayed with the new value. For example I would like label1 to change to "These are the controls". But when a button is clicked, changes don't happen- I would like the GUI to be updated with the new text, through the changing of the values of the label variables. I believe that since those are being returned, the code above no longer loops through. The ideas I've been given, is to put the different sections into functions, or to use threading. Does anyone have any tips to push me in the right direction. I understand it may be too much be to ask, if so I'll continue to look for a solution myself. I can show some of the code if needed.
import kivy.uix.boxlayout
import kivy.uix.textinput
import kivy.uix.label
import kivy.uix.button
from kivy.app import App
from random import shuffle
import time
from kivy.uix.button import Button
from kivy.clock import Clock
alive = 1
buttonPressed = 0
class SimpleApp(App):
def build(self):
global alive
global buttonPressed
donext = 0
alive = 1
def callback(self):
global buttonPressed
buttonPressed = 1
self.label1 = kivy.uix.label.Label(text="")
self.label2 = kivy.uix.label.Label(text="")
self.label3 = kivy.uix.label.Label(text="You have found yourself in a dungeon, somewhere is your escape path, will you make it out, and if so, what with?")
self.label4 = kivy.uix.label.Label(text="")
print(buttonPressed)
if buttonPressed == 1:
print("Has been pressed should work theoretically")
self.label1 = kivy.uix.label.Label(text="These are the basic controls-")
self.label2 = kivy.uix.label.Label(text="The controls-")
self.label3 = kivy.uix.label.Label(text="A- approach enemy/ attack enemy")
self.label4 = kivy.uix.label.Label(text="C- Go to chest")
print("Press enter to continue.")
self.boxLayout = kivy.uix.boxlayout.BoxLayout(orientation="vertical")
self.boxLayout.add_widget(self.label1)
self.boxLayout.add_widget(self.label2)
self.boxLayout.add_widget(self.label3)
self.boxLayout.add_widget(self.label4)
self.btn1 = Button(text='Hello world 1', on_press=callback)
self.boxLayout.add_widget(self.btn1)
return self.boxLayout # Causes script not to continue
if __name__ == "__main__":
simple = SimpleApp()
simple.run()
If you have been advised to use threads it seems that your advisor does not know about GUI, in the GUIs the tasks are done asynchronously through events, that is, the GUI will provide you with methods to indicate when something has happened in the GUI, for example the event on_press notifies you when the button is pressed, so they connect a signal to that event. On the other hand the GUIs have a high component of object-oriented programming, event-oriented programming, and in the .kv is a declarative language, so I recommend you read about those concepts and for it kivy offers a great documentation and examples, review them. If you want to update a Label at least it must be accessible in the whole class, so it must be an attribute of the class and use the text property, on the other hand if you want to show a text of several lines use \n to indicate that there is a jump of line.
Considering the above, the solution is as follows:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
class SimpleApp(App):
def build(self):
self.label = Label(text="You have found yourself in a dungeon,\nsomewhere is your escape path,\nwill you make it out, and if so, what with?")
self.button = Button(text="Press Me", on_press=self.on_clicked, size_hint=(1.0, None))
layout = BoxLayout(orientation="vertical")
layout.add_widget(self.label)
layout.add_widget(self.button)
return layout
def on_clicked(self, instance):
self.label.text = "These are the basic controls-\nThe controls-\nA- approach enemy/ attack enemy\nC- Go to chest"
if __name__ == "__main__":
SimpleApp().run()

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.

Kivy: Label text does not update during for-loop

I have an issue when I try to update a label text during a for-loop. There are similar entries (e.g.: Update properties of a kivy widget while running code) but they do not seem to fit my issue exactly (or I missed the point…).
I run following code:
*.py:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
#from time import sleep
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
for i in range(1,10):
self.tobeupd = str(i)
print(self.tobeupd)
input('Write something: ') # new line, see edit below
#sleep(0.5)
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
*.kv
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
Whereas the ‘print’ statement updates the shell regularly, the label text updates at the end of the for-loop only.
Can anyone explain to me why Kivy works this way and how I can overcome this problem?
EDIT: According to PM2Ring and Gugas, I changed the code in order to avoid the sleep-function. The problem remains if I ask the user to enter something before the loop can be continued. The values are updated in the shell but not on the label.
You can use threading for this.
When you do a loop or wait for an input in kivy, the main thread is waiting, and nothing will update on the app. threading will prevent that.
Use threading to make another thread besides the main thread.
Example:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import StringProperty
from kivy.lang import Builder
import threading
Builder.load_string('''
<MyBox>:
orientation: 'horizontal'
cols: 2
Label:
text: root.tobeupd
Button:
text: 'Start Update'
on_release: root.upd_ltxt()
''')
class MyBox(BoxLayout):
tobeupd = StringProperty()
def __init__(self,*args,**kwargs):
super(MyBox,self).__init__(*args,**kwargs)
self.tobeupd = '#'
def upd_ltxt(self):
threading.Thread(target=self.update_label).start()
def update_label(self):
for i in range(1,10):
print(self.tobeupd)
self.tobeupd = str(i)
input('Write something: ') # new line, see edit below
class updApp(App):
def build(self):
return MyBox()
if __name__ == '__main__':
updApp().run()
Now its worth mentioning that you can keep pushing the button and start threads, even if the first did not finish yet. This might be an unwanted behavior.
This can be prevented by disabling the button in the beginning of the thread, and enabling it again at the end.
Give the button an id in kv:
Button:
id: updatebutton
text: 'Start Update'
on_release: root.upd_ltxt()
And in the thread do like this:
def update_label(self):
self.ids.updatebutton.disabled = True
for i in range(1,10):
self.tobeupd = str(i)
input('Write something: ')
self.ids.updatebutton.disabled = False
You can also use Kivys clock Class, which is an Event dispatcher. It will schedule an event, which is a function. For example, updating your labels text.
from kivy.clock import Clock
def to_be_called_back(self,dt):
print("This function should be periodically executed")
def do_the_loop(self):
Clock.schedule_interval(self.to_be_called(),0.5)
Here the function to_be_called() will be called every 0.5 seconds. The dt variable stands for deltatime and is just apparently needed by the Clock class (without it, it made problems with my code)
I'd still put the do_the_loop() function into a separate thread. But thats what kivy provides for it. If you want to know more about the clock Class head over here.
I believe all you need to do is put
root.update()
whenever you want the GUI to update, so therefore in the loop, after the text change has been made. Works in tkinter like a charm. You can also restrict the update to a specific text area by changing root (or main) to the name of the text area.

Categories

Resources