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
Related
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.
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
Just to make a clear distinction between my questions and a lot of other questions on here:
I have already written the 'main' program (which has classes, functions, and variables alike) and a good chunk of the GUI.
So this isn't a question on how to write in tkinter or python, but more so how can I combine them?
Should I run the program from the GUI? And import the various variables, functions, and classes? And if so, should I import the whole main program of use from to import each item when needed?
Should I create a third program that imports the main and the GUI?
I just can't seem to find any clear answer, or at least I can't seem to find out how to even phrase the question because all search results point to how to write GUI, which I already get the gist of.
Here is an example of structure I did for one of my projects containing a Server (your actual main code), a GUI, and a third program I called "App" that just runs the 2. I created functions like link_with_gui or link_with_server so you can access to your GUI's variables from the Server and vice-versa.
To run this program, you just have to call python app.py. I added sections if __name__ == '__main__' in the Server and GUI so you can run them independantly (for testing purposes).
EDIT : I updated my code with threads. In the Server, you have an infinite loop that increments the variable self.count every second, and in the GUI if you click on the button, it will print this count.
App :
# app.py
from server import Server
from gui import GUI
class App:
def __init__(self):
self.gui = GUI()
self.server = Server()
self.link_parts()
def link_parts(self):
self.server.link_with_gui(self.gui)
self.gui.link_with_server(self.server)
def main():
app = App()
app.gui.mainloop()
if __name__ == '__main__':
main()
Server :
# server.py
import threading
import time
class Server:
def __init__(self):
self.count = 0
thread = threading.Thread(target=self.counter)
thread.daemon = True
thread.start()
def link_with_gui(self, gui):
self.gui = gui
def display(self):
self.gui.chat_text.delete("1.0", "end")
self.gui.chat_text.insert("insert", "This is Server count : {}".format(self.count))
def counter(self):
while True:
self.count += 1
time.sleep(1)
print("self.count", self.count)
if __name__ == '__main__':
server = Server()
time.sleep(4)
GUI :
# gui.py
import tkinter as tk
class GUI(tk.Tk): # Graphic User Interface
def __init__(self):
super().__init__()
self.btn = tk.Button(master=self, text="Click Me")
self.btn.pack()
self.chat_text = tk.Text(self, width=20, height=3)
self.chat_text.pack()
def link_with_server(self, server):
self.server = server
self.btn.configure(command=self.server.display)
if __name__ == '__main__':
gui = GUI()
gui.mainloop()
I am running into an issue when I am taking in values over serial and then attempting to update my Gui with those values. Unfortunately, even though the values update correctly, I am unable to get to the screen to refresh unless I click off of it and then back on to it. I have tried repaint, update, and processEvents() but have been unable to solve the problem.
Here is the code I am working with:
import sys
import serial
import time
import requests
import PyQt5
from PyQt5.QtWidgets import *
from PyQt5.QtCore import*
from PyQt5.QtGui import *
import mainwindow_auto
CUSTOM_EVENT = 1000
ser = serial.Serial('/dev/ttyACM0', 9600)
class TestThread(QThread):
def __init__(self, target):
QThread.__init__(self)
self.target = target
def run(self):
while True:
QApplication.postEvent(self.target, QEvent(QEvent.Type(CUSTOM_EVENT)))
QApplication.processEvents()
QThread.sleep(15)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(self.__class__, self).__init__()
self.setupUi(self)# gets defined in the UI file
self.thread = TestThread(self)
self.thread.start()
def event(s, e):
if(e.type() == CUSTOM_EVENT):
print("Readline: ",int(ser.readline()))
SOC = int(ser.readline())
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
print("SOC: ",SOC)
print(s.lcdNumber.value())
return True
def main():
app = QApplication(sys.argv)
form = MainWindow()
form.lcdNumber.display(30)
form.progressBar.setValue(30)
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Thanks in advance!
Since you already have an I/O thread, let it handle the I/O and sent the received value to the main thread via a signal.
No need for a custom event, no I/O on the main thread.
Just adding a signal to the thread subclass and connecting a slot to that before starting the thread.
Rather than rewriting the code that I had above, I ended up fixing it by force redrawing using s.hide() and s.show() after updating the values in the event code. It forced a redraw that otherwise refused to work.
s.lcdNumber.display(SOC)
s.progressBar.setValue(SOC)
s.hide()
s.show()
As suggested by #KevinKrammer, this is simple to do with a custom signal:
class TestThread(QThread):
serialUpdate = pyqtSignal(int)
def run(self):
while True:
QThread.sleep(1)
value = int(ser.readline())
self.serialUpdate.emit(value)
class MainWindow(QMainWindow, mainwindow_auto.Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.setupUi(self)
self.thread = TestThread(self)
self.thread.serialUpdate.connect(self.handleSerialUpdate)
self.thread.start()
def handleSerialUpdate(self, value):
print("Readline: ", value)
self.lcdNumber.display(value)
self.progressBar.setValue(value)
Let's say I have a PyQt program that goes through a given directory, looks for *JPEG images, and does some processing every time it finds one. Depending on the size of the selected directory, this may take from some seconds to minutes.
I would like to keep my user updated with the status - preferably with something like "x files processed out of y files" . If not, a simple running pulse progress bar by setting progressbar.setRange(0,0) works too.
From my understanding, in order to prevent my GUI from freezing, I will need a seperate thread that process the images, and the original thread that updates the GUI every interval.
But I am wondering if there is any possible way for me to do both in the same thread?
Yes, you can easily do this using processEvents, which is provided for this exact purpose.
I have used this technique for implementing a simple find-in-files dialog box. All you need to do is launch the function that processes the files with a single-shot timer, and then periodically call processEvents in the loop. This is should be good enough to update a counter with the number of files processed, and also allow the user to cancel the process, if necessary.
The only real issue is deciding on how frequently to call processEvents. The more often you call it, the more responsive the GUI will be - but this comes at the cost of considerably slowing the processing of the files. So you may have to experiment a little bit in order to find an acceptable compromise.
UPDATE:
Here's a simple demo that shows how the code could be structured:
import sys, time
from PyQt5 import QtWidgets, QtCore
class Window(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton('Start')
self.progress = QtWidgets.QLabel('0')
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
layout.addWidget(self.progress)
self.button.clicked.connect(self.test)
self._stop = False
self._stopped = True
def test(self):
if self._stopped:
self._stop = False
self.progress.setText('0')
self.button.setText('Stop')
QtCore.QTimer.singleShot(1, self.process)
else:
self._stop = True
def process(self):
self._stopped = False
for index in range(1, 1000):
time.sleep(0.01)
self.progress.setText(str(index))
if not index % 20:
QtWidgets.qApp.processEvents(
QtCore.QEventLoop.AllEvents, 50)
if self._stop:
break
self._stopped = True
self.button.setText('Start')
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I could not achieve the thing you need without multi threading and this is not possible because gui can be only updated in main thread. Below is an algorithm how I did this with multithreading.
Let's say you have your application processing images. Then there are the following threads:
Main thread (that blocks by GUI/QApplication-derived classes.exec())
Timer with, for example, 1 second interval which updates a variable and calls a slot in GUI thread which updates a variable in user interface.
A thread which is processing images on your pc.
def process(self):
self._status = "processing image 1"
....
def _update(self):
self.status_label.setText(self._status)
def start_processing(self, image_path):
# create thread for process and run it
# create thread for updating by using QtCore.QTimer()
# connect qtimer triggered signal to and `self._update()` slot
# connect image processing thread (use connect signal to any slot, in this example I'll stop timer after processing thread finishes)
#pyqtSlot()
def _stop_timer():
self._qtimer.stop()
self._qtimer = None
_update_thread.finished.connect(_stop_timer)
In pyqt5 it is possible to assign a pyqtvariable from a one nested thread(first level). So you can make your variable a pyqtvariable with setter and getter and update gui in a setter or think how you can do this by yourself.
You could just use the python threading module and emit a signal in your threaded routine.
Here's a working example
from PyQt4 import QtGui, QtCore
import threading
import time
class MyWidget(QtGui.QWidget):
valueChanged = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
self.computeButton = QtGui.QPushButton("Compute", self)
self.progressBar = QtGui.QProgressBar()
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.computeButton)
layout.addWidget(self.progressBar)
self.computeButton.clicked.connect(self.compute)
self.valueChanged.connect(self.progressBar.setValue)
def compute(self):
nbFiles = 10
self.progressBar.setRange(0, nbFiles)
def inner():
for i in range(1, nbFiles+1):
time.sleep(0.5) # Process Image
self.valueChanged.emit(i) # Notify progress
self.thread = threading.Thread(target = inner)
self.thread.start()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())