I am trying to do an asynchronous label update in Kivi gui.
When I run the function via loop.run_until_complete(self.update()) I get only output to the console, the application window is not updated. What am I missing?
# async kivy app example
import kivy
kivy.require('2.1.0') # replace with your current kivy version !
from kivy.app import App
from kivy.app import async_runTouchApp
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.button import Button
from kivy.uix.label import Label
import asyncio, time
class RootWidget(BoxLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
btn = Button(text='Run Async func')
btn.fbind('on_press', self.async_callback)
self.lbl = Label(text = 'Click on the button!')
self.add_widget(btn)
self.add_widget(self.lbl)
async def update(self):
async for i in async_func():
print(i)
self.lbl.text = str(i)
def async_callback(self, *args):
loop = asyncio.get_event_loop()
loop.run_until_complete(self.update())
async def async_func():
for i in range(0,100):
yield i
await asyncio.sleep(1)
class AsyncApp(App):
def build(self):
return RootWidget()
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(async_runTouchApp(AsyncApp().run(), async_lib='asyncio'))
loop.close()
Related
when I press on a button i would like run a function everey minute.
I am using sched.schedulerbut my kivy app crash.
s.enter(60, 1, self.graph_data, (s,))
s.run()
Someone can help me ?
You can use the Clock object provided by kivy:
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.clock import Clock
class TestApp(App):
def build(self):
self.timer = None
btn1 = Button(text='Click to start')
btn1.bind(state=self.callbackStart)
btn2 = Button(text='Click to stop')
btn2.bind(state=self.callbackStop)
bl = BoxLayout()
bl.add_widget(btn1)
bl.add_widget(btn2)
return bl
def callbackStart(self, instance, value):
print('My button1 <%s> state is <%s>' % (instance, value))
if self.timer is not None:
Clock.unschedule(self.timer)
self.timer = Clock.schedule_interval(self.timedAction, 0.5)
def callbackStop(self, instance, value):
print('My button2 <%s> state is <%s>' % (instance, value))
Clock.unschedule(self.timer)
def timedAction(self, dt):
print("Repeat")
if __name__ == '__main__':
TestApp().run()
You should also be able to do something like this by using the threading build-in module with the Thread and Event objects.
import kivy
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
import threading
class MyTimedAction(threading.Thread):
def __init__(self, event):
threading.Thread.__init__(self)
self.stopped = event
def run(self):
while not self.stopped.wait(0.5):
print("Periodic action performed")
class TestApp(App):
def build(self):
# Create the stopTimer event. Initialize as stopped.
self.stopTimer = threading.Event()
self.stopTimer.set()
btn1 = Button(text='Click to start')
btn1.bind(state=self.callbackStart)
btn2 = Button(text='Click to stop')
btn2.bind(state=self.callbackStop)
bl = BoxLayout()
bl.add_widget(btn1)
bl.add_widget(btn2)
return bl
def callbackStart(self, instance, value):
print('My button1 <%s> state is <%s>' % (instance, value))
if self.stopTimer.isSet(): # (Eventually) avoid duplicates if timer already started
self.stopTimer.clear()
thread = MyTimedAction(self.stopTimer)
thread.start()
def callbackStop(self, instance, value):
print('My button2 <%s> state is <%s>' % (instance, value))
self.stopTimer.set()
if __name__ == '__main__':
TestApp().run()
The examples above assumes that if the timer is already started it doesn't start again another timed job on a new thread.
The brief GUI code is just for demo purpose.
Button Start alarm starts ringing of alarm. I want to stop ringing by button Stop alarm. I don't know to influece running program. How must I repair function stop_alarm?
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
import winsound
class ControlPanel(BoxLayout):
def __init__(self, **kwargs):
# make sure we aren't overriding any important functionality
super(ControlPanel, self).__init__(**kwargs)
self.alarm_status = True
self.orientation = "vertical"
butOn = Button(text = "Start alarm: ", on_release = self. start_alarm)
butStop = Button(text = "Stop alarm: ", on_release = self.stop_alarm)
self.add_widget(butOn)
self.add_widget(butStop)
def start_alarm(self, obj):
while self.alarm_status == True:
winsound.PlaySound("alarm.wav", winsound.SND_FILENAME)
def stop_alarm(self, obj):
self.alarm_status = False
class LifeApp(App):
def build(self):
return ControlPanel()
if __name__ == '__main__':
LifeApp().run()
The main problem with your code is that you have a while loop on your main GUI thread. So what you need to do is to run the start_alarm on a different thread than your main GUI thread for the GUI thread to be responsive.
As for not being able to play alarm next time, you didn't set the alarm_status flag to True again.Once you have that you can start and stop the sound as many times you want.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.widget import Widget
import time
import winsound
import threading
class ControlPanel(BoxLayout):
def __init__(self, **kwargs):
# make sure we aren't overriding any important functionality
super(ControlPanel, self).__init__(**kwargs)
self.alarm_status = True
self.orientation = "vertical"
butOn = Button(text = "Start alarm: ", on_release = self.start_thread)
butStop = Button(text = "Stop alarm: ", on_release = self.stop_alarm)
self.add_widget(butOn)
self.add_widget(butStop)
def start_alarm(self, obj):
while self.alarm_status == True:
winsound.PlaySound("alarm.wav", winsound.SND_FILENAME)
#Set Alarm_Status True so that next time it works
self.alarm_status = True
def stop_alarm(self, obj):
self.alarm_status = False
#Function to run your start_alarm on a different thread
def start_thread(self,obj):
t1 = threading.Thread(target=self.start_alarm,args=(obj,))
t1.start()
class LifeApp(App):
def build(self):
return ControlPanel()
if __name__ == '__main__':
LifeApp().run()
Hope this helps!
So I want to build a kivy program that basically just countdown a certain minutes and seconds.
this is how far I have come:
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
from datetime import datetime, date, time
class IncrediblyCrudeClock(Label):
a = time(0, 1, 1)
def update(self, *args):
self.text = str(self.a)
print(str(self.a))
self.a = datetime.combine(date.today(), self.a) - datetime.combine(date.today(), time(0,0,1))
class TimeApp(App):
def build(self):
crudeclock = IncrediblyCrudeClock()
Clock.schedule_interval(crudeclock.update, 1)
return crudeclock
if __name__ == "__main__":
TimeApp().run()
the problem is that when I try to deduct the old time from the new time, sothat I have 1 second less displayed, I get the following error:
self.a = datetime.combine(date.today(), self.a) datetime.combine(date.today(), time(0,0,1))
TypeError: combine() argument 2 must be datetime.time, not datetime.timedelta
this makes me think that, after the first sustraction, a is now not a "time" object anymore, but a "timedelta" wich unfortunatly can not be deducted.
any help would be great!
There is a simpler approach if you just need a countdown. You can use kivy's Animation class which is described very nicely by #inclement on youtube.
So here's the code (main.py and time.kv):
main.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.properties import StringProperty, NumericProperty
class IncrediblyCrudeClock(Label):
a = NumericProperty(5) # seconds
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
def finish_callback(animation, incr_crude_clock):
incr_crude_clock.text = "FINISHED"
self.anim.bind(on_complete=finish_callback)
self.anim.start(self)
class TimeApp(App):
def build(self):
crudeclock = IncrediblyCrudeClock()
crudeclock.start()
return crudeclock
if __name__ == "__main__":
TimeApp().run()
time.kv
<IncrediblyCrudeClock>
text: str(round(self.a, 1))
Enjoy! :)
Update:
The OP requested a solution without a kv-file -- so here is one:
from kivy.app import App
from kivy.uix.label import Label
from kivy.animation import Animation
from kivy.properties import StringProperty, NumericProperty
class IncrediblyCrudeClock(Label):
a = NumericProperty(5) # seconds
def start(self):
Animation.cancel_all(self) # stop any current animations
self.anim = Animation(a=0, duration=self.a)
def finish_callback(animation, incr_crude_clock):
incr_crude_clock.text = "FINISHED"
self.anim.bind(on_complete=finish_callback)
self.anim.start(self)
def on_a(self, instance, value):
self.text = str(round(value, 1))
class TimeApp(App):
def build(self):
crudeclock = IncrediblyCrudeClock()
crudeclock.start()
return crudeclock
if __name__ == "__main__":
TimeApp().run()
I'd like to have a kivy app function as a launcher for other kivy apps, depending on input. The way I implemented it below is obviously not working (because the kv file gets reloaded and its styles reapplied, thus adding more and more buttons), and there also seems to be some recursion as the trace suggests when I hit Esc to exit.
I do get the warning that app1.kv is loaded multiple times, however, in the documentation for App.load_kv() it says
This method is invoked the first time the app is being run if no
widget tree has been constructed before for this app.
This implies to me that it should be possible to run() an app multiple times?
Here is my code:
main.py
from kivy.uix.widget import Widget
from kivy.uix.gridlayout import GridLayout
from kivy.app import App
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.lang import Builder
class OutsideApp(App):
current_app = ObjectProperty(None)
def build(self):
Clock.schedule_interval(self.update, 3)
return Widget()
def update(self, dt):
if isinstance(self.current_app, App):
self.current_app.stop()
if isinstance(self.current_app, App1):
self.current_app = App2()
else:
self.current_app = App1()
self.current_app.run()
class App1(App):
pass
class App2(App):
def build(self):
gl = Builder.load_string("<SequencesGame#GridLayout>:\n cols: 2\n Button:\n text: \"hello 2\"\nSequencesGame:")
return gl
if __name__ == '__main__':
oa = OutsideApp()
oa.run()
app1.kv
#:kivy 1.0.9
<SequencesGame#GridLayout>:
cols: 2
Button:
text: "hello 111"
SequencesGame:
This seems to be an issue even if apps are not nested:
main2.py
from kivy.app import App
from kivy.clock import Clock
from kivy.logger import Logger
from kivy.lang import Builder
class App1(App):
pass
class App2(App):
def build(self):
return Builder.load_string("<SequencesGame#GridLayout>:\n cols: 2\n Button:\n text: \"hello 2\"\nSequencesGame:")
current_app = None
def switch(*args):
global current_app
if isinstance(current_app, App):
current_app.stop()
if isinstance(current_app, App1):
current_app = App2()
else:
current_app = App1()
current_app.run()
if __name__ == '__main__':
Clock.schedule_interval(switch, 2)
switch()
In the end I followed #inclement's suggestion of using multiprocessing. Here is a basic implementation (without communication through Pipes or Queues):
import sys
import multiprocessing
from kivy.app import App
from kivy.lang import Builder
from time import sleep#, localtime, time
def str_to_class(str):
return reduce(getattr, str.split("."), sys.modules[__name__])
class App1(App):
def build(self):
return Builder.load_string("BoxLayout:\n Button:\n text: \"hello 1\"")
class App2(App):
def build(self):
return Builder.load_string("BoxLayout:\n Button:\n text: \"hello 2\"")
class Wrapper(object):
def __init__(self, *kargs, **kwargs):
super(Wrapper, self).__init__()
self.running_app = None
self.next_app = 'App1'
def change_running_app(self, name='App1'):
if self.running_app is not None:
self.running_app.terminate()
self.running_app = multiprocessing.Process(target=self.run_app, kwargs={'name':name})
self.running_app.start()
def run_app(self, name='App1'):
app = str_to_class(name)()
app.run()
def swap(self):
self.change_running_app(name=self.next_app)
self.next_app = ['App1', 'App2']['App1' is self.next_app]
if __name__ == '__main__':
counter = 0
w = Wrapper()
while True:
counter += 1
print "Started App instance {}".format(counter)
w.swap()
sleep(5)
I am working with a chat app and I would like the server to control to flush the data to clients or not by pressing a button. I tried to do that by updating a global variable to trigger a if statement that can flush the data. However it seems that even the global variable is updated, it still does not trigger the if statement as the debugging "print" is not working, how can i fix that?
Here is the code
import kivy
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.scatter import Scatter
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.scrollview import ScrollView
from kivy.uix.button import Button
from kivy.graphics.vertex_instructions import Rectangle
from kivy.graphics.context_instructions import Color
from kivy.graphics.instructions import Instruction
from kivy.base import runTouchApp
from kivy.lang import Builder
import socket
from kivy.core.window import Window
import pygame
import random
from kivy.support import install_twisted_reactor
install_twisted_reactor()
from twisted.internet import reactor, protocol, defer
from twisted.protocols.basic import LineReceiver
from twisted.internet.protocol import Protocol, Factory
censored = 0
class MultiClientEcho(protocol.Protocol):
def __init__(self, factory, app):
self.factory = factory
self.app = app
def connectionMade(self):
self.factory.clients.append(self)
def dataReceived(self, data):
for client in self.factory.clients:
storedmessage = self.factory.app.handle_message(data)
global censored
if censored ==1:
print "send message"
client.transport.write(storedmessage)
elif censored ==2:
print "banned message"
client.transport.write("censored")
def connectionLost(self,reason):
self.factory.clients.remove(self)
class MultiClientEchoFactory(protocol.Factory):
protocol = MultiClientEcho
def __init__(self,app):
self.clients = []
self.app = app
def buildProtocol(self, addr):
return MultiClientEcho(self, self.app)
class ServerApp(App):
def build(self):
self.label = Label(text="server started\n")
self.approve_btn = Button(text="approve")
self.approve_btn.bind(on_release=self.send_message)
self.banned_btn = Button(text="banned")
self.banned_btn.bind(on_release=self.banned_message)
self.layout = BoxLayout(orientation='vertical', spacing=10)
reactor.listenTCP(8000, MultiClientEchoFactory(self))
self.layout.add_widget(self.label)
self.layout.add_widget(self.approve_btn)
self.layout.add_widget(self.banned_btn)
return self.layout
def send_message(self, *args):
global censored
censored = 1
self.label.text += "sent\n"
print censored
return censored
def banned_message(self, *args):
global censored
censored = 2
self.label.text += "censored\n"
print censored
return censored
def handle_message(self, msg):
self.label.text += "received: %s\n" % msg
return msg
if __name__ == '__main__':
ServerApp().run()