Update Kivy ProgressBar in Real-time from external input - python

I have an app which depends on a system app's output as such, to get that output i'm using the subprocess module which works great, the problem is that this system app generates outputs as it is running as such I capture the output in realtime(no problem here) and I want to use this same output to update my Kivy ProgressBar in realtime. So far, i get the output and try to update the progressbar but the application simply hangs and updates the progressbar when the subprocess call is finished. here is a simple example.
Assume that the directory structure is like this:
project
|----------slow.py
|----------myapp.py
Suppose slow.py is the file handling the system calls and it gets the outputs and feeds it to a function, here is the minimal code for that
from time import sleep
class Slow(object):
def __init__(self):
pass
def call_me(self, callback):
for x in range(10):
sleep(2)
callback(x)
Now for The myapp.py code:
from kivy.uix.progressbar import ProgressBar
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from slow import Slow
class MyWindow(BoxLayout):
def __init__(self, **kw):
super().__init__(**kw)
self.padding = 100
self.sl = Slow()
self.pb = ProgressBar()
self.pb.min=0
self.pb.max=100
btn = Button(text='Start Update')
btn.bind(on_release=self.start_update)
self.add_widget(self.pb)
self.add_widget(btn)
def start_update(self, *args):
self.sl.call_me(self.update_progress)
def update_progress(self, progress):
print(progress)
self.pb.value = float(progress)
class MyApp(App):
def build(self):
return MyWindow()
MyApp().run()
If you run this app, you'll notice it freezes until the counting is done. How can I fix such a scenario?
NB: I already tried using Clock.schedule_once which, needless to say, gives the same result

Related

In PyQt5 How to call function or method after load aplication?

from PyQt5 import QtWidgets
import time
def show_message(self):
time.sleep(5)
self.label.setText("It's me")
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
#How to call this func after load application
show_message(self)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
How to call show_message(self) after load application
Does pyqt5 have a function or method like self.afterLoad(application, show_message)
It seems to me something like this available on tkinter
I am guessing you want to start the application and then after 5 seconds the text in the label will get changed to the new text.
What you have right now is almost working, however the problem is that when you call time.sleep(5) then the whole execution of the program will get paused and nothing gets shown for that amount of time. If you still wanna be able to interact with the program during those 5 seconds then you will need to use a timer that is running in the background instead.
PyQt already has something like that in PyQt5.QtCore.QTimer. If you use that then your code could look something like this
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer # Importing QTimer
import time
def show_message(self):
self.label.setText("It's me")
# Stopping the timer. Otherwise it will run over and over again.
self.timer.stop()
class Main(QtWidgets.QMainWindow):
def __init__(self):
super(Main, self).__init__()
self.label = QtWidgets.QLabel('Hello', self)
# Create a new QTimer
self.timer = QTimer(self)
# Tell the timer that it should call show_message(self) when the time runs out
self.timer.timeout.connect(lambda: show_message(self))
# Start the timer which then starts running in the background for 5 seconds
self.timer.start(5000)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication([])
application = Main()
application.show()
sys.exit(app.exec_())
One last thing that I would recommend doing is putting your functions that belong to a class inside of it. So here it would probably be better to put show_message inside of the main class because right now you can call the function from every part of the code and that can lead to errors.

How to update a Kivy Scrollview Label in real time?

