Update widgets after specified time - python

I cannot understand how to update kivy screen.
This is my python file:
import kivy
kivy.require('1.1.1')
from kivy.app import App
from kivy.uix.widget import Widget
import time
class PongGame(Widget):
labe = ObjectProperty(None)
def settext(self,x):
self.labe.text = str(x)
print "DONE"
class PongApp(App):
def build(self):
game = PongGame()
game.settext(1)
time.sleep(3)
game.settext(5)
time.sleep(3)
game.settext(87)
time.sleep(3)
game.settext(5)
return game
if __name__ == '__main__':
PongApp().run()
this is my kv file
#:kivy 1.0.9
<PongGame>:
labe:lab
Label:
id: lab
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str("Hello")
When I run it all it freezes. Then all I see is a 5.
How can i get the others to show up?

def build(self):
game = PongGame()
game.settext(1)
time.sleep(3)
game.settext(5)
time.sleep(3)
game.settext(87)
time.sleep(3)
game.settext(5)
return game
Your problem is that sleep blocks the entire thread - your whole program just stops doing anything for the duration of the call. That includes any graphical interface that has been drawn, it can't update or even receive input because it's running in the same thread and doesn't get run again until your blocking function call has stopped.
Actually, you also have the problem that all these changes take place before the gui is even drawn (the game is displayed in the kivy window only after it's returned from build).
You have to instead think in terms of kivy's main loop and clock - in the background kivy is trying to run certain functions like touch detection and graphical updates as frequently as possible. If you run some code in this loop that takes a long time, none of the rest of kivy can work until your code terminates. This is normal in gui programming, you always have to be careful not to block the main program loop.
The easiest way in kivy to schedule something regularly is to create a function and hook into this event loop, telling the clock to run your function after some interval or after every repeat of some time period. In your case, you want to change the text every 3 seconds.
Here is a short example of how to achieve exactly what you want:
from kivy.app import App
from kivy.uix.widget import Widget
import time
from kivy.properties import ObjectProperty
from functools import partial
from kivy.lang import Builder
from kivy.clock import Clock
Builder.load_string('''
<PongGame>:
labe:lab
Label:
id: lab
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str("Hello")
''')
class PongGame(Widget):
labe = ObjectProperty(None)
def settext(self, x, *args):
self.labe.text = str(x)
print "DONE"
class PongApp(App):
def build(self):
game = PongGame()
game.settext(1)
Clock.schedule_once(partial(game.settext, 5), 3)
Clock.schedule_once(partial(game.settext, 87), 6)
Clock.schedule_once(partial(game.settext, 5), 9)
return game
if __name__ == '__main__':
PongApp().run()
There are a few important notes. One is that I used functools.partial() because you need to pass a function to Clock.schedule_once, and partial creates a function from an existing function (here game.settext) and some default arguments to use (here the numbers for the label). It was also important to add *args to PongGame.settext because the clock automatically passes some extra arguments that we don't care about.
If the meaning of that is not clear to you, experiment with these parameters to see what happens.

Related

How do I fix 'ValueError: callback must be a callable, got 18:19:46'

So I'm trying to get a good grasp on kivy and I want to create a basic clock app and I keep running into a problem when I go to update the clock label. I've looked at other threads on here and still cant find the right solution. I believe I am doing something wrong at Clock.schedule_interval but again, I'm trying to get a grasp on kivy and I'm not sure. Any help would be greatly appriciated
Python code
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.lang import Builder
from kivy.clock import Clock
import time
Builder.load_file('myApp.kv')
class ClockScreen(Widget):
def updateClock(self):
hour = time.strftime("%H")
minute = time.strftime("%M")
second = time.strftime("%S")
Clocktime = self.root.ids.clock.text
print(Clocktime)
newTime = self.root.ids.clock.text = f'{hour}:{minute}:{second}'
return newTime
class FirstApp(App):
def build(self):
return ClockScreen()
def on_start(self):
Clock.schedule_interval(ClockScreen.updateClock(self), 1)
FirstApp().run()
```
KV File
:
FloatLayout:
size: root.width, root.height
Label:
id: clock
text: "12:00"
Try
def on_start(self):
Clock.schedule_interval(ClockScreen.updateClock, 1)
Reason:
ClockScreen.updateClock is a callable (a function handle). This is also explained in the docs.
ClockScreen.updateClock(self) is actually calling the function that returns the time, and you cannot schedule a string (18:19:46, as per your error).
However, this doesn't say which instance of the ClockScreen gets updated (you are referencing the function via a class rather than a specific instance), so this may be more accurate for what you want
class FirstApp(App):
def build(self):
return ClockScreen()
def on_start(self):
cs = self.build()
Clock.schedule_interval(cs.updateClock, 1)

plt.figure() affecting kivy window?

I'm trying to develop a kivy app for my less-comp-savvy colleagues which wraps a nice GUI around some computations which I developed for a project; currently the rather odd problem I'm having is that the plt.figure() function I'm calling to graph some data with in matplotlib.plotly is causing the kivy app window to shrink. Locking the size with Config.set() doesn't disinhibit this action. Any idea why this is happening? Odder thing still, the shrinking seems to increase the resolution of the kivy app such that the window is more crisp and easier to read, which is actually kind of nice. Just trying to understand what's going on here and how to work with it. Below is an example which recreates the phenomena.
--GUI.py--
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
import threading
from calc import main
class Pop(Popup):
pass
class MetaLevel(GridLayout):
def dummy(self, *args):
App.get_running_app().pop.open()
threading.Thread(target=self.calculate, args=(args,)).start()
def calculate(self, *args):
main()
class graphics(App):
def build(self):
self.pop = Pop()
return MetaLevel()
if __name__ == "__main__":
graphics().run()
--calc.py--
def main():
import numpy as np
import matplotlib.pyplot as plt
from kivy.app import App
from pathos.multiprocessing import ProcessPool as Pool
grid = np.array([(m, n)
for m in np.arange(1, 100, 1)
for n in np.arange(1, 100, 1)])
def calc(grid):
var1 = grid[0]
var2 = grid[1]
y = var1*var2
return y
res = Pool().map(calc, grid)
fig = plt.figure(111, dpi=200)
print('done')
App.get_running_app().pop.dismiss()
# data output from res here
--graphics.kv--
# Custom button
<Button>:
font_size: 12
<MetaLevel>:
id: calculator
rows: 5
padding: 10
spacing: 10
BoxLayout:
height: 10
Label:
spacing: 10
text: 'test'
BoxLayout:
Button:
id: run_button
text: "Run"
on_release: root.dummy()
It sounds like something like the following might be happening:
you have a high dpi display
the kivy app is treating it as a low dpi display and letting the OS scale it up
when matplotlib creates a window it requests a different config from the window manager (no stretching)
the new config also applies to the Kivy window
I'm not sure exactly what the sequence of events would be, and this could actually arise from a bug somewhere, but it looks like a plausible reason for this to happen.
If this is the case, I don't know what the fix is. It would need some testing on the same OS with a comparable display setup. What OS are you using?

Python kivy code from book Kivy Blueprints

I am trying to follow the book "Kivy Blueprints" from Mark Vasilkov. On page 21 he introduces a function that updates the text of a label.
There are two files inside the project folder (see code below). Inside the class ClockApp the function update_time(self, nap) is defined. I am using Intellij Idea Community with the python plugin and the integrated development environment (IDE) tells me that nap is an unused parameter. If I remove nap as parameter I get an error update_time() takes 1 positional argument but 2 were given. How can I get rid of this dummy parameter?
# Source: Chapter 1 of Kivy Blueprints
# File: main.py
from time import strftime
from kivy.app import App
from kivy.clock import Clock
from kivy.core.window import Window
from kivy.utils import get_color_from_hex
Window.clearcolor = get_color_from_hex("#101216")
# from kivy.core.text import LabelBase
class ClockApp(App):
def update_time(self, nap):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(self.update_time, 1)
if __name__ == "__main__":
ClockApp().run()
There is also an additional clock.kv file
# File: clock.kv
BoxLayout:
orientation: "vertical"
Label:
id: time
text: "[b]00[/b]:00:00"
font_name: "Roboto"
font_size: 60
markup: True
the binding always passes additional information, for example in this case it sends us the exact time period in which the function was called. If you do not want to use it you can use a lambda method:
class ClockApp(App):
def update_time(self):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(lambda *args: self.update_time(), 1)
if you only want to silence the warning: "unused parameter" you could use the _:
class ClockApp(App):
def update_time(self, _):
self.root.ids.time.text = strftime("[b]%H[/b]:%M:%S")
def on_start(self):
Clock.schedule_interval(self.update_time, 1)

Running functions in kivy extremely frequently (Kivy Clock/FreeClock)

I am working on an app that takes in data from a Bluetooth device (that I handle simply through the serial module). That I know how to do. But giving it a new life in Kivy - displaying it - creates a problem of everything being synced with FPS. I want to run the function all the time in the background, hundreds of times a second. It's fair to say that out of 10000 incoming packets 10 might be useful. So if I would go by clock scheduling it would have to be (well) under 20 ms per cycle.
Simply put: how do I run one of the functions separately from the FPS? Is there a clean way of using the Free version of the clock for only one function and how is that achieved?
I want a simple solution, I can reinvent the wheel, but I don't want to.
Any help is appreciated, thank you.
You could use threading for this.
Here is a little example of using threading with kivy:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
from kivy.properties import NumericProperty
import threading
import time
Builder.load_string('''
<MyLayout>:
Label:
text: str(root.data)
''')
class MyLayout(BoxLayout):
data = NumericProperty(0)
count = 0
running = True
def __init__(self,**kwargs):
super(MyLayout,self).__init__(**kwargs)
Clock.schedule_once(self.after_init)
def after_init(self, dt):
threading.Thread(target=self.func).start()
def func(self):
while self.running:
self.data += 1
time.sleep(0.1)
class MyApp(App):
def build(self):
self.root = MyLayout()
return self.root
def on_stop(self):
self.root.running = False
MyApp().run()

Kivy popup running in separate thread

I am populating a treeview in Kivy that takes some time depending on how large it is.
In the case the tree is large and takes awhile, I would like to display a popup while it is populating so the user is aware the program has not frozen, and close this popup when the logic for populating the tree finishes.
Here is what I have come up with through some research on the topic, but the popup still seems to only come once the tree is finished populating:
def show(self, *args):
self.error_popup.open()
def populate_tree(self, model):
#Clock.schedule_once(self.error_popup.open())
popup_thread = threading.Thread(target=self.show())
popup_thread.start()
# order the dictionary for better user experience
ordered_data = collections.OrderedDict(sorted(model.items()))
# logic to populate tree
for county, value in ordered_data.items():
if county != "model_name":
# set initial county dropdowns in tree
county_label = self.treeview.add_node(TreeViewButton(text=str(county), on_press=self.edit_node))
i = 0 # keep count of rules
# add rules children to county
for rule_obj, rule_list in value.items():
for rule in rule_list:
i += 1
# set rule number in tree
rule_label = self.treeview.add_node(TreeViewButton(text='Rule ' + str(i), on_press=self.edit_node), county_label)
# add conditions children to rule
for condition in rule:
self.treeview.add_node(TreeViewButton(text=condition, on_press=self.edit_node), rule_label)
#Clock.schedule_once(self.error_popup.dismiss())
#somehow close popup_thread
I included a kivy Clock attempt in case that is more on the right track of what I am looking for, however currently it will just open the popup and never populate the tree. I am new to GUI programming and event callbacks, so any help is greatly appreciated.
I tried keeping the code short, if more is needed please let me know.
I built an app which does something similar to what you're doing (different computation, but as you said the point was it was time-consuming and you want to thread a popup that shows the app isn't crashed - it's just crankin' the numbers). What ended up working for me was to set up a button to execute a dummy function which toggles both the popup and the calculation. Run the popup first and then thread the calculation through the 'from threading import Thread' module to execute the computation on a separate thread.
Here's a working example. It's just sleeping for 5 seconds but you can stick your computation into that function and it should work just fine. What it does is opens the popup before the computation and closes the popup when the calculation is done. Also, you can stick a 'Loading.gif' file into the folder and it'll import that as your loading gif if you want to use something other than what kivy pulls up (which is essentially a loading gif for loading your Loading.gif which isn't loading because it's not there... haha). Also added an 'ABORT' button if your user gets tired of waiting.
Finally just as a side note, I've had difficulties getting the .kv file to build into the pyinstaller application bundeler, so just as a heads up, using the builder.load_string(KV) function is a good alternative for that.
from threading import Thread
from sys import exit
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.popup import Popup
from kivy.lang import Builder
KV = '''
<Pop>:
id:pop
title: ''
auto_dismiss: False
padding: 10
spacing: 10
BoxLayout:
BoxLayout:
padding: 10
spacing: 10
orientation: 'vertical'
Label:
font_size: 22
size_hint_y: None
text_size: self.width, None
height: self.texture_size[1]
text: "Process is currently running."
Label:
id: error_msg
size_hint_x: 0.3
text: ''
BoxLayout:
orientation: 'vertical'
Button:
background_color: (1,0,0,1)
text: "ABORT"
on_press: root.sysex()
AsyncImage:
source: 'Loading.gif'
<MetaLevel>:
rows: 1
cols: 1
Button:
text: 'RUN'
on_release: root.dummy()
'''
Builder.load_string(KV)
class MetaLevel(GridLayout):
def dummy(self, *args):
App.get_running_app().pop.open()
Thread(target=self.calculate, args=(args,), daemon=True).start()
def calculate(self, *args):
import time
time.sleep(5)
App.get_running_app().pop.dismiss()
class Pop(Popup):
def sysex(self):
exit()
class Cruncher(App):
def build(self):
self.pop = Pop()
return MetaLevel()
if __name__ == "__main__":
Cruncher().run()
Were you able to get this sorted?
I think it works if you use the thread for populating the tree rather than using it for showing the popup. After populating the tree, in the same thread you can close the pop up using Popup.dismiss()
main.py file
from kivy.app import App
from kivy.uix.popup import Popup
from kivy.uix.label import Label
from kivy.uix.boxlayout import BoxLayout
import time, threading
class popupTestApp(App):
def waitSec(self):
time.sleep(5)
self.p.dismiss()
def popUpFunc(self):
self.p = Popup(title='Test Popup', content=Label(text='This is a test'), size_hint=(None,None), size=(400,400))
self.p.open()
popUpThread = threading.Thread(target=self.waitSec)
popUpThread.start()
if __name__ == '__main__':
popupTestApp().run()
popuptest.kv file
BoxLayout:
BoxLayout:
id:LeftPane
Button:
id:MyButton
text:'Pop it up!'
on_release:app.popUpFunc()
BoxLayout:
id:RightPane
Label:
text: 'Another Pane'
Take a look at the below link where this is explained well.
Building a simple progress bar or loading animation in Kivy

Categories