How to dynamically update QTextEdit - python

so I have a QTextEdit within a Main Window in my GUI. I want to live update the text in this by pulling from a remotely updating list. I don't know how to infinitely check this list, without either a) doing an infinite loop or b) thread.
a) Crashes the GUI, as it is an infinite loop
b) produces an error saying:
QObject: Cannot create children for a parent that is in a different thread.
Which I understand.
What could I do to fix this?

this is how it works without threads :)
1) Create pyqt textEditor logView:
self.logView = QtGui.QTextEdit()
2)add pyqt texteditor to layout:
layout = QtGui.QGridLayout()
layout.addWidget(self.logView,-ROW NUMBER-,-COLUMN NUMBER-)
self.setLayout(layout)
3) the magic function is:
def refresh_text_box(self,MYSTRING):
self.logView.append('started appending %s' % MYSTRING) #append string
QtGui.QApplication.processEvents() #update gui for pyqt
call above function in your loop or pass concatenated resultant string directly to above function like this:
self.setLayout(layout)
self.setGeometry(400, 100, 100, 400)
QtGui.QApplication.processEvents()#update gui so that pyqt app loop completes and displays frame to user
while(True):
refresh_text_box(MYSTRING)#MY_FUNCTION_CALL
MY_LOGIC
#then your gui loop
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog = MAIN_FUNCTION()
sys.exit(dialog.exec_())

Go for QThread, after all, moving your code from a python thread to QThread shouldn't be hard.
Using signals & slots is imho the only clean solution for this. That's how Qt works and things are easier if you adapt to that.
A simple example:
import sip
sip.setapi('QString', 2)
from PyQt4 import QtGui, QtCore
class UpdateThread(QtCore.QThread):
received = QtCore.pyqtSignal([str], [unicode])
def run(self):
while True:
self.sleep(1) # this would be replaced by real code, producing the new text...
self.received.emit('Hiho')
if __name__ == '__main__':
app = QtGui.QApplication([])
main = QtGui.QMainWindow()
text = QtGui.QTextEdit()
main.setCentralWidget(text)
# create the updating thread and connect
# it's received signal to append
# every received chunk of data/text will be appended to the text
t = UpdateThread()
t.received.connect(text.append)
t.start()
main.show()
app.exec_()

Related

How to use a timer or equivalent within a python thread without blocking the GUI

I am a writing a GUI based application using the PyQT framework which connects to a device, sends commands, reads the corresponding data and displays this in real time to a table, graph widget and to a file.
Once the run button is clicked it starts a thread which sends the external device commands according to a procedure table and emits signals with the data to various methods to change the GUI.
When the run button is clicked it executes the following lines:
worker = Worker(self.runProcedure)
worker.signals.updateResults.connect(self.updateResultsTable)
worker.signals.writeResults.connect(self.writeResultsFile)
worker.signals.finished.connect(self.procedure_complete)
self.threadpool.start(worker)
within the runProcedure commands are sent to the device from the procedure table and the data read from the device is put into a list 'hfData' using code similar to that listed below:
while float(currentForceReading) <= float(target) and stopAlarm == 0:
ts = dt.now().timestamp()
hfData = (connection.readline()).split()
updateResults_callback.emit(ts, hfData) #method to update results table and graphs
writeResults_callback.emit(ts, hfData) #method to write results to a file
One of the options in the application is to hold for a period of time where it continues collecting data from the device without sending new commands.
I am looking for a way to continue taking measurements whilst in this hold time and continue updating the GUI.
I have tried to implement the following code, however this while loop blocks the GUI from updating:
stepHoldTime = float(procedureModel.data(procedureModel.index(row,4), Qt.DisplayRole))
if(stepHoldTime != 0):
endTime = time.monotonic() + stepHoldTime
while(time.monotonic() < endTime):
ts = dt.now().timestamp()
hfData = (connection.readline()).split()
updateResults_callback.emit(ts,hfData)
Is there a correct way to implement this functionality?
Instead of while-loop you could run QTimer which will execute code every few milliseconds.
It is minimal example which shows how to run function every 1000ms and update time in label.
from PyQt5.QtWidgets import QApplication, QLabel
from PyQt5.QtCore import QTimer, QDateTime
class Window(QLabel):
def __init__(self, parent=None):
super().__init__(parent)
self.showTime()
help(QTimer)
self.timer = QTimer()
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
def showTime(self):
text = QDateTime.currentDateTime().toString('yyyy-MM-dd hh:mm:ss')
self.setText(text)
if __name__ == '__main__':
app = QApplication([])
win = Window()
win.show()
app.exec()
But your problem can be more complex and it may need more complex solution. You may have to show minimal working code which we could run and see problem - and test some ideas to resolve problem.
You can start your "reader" from a separate thread
class YourUi(QtWidgets.QMainWindow):
update_data = QtCore.pyqtSignal(str)
def __init__(self):
super(YourUi, self).__init__()
self.update_data.connect(self.do_update_data)
t = threading.Thread(target=self.update_worker, args=(self.update_data,), daemon=True)
t.start()
#staticmethod
def update_worker(signal):
connection = create_conncetion()
while True:
hfData = (connection.readline()).split()
signal.emit(hfData)
time.sleep(0.1)
def do_update_data(self, hf_data):
# access GUI elemetns from main thread to prevent freezing
self.some_qt_label.setText(str(hf_data))

PySide2 UI stops responding when entering a while loop after showing it

I'm having trouble where my QtWidget stops responding if I try to use a while loop after calling the widget.show() method for the QtWidget object. I initially thought the issue was in how I was using the signals and slots. I had created my own signal with new_data = Signal(float) and I was sampling data at an interval established by a time.sleep() call within that while loop and emitting the new_data signal each time the data was sampled. That was connected to a method in my QtWidget that just set the text of a label in the QtWidget to display the new data.
However, after some testing I found that if I ONLY try to print("in loop") inside that while loop, I get the same behavior. The QtWidget object stops responding. What is the proper way to update PySide2 interface at a periodic interval from outside the interface object? Can I perhaps run the interface as a process and feed it with updated data with a queue? I imaging that is possible, but am having trouble finding an example. The interface is only one piece of this application primarily made in Python and I will have multiple processes and multiple queues besides the Qt interface. Here is the code:
import sys
from PySide2.QtUiTools import QUiLoader
from PySide2.QtWidgets import QApplication, QWidget
from PySide2.QtCore import QFile, QObject, Signal, Slot
import time
import NI9213
class MyDaq(QObject):
new_daq_data = Signal(float)
def __init__(self):
QObject.__init__(self)
daq_channels = "cDAQ1Mod2/ai0"
self.daq = NI9213.NI9213(channels=daq_channels)
def sample_daq(self):
data = self.daq.read_all()
self.new_daq_data.emit(data)
class DigitalDisplay(QWidget):
def __init__(self):
#Initialize the QWidget object used to create the user interface
QWidget.__init__(self)
#Load the user interface
designer_file = QFile("signal_digital_display.ui")
designer_file.open(QFile.ReadOnly)
loader = QUiLoader()
self.ui = loader.load(designer_file, self)
designer_file.close()
#Add title to the UI window
self.setWindowTitle("Digital Display")
self.mode = 'run'
self.ui.stopButton.clicked.connect(self.stopMode)
self.sampling_period = 0.1
#Slot(float)
def refresh_data(self, data):
self.ui.label.setText(str(data))
def stopMode(self):
self.mode = 'stop'
if __name__ == "__main__":
app = QApplication(sys.argv)
digital_display = DigitalDisplay()
digital_display.show()
## data = MyDaq()
## data.new_daq_data.connect(digital_display.refresh_data)
##
while(digital_display.mode=='run'):
print("after display.show")
## data.sample_daq()
time.sleep(digital_display.sampling_period)
sys.exit(app.exec_())
The problem here is that you have an infinite loop before you start the Qt event loop.
if __name__ == "__main__":
app = QApplication(sys.argv) # [1]
digital_display = DigitalDisplay()
digital_display.show() # [2]
while(digital_display.mode=='run'):
print("after display.show")
time.sleep(digital_display.sampling_period) # [3]
sys.exit(app.exec_()) # [4]
[1] This creates the QApplication object. It does not start the event loop yet.
[2] Create your widget and open a new window to display it. The operating system will create events for the window, but they have no effect on the application itself until we start the event loop.
[3] digital_display.mode will never change. This is an infinite loop and Python will never advance past this point.
[4] Here we start the application event loop and close the process once the application has finished. But we never get here.
What you should do instead is to create a QTimer on the DigitalDisplay widget that periodically fires a signal that can be connected to data.sample_daq.

Threading a task beside a GUI PyQT4

So i am trying to run a PyQT GUI while another functions is gathering information in the background. If Information is found the GUI should update itself.
I am new in Threading so i googled a lot and found some good HowTo's although it does not work as it should.
when i run the program it just ends itself after 3 s.
Maybe you see some major mistake ive done.
Here is the basic code i am trying to get to run
class scan_Thread(QThread):
def __init__(self, samp_rate, band, speed, ppm, gain, args, prn):
QThread.__init__(self)
self.samp_rate=samp_rate
self.band=band
self.speed=speed
self.ppm=ppm
self.gain=gain
self.args=args
self.prn=prn
def __del__(self):
self.wait()
def run(self):
do_scan(self.samp_rate, self.band, self.speed,
self.ppm, self.gain, self.args, self.prn)
def start_gui():
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
#app.exec_()
#sys.exit()
def main(options = None):
def printfunc(found_list):
for info in sorted(found_list):
print info
get_thread = scan_Thread(options.samp_rate, options.band, options.speed,
options.ppm, options.gain, options.args, printfunc)
get_thread.start()
start_gui()
Thanks!
Many of the objects of the Qt classes and therefore of PyQt need to start some object of type Application (QCoreApplication, QtGuiApplication or QApplication), but only one of these objects must exist.
In your particular case QThread needs it. The previous classes are responsible for generating the necessary loops.
So you should modify your code to the following:
def main(options = None):
app = QtGui.QApplication(sys.argv) // before creating any PyQt object.
def printfunc(found_list):
for info in sorted(found_list):
print info
get_thread = scan_Thread(options.samp_rate, options.band, options.speed,
options.ppm, options.gain, options.args, printfunc)
get_thread.start()
window = Window()
window.show()
sys.exit(app.exec_())

How to prevent the GUI from freezing using QThread?

I have a GUI which needs to perform work that takes some time and I want to show the progress of this work, similar to the following:
import sys
import time
from PyQt4 import QtGui, QtCore
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# start loop with signal
self.button = QtGui.QPushButton('loop', self)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def update(self):
self.pbar.setValue(self.pbar.value() + 1)
def loop(self):
for step in range(100):
self.update()
print step
time.sleep(1)
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop() # call loop directly to check whether view is displayed
sys.exit(app.exec_())
When I execute the code the loop method is called and it prints out the values as well as updates the progress bar. However the view widget will be blocked during the execution of loop and although this is fine for my application it doesn't look nice with Ubuntu. So I decided to move the work to a separate thread like this:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def loop(self):
for step in range(10):
print step
time.sleep(1)
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
work = Worker()
thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop) # alternative
thread.start()
work.loop() # not called if thread started() connected to loop
sys.exit(app.exec_())
When I run this version of the script the loop starts running (the steps are displayed in the terminal) but the view widget is not shown. This is the first thing I can't quite follow. Because the only difference from the previous version here is that the loop runs in a different object however the view widget is created before and therefore should be shown (as it was the case for the previous script).
However when I connected the signal started() from thread to the loop function of worker then loop is never executed although I start the thread (in this case I didn't call loop on worker). On the other hand view is shown which makes me think that it depends whether app.exec_() is called or not. However in the 1st version of the script where loop was called on view it showed the widget although it couldn't reach app.exec_().
Does anyone know what happens here and can explain how to execute loop (in a separate thread) without freezing view?
EDIT: If I add a thread.finished.connect(app.exit) the application exits immediately without executing loop. I checked out the 2nd version of this answer which is basically the same what I do. But in both cases it finishes the job immediately without executing the desired method and I can't really spot why.
The example doesn't work because communication between the worker thread and the GUI thread is all one way.
Cross-thread commnunication is usually done with signals, because it is an easy way to ensure that everything is done asynchronously and in a thread-safe manner. Qt does this by wrapping the signals as events, which means that an event-loop must be running for everything to work properly.
To fix your example, use the thread's started signal to tell the worker to start working, and then periodically emit a custom signal from the worker to tell the GUI to update the progress bar:
class Worker(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(int)
def loop(self):
for step in range(0, 10):
print step
time.sleep(1)
self.valueChanged.emit((step + 1) * 10)
...
thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()
sys.exit(app.exec_())

PyQt: Displaying QTextEdits over the window

I want to display some QTextEdits over my main window at arbitrary locations. Below is my first attempt. It doesn't quite work. If I create the text edits before I show the window, the text edits appear, but if I create them after I have shown the window they don't appear. What's up with that? How can I get the ones created later to show up?
import sys, random
from PyQt4 import QtGui, QtCore
app = QtGui.QApplication(sys.argv)
win = QtGui.QMainWindow()
win.resize(500,500)
def new_text():
print "new text"
text = QtGui.QTextEdit(win)
text.move(random.random() * 400, random.random() * 400)
for i in range(3):
new_text()
timer = QtCore.QTimer()
timer.connect(timer, QtCore.SIGNAL("timeout()"), new_text)
timer.start(500)
win.show()
app.exec_()
Oh, I got it. You have to call show on each widget before it appears. I guess QMainWindow.show recursively calls the method for all of its children. So just add text.show() to the end of the new_text function and it works.

Categories