I could really really need some help with my actually quite simple Python Kivy Problem! I wrote a program that first announces counting to 5 and then should start counting from 1 to 5. The info should be shown in a scrollview-Label. The code roughly does its job but does not update the scrollview step-by-step but all at once after time is elapsed...can anybody please help? Thank you in advance!
import kivy
from kivy.config import Config
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.core.window import Window
from kivy.uix.scrollview import ScrollView
import time
kivy.require("2.0.0")
Config.set('kivy', 'keyboard_mode', 'systemandmulti')
class MainMenu(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.cols = 1
self.rows = 2
self.infowindow = ScrollableInfo(height=Window.size[1]*0.8, size_hint_y=None)
self.add_widget(self.infowindow)
self.ButtonCheckConnection = Button(text="Start Counting to 5")
self.ButtonCheckConnection.bind(on_press=self.countingtofive)
self.add_widget(self.ButtonCheckConnection)
def countingtofive(self, *_):
self.infowindow.update_scrollview(f"Counting to 5 is going to start in 3 seconds")
time.sleep(3)
countingmaximum = 5
for i in range(countingmaximum):
currentnumber = i+1
self.infowindow.update_scrollview(str(currentnumber))
time.sleep(1)
class ScrollableInfo(ScrollView):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.layout = GridLayout(cols=1, size_hint_y=None)
self.add_widget(self.layout)
self.connectioninfo_history = Label(size_hint_y=None, markup=True)
self.layout.add_widget(self.connectioninfo_history)
def update_scrollview(self, newinfo):
self.connectioninfo_history.text += '\n' + newinfo
self.layout.height = self.connectioninfo_history.texture_size[1]+15
self.connectioninfo_history.height = self.connectioninfo_history.texture_size[1]
self.connectioninfo_history.text_size = (self.connectioninfo_history.width*0.98, None)
class Counting(App):
def build(self):
self.screen_manager = ScreenManager()
self.mainmenu_page = MainMenu()
screen = Screen(name="MainMenu")
screen.add_widget(self.mainmenu_page)
self.screen_manager.add_widget(screen)
return self.screen_manager
if __name__ == "__main__":
counting_app = Counting()
counting_app.run()
The problem is that you are running your countingtofive() method on the main thread. Since Kivy uses the main thread to update the GUI, it cannot do that until you release the main thread (by returning from the countingtofive() method). That is why you never see anything until that method completes.
To fix that, run the countingtofive() method in another thread, like this:
def start_counting_thread(self, *args):
Thread(target=self.countingtofive, daemon=True).start()
And change the Button to bind to the start_counting_thread() method:
self.ButtonCheckConnection.bind(on_press=self.start_counting_thread)
And one minor change to the update_scrollview() method (add the #mainthread decorator):
#mainthread
def update_scrollview(self, newinfo):
The #mainthread decorator forces the decorated method to be run on the main thread. The same can be accomplished by using Clock.schedule_once(), but the decorator is easier. Just the piece of the code that actually updates the GUI must be run on the main thread. Generally, you should try to avoid long running methods on the main thread.

Testing tkinter application

I wrote a small application using python 3 and tkinter. Testing every widget, even though there are not many of them feels daunting so I wanted to write a couple of automated tests to simplify the process. I read some other question that seemed relevant to this problem but none fit my needs. Right now I'm doing the testing in a very simple manner - I invoke the command for every widget and manually click through it to see if it works. It does make things a bit faster, but I constantly run into some problems - i.e. I can't automatically close popup windows (like showinfo) even with using libraries to simulate keyboard clicks (namely pynput). Is there an efficient approach for testing applications using tkinter?
Here is the code I use right now:
import tkinter as tkinter
import unittest
from mygui import MyGUI
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.info_button.invoke()
v.close_button.invoke()
v.btnOut.invoke()
if __name__ == "__main__":
unittest.main()
I don't know much about unittest but I found a workaround to close popup dialogs like showinfo during the tests. The idea is to use keyboard event to invoke the button of the dialog. But since the app is waiting for the user to close the popup dialog, we need to schedule in advance the keyboard event using after:
self.root.after(100, self.root.event_generate('<Return>'))
v.button.invoke()
Full example
import tkinter
from tkinter import messagebox
import unittest
class MyGUI(tkinter.Frame):
def __init__(self, master, **kw):
tkinter.Frame.__init__(self, master, **kw)
self.info_button = tkinter.Button(self, command=self.info_cmd, text='Info')
self.info_button.pack()
self.quit_button = tkinter.Button(self, command=self.quit_cmd, text='Quit')
self.quit_button.pack()
def info_cmd(self):
messagebox.showinfo('Info', master=self)
def quit_cmd(self):
confirm = messagebox.askokcancel('Quit?', master=self)
if confirm:
self.destroy()
class TKinterTestCase(unittest.TestCase):
def setUp(self):
self.root = tkinter.Tk()
self.root.bind('<Key>', lambda e: print(self.root, e.keysym))
def tearDown(self):
if self.root:
self.root.destroy()
def test_enter(self):
v = MyGUI(self.root)
v.pack()
self.root.update_idletasks()
# info
v.after(100, lambda: self.root.event_generate('<Return>'))
v.info_button.invoke()
# quit
def cancel():
self.root.event_generate('<Tab>')
self.root.event_generate('<Return>')
v.after(100, cancel)
v.quit_button.invoke()
self.assertTrue(v.winfo_ismapped())
v.after(100, lambda: self.root.event_generate('<Return>'))
v.quit_button.invoke()
with self.assertRaises(tkinter.TclError):
v.winfo_ismapped()
if __name__ == "__main__":
unittest.main()

Kivy Producer/Consumer Queue Updates

Looking to understand why this code is not updating. Any help is much appreciated.
TJ
Here is my Main.py. Here, I'm attempting to link up a queue that stores the state of a number producer process. This is to simulate some background process that will update the user interface over time.
import Consumer
import Producer
import multiprocessing
if __name__ == '__main__':
shared_queue = multiprocessing.Queue()
producer = Producer.NumberGenerator(shared_queue)
multiprocessing.Process(target=producer.generate_numbers).start()
app = Consumer.TimerApp()
app.set_queue(shared_queue)
app.run()
Producer.py runs as a separate process that generates a new number once a second. It's just there to show that a background task is able to continually update the user interface.
import time
class NumberGenerator(object):
def __init__(self, q):
self.q = q
self.counter = 0
def generate_numbers(self):
while True:
time.sleep(1)
self.counter += 1
# print self.counter
self.q.put(self.counter)
Consumer.py is our Kivy app. It is meant to listen to the queue by popping items from it. Then, update the UI to demonstrate things are working.
from kivy.app import App
from kivy.uix.label import Label
from kivy.properties import StringProperty
import multiprocessing
class YourWidget(Label):
temp = StringProperty()
def update_text(self, txt):
self.temp = txt
class TimerApp(App):
def build(self):
self.widget = YourWidget()
self.widget.update_text("Initial Text!")
# Build registry here
# Start queue reader here
# How do I pass in a reference? Setter function??
return self.widget
def set_queue(self, q):
self.q = q
def consumer_process_queue(self):
while True:
value = str(self.q.get())
print "Consumer: {}".format(value)
self.widget.update_text(value)
def on_start(self):
# self.widget.update_text("Hello World!")
multiprocessing.Process(target=self.consumer_process_queue).start()
timer.kv:
<YourWidget>:
text: root.temp
When things are processing, I can see the numbers updating to STDOUT. This indicates that "self.widget.update_text(value)" is not doing what I want it to.
Any ideas?
Also, if I use the commented "self.widget.update_text("Hello World!")" code and comment out the "multiprocessing.Process(target=self.consumer_process_queue).start()", the widget updates the text.
Problem
def consumer_process_queue(self):
while True:
value = str(self.q.get())
print("Consumer: {}".format(value))
self.widget.update_text(value)
When you run this, the program will never exit your loop, preventing Kivy from doing all of the other things that need doing. As a result, all you’ll see is a black window with "Initial Text!", which you won’t be able to interact with.
Solution
Instead, you need to “schedule” your consumer_process_queue() function to be called repeatedly.
Please refer to the example for details. No changes to Producer.py and main.py.
Example
Consumer.py
from kivy.app import App
from kivy.uix.label import Label
from kivy.clock import Clock
import multiprocessing
class YourWidget(Label):
def update_text(self, txt):
self.text = txt
class TimerApp(App):
def build(self):
self.widget = YourWidget()
self.widget.update_text("Initial Text!")
Clock.schedule_interval(self.consumer_process_queue, 1)
# Build registry here
# Start queue reader here
# How do I pass in a reference? Setter function??
return self.widget
def set_queue(self, q):
self.q = q
def consumer_process_queue(self, dt=0):
value = str(self.q.get())
print("Consumer: {}".format(value))
self.widget.update_text(value)
def on_start(self):
# self.update_text("Hello World!")
multiprocessing.Process(target=self.consumer_process_queue).start()
timer.kv
#:kivy 1.10.0
<YourWidget>:
text: ""
Output

Running multiple Kivy apps at same time that communicate with each other

I would like my Kivy application to be able to spawn multiple apps (i.e. new windows) on a Windows machine that can communicate with each other.
ScreenManager and Popup options will not cut it because they live in the same window..I need to be able to drag new screens across multiple monitors and therefore need multiple windows.
Kivy docs explicitly state that "Kivy supports only one window
per application: please don't try to create more than one."
A google search produces this simple approach of simple spawning a new app from within another app, like so:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
ChildApp().run()
if __name__ == '__main__':
MainApp().run()
However, when I do this, it launches the app within the same window and crashes, and my terminal spits out like crazy:
Original exception was:
Error in sys.exceptionhook:
I get the same result if instead of ChildApp().run() I do multiprocessing.Process(target=ChildApp().run()).start()
Using the subprocess library gets me closer to what I want:
# filename: test2.py
from kivy.app import App
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
if __name__ == '__main__':
ChildApp().run()
# filename: test.py
from kivy.app import App
from kivy.uix.button import Button
import subprocess
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
subprocess.call('ipython test2.py', shell=True)
if __name__ == '__main__':
MainApp().run()
This spawns the child window without error, however now the main window is locked (white canvas) and if I close the child window, it just gets reopened.
They need to be able pass data between one another. Any ideas on how to do this correctly in Windows? This post seems to suggest that this is possible but I'm not sure where to start.
I tried baconwichsand's code and can confirm with Python 3.6 and Windows 10 it does not work. Apparently only top level object classes can be pickled, and since both apps inherit from the App class python throws an error. However a top level definition that simply executes the ChildApp().run() command can be pickled and works. Here is my working code.
import multiprocessing
from kivy.app import App
from kivy.uix.label import Label
class MainApp(App):
def build(self):
return Label(text='Main App Window')
class OtherApp(App):
def build(self):
return Label(text='Other App Window')
def open_parent():
MainApp().run()
def open_child():
OtherApp().run()
if __name__ == '__main__':
a = multiprocessing.Process(target=open_parent)
b = multiprocessing.Process(target=open_child)
a.start()
b.start()
And here is the code I am using, including the Builder to use a shared .kv file for both windows.
import multiprocessing
from kivy.lang import Builder
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.uix.widget import Widget
class MainRoot(Widget):
pass
class OtherRoot(Widget):
pass
class MainApp(App):
def build(self):
Builder.load_file('B:\Python_Codes\Testing Grounds\shared.kv')
main = MainRoot()
return main
class OtherApp(App):
def build(self):
Builder.load_file('B:\Python_Codes\Testing Grounds\shared.kv')
other = OtherRoot()
return other
def open_parent():
MainApp().run()
def open_child():
OtherApp().run()
if __name__ == '__main__':
a = multiprocessing.Process(target=open_parent)
b = multiprocessing.Process(target=open_child)
a.start()
b.start()
I'm not sure why it doesn't work with multiprocessing (I've never tried it), but it should at least work with subprocess. The reason your main window is locked is because subprocess.call blocks the thread that calls it while it waits for the subprocess to finish and return a result.
You want to use subprocess.Popen instead, which does not block.
bj0's answer regarding subprocess was correct.
Even better, I figured out how to do this via multiprocessing, which allows better communication and passing of information between apps. It wasn't working before because I did multiprocessing.Process(target=ChildApp().run()).start() when it should be multiprocessing.Process(target=ChildApp().run).start(). The following works
# filename: test.py
from kivy.app import App
from kivy.uix.button import Button
from test2 import ChildApp
import multiprocessing
class MainApp(App):
def build(self):
b = Button(text='Launch Child App')
b.bind(on_press=self.launchChild)
return b
def launchChild(self, button):
app = ChildApp()
p = multiprocessing.Process(target=app.run)
p.start()
if __name__ == '__main__':
MainApp().run()
# filename: test2.py
from kivy.app import App
from kivy.uix.label import Label
class ChildApp(App):
def build(self):
return Label(text='Child')
if __name__ == '__main__':
ChildApp().run()
Working on Ubuntu Python 3.10
Calling a subprocess in a multiprocess.
main.py
import multiprocessing
import subprocess
import shlex
#my kivy code..
def sub_window():
subprocess.call(shlex.split('python3 test.py'))
if __name__ == '__main__':
b = multiprocessing.Process(target=sub_window)
b.start()
MyApp().run()
test.py
#another kivy app script..
OtherApp().run()

Categories

Resources