Dynamically update matplotlib canvas in a pyqt5 interface - python

I have a very simple program and the structure must be preserved (it is a condition of the exercise).
We start with an interface formed by a button and a Canvas, like shown in the figure above.
Once the button is clicked, a background task is initiated, which calls a function called animation. In here we start a process that generates random data every time waiting_time.
We want to update the plot everytime there is a new x and y variable.
The code is the following:
from PyQt5 import QtCore, QtWidgets
from mplwidget import MplWidget
import threading
import time
import numpy as np
import sys
class RandomDataGeneration():
"""
Mandatory Class. This Class must exist.
"""
def __init__(self):
pass
def data_generation(self):
while True:
waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
print(waiting_time)
time.sleep(waiting_time)
self.x = np.random.rand(10)
self.y = np.random.rand(10)
print(self.x)
print(self.y)
#self.update_plot()
def update_plot(self):
self.MplWidget.canvas.axes.clear()
self.MplWidget.canvas.axes.set_title('GRAPH')
self.MplWidget.canvas.axes.plot(x, y, marker='.', linestyle='')
self.MplWidget.canvas.axes.legend(('random'), loc='upper right')
self.MplWidget.canvas.draw()
def animation():
"""
This function initiates the RandomDataGeneration
"""
app = RandomDataGeneration()
app.data_generation()
class Ui_MainWindow():
def __init__(self):
super().__init__()
def start_download(self):
download_info = threading.Thread(target=animation)
download_info.start()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(1280, 1024)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(880, 80, 221, 32))
self.pushButton.setObjectName("pushButton")
self.MplWidget = MplWidget(self.centralwidget)
self.MplWidget.setGeometry(QtCore.QRect(49, 39, 771, 551))
self.MplWidget.setObjectName("MplWidget")
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
self.pushButton.clicked.connect(self.start_download)
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
To run the code is necessary to have in the same folder the following code with name mplwidget.py.
# ------------------------------------------------------
# -------------------- mplwidget.py --------------------
# ------------------------------------------------------
from PyQt5.QtWidgets import*
from matplotlib.backends.backend_qt5agg import FigureCanvas
from matplotlib.figure import Figure
class MplWidget(QWidget):
def __init__(self, parent = None):
QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)

Since you are creating a pyqt5 application I guess your best bet is to use a QThread for the blocking part and emit a signal every time new data is generated. One way would be to make RandomDataGeneration a subclass of QThread and implement run, e.g.
class RandomDataGeneration(QtCore.QThread):
"""
Mandatory Class. This Class must exist.
"""
new_data = QtCore.pyqtSignal()
def __init__(self, parent = None):
super().__init__(parent)
def data_generation(self):
while True:
waiting_time = np.random.randint(1,4) # waiting time is given by a random number.
print(waiting_time)
time.sleep(waiting_time)
self.x = np.random.rand(10)
self.y = np.random.rand(10)
print(self.x)
print(self.y)
self.new_data.emit()
def run(self):
self.data_generation()
To use the thread you could subclass QMainWindow and create an instance of RandomDataGeneration in there. Subclassing QMainWindow has an additional advantage that you can move the gui setup and the signal-slot connections in there as well, e.g.
class MyMainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.download_thread = RandomDataGeneration(self)
self.download_thread.new_data.connect(self.plot_data)
self.ui.pushButton.clicked.connect(self.start_download)
def start_download(self):
if not self.download_thread.isRunning():
self.download_thread.start()
def plot_data(self):
self.ui.MplWidget.update_plot(self.download_thread.x, self.download_thread.y)
The main part then becomes
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = MyMainWindow()
MainWindow.show()
sys.exit(app.exec_())

Related

Python threading - Change variable value in another class

I am looking for a way to change the variables between classes in different threads.
As I had a question about threading recently, I'd like to use that example code for this question again.
Main-File:
#PROGRAM/SCRIPT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import datetime
import pyqtgraph
import time
plotsize = 20
class Worker2(QtCore.QThread):
def __init__(self):
print("Thread2 has been started")
def run(self):
#now get access to variable of class Worker
# =============================================================================
# Threading for not freezing the GUI while running
# =============================================================================
class Worker(QtCore.QThread):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
widgetplot = QtCore.pyqtSignal(list, list)
def __init__(self, plot_time, plot_value):
print("Thread has been started")
QtCore.QThread.__init__(self, objectName='WorkerThread')
self.plot_time = plot_time
self.plot_value = plot_value
def run(self):
#now get access to variable of Worker2
_count = 0
_start_time = datetime.datetime.now()
while 0 <= _count < 100:
# Use local variable!
_count_prev = _count
QtCore.QThread.usleep(10000)
_diff = datetime.datetime.now() - _start_time
_count = int((_diff.total_seconds() * 10))
if(_count != _count_prev):
print(_count)
x = self.plot_time[:_count]
y = self.plot_value[:_count]
self.widgetplot.emit(x, y)
class my_class(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(my_class, self).__init__(parent)
self.setupUi(self)
self.WidgetPlot.setXRange(0, 105, padding=0)
self.WidgetPlot.setYRange(0, 105, padding=0)
self.second_line = pyqtgraph.PlotDataItem(pen=pyqtgraph.mkPen('w', width=plotsize*2))
self.plot_time = []
self.plot_value = []
self.worker_thread = Worker(self.plot_time, self.plot_value)
self.worker_thread.widgetplot.connect(self.update_second_line_plot)
self.worker_thread2 = Worker2()
self.pushButton.clicked.connect(self.my_function)
self.WidgetPlot.setMouseEnabled(x=False, y=False)
font=QtGui.QFont()
font.setPixelSize(20)
font.setBold(True)
self.WidgetPlot.getAxis("bottom").setTickFont(font)
self.WidgetPlot.getAxis("left").setTickFont(font)
def my_function(self):
_l = list(range(100))
self.plot_time.extend(_l)
self.plot_value.extend(_l)
self.start()
def update_second_line_plot(self, plot_time, plot_value):
self.second_line.setData(plot_time, plot_value)
def start(self):
self.WidgetPlot.plot(self.plot_time, self.plot_value, pen=pyqtgraph.mkPen('r', width=plotsize))
self.WidgetPlot.addItem(self.second_line)
self.worker_thread.start()
self.worker_thread2.start()
def main():
app = QApplication(sys.argv)
form = my_class()
form.show()
app.exec_()
if __name__ == '__main__':
main()
GUI:
#GUI.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(739, 532)
MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.WidgetPlot = PlotWidget(self.centralwidget)
self.WidgetPlot.setGeometry(QtCore.QRect(100, 40, 541, 341))
self.WidgetPlot.setObjectName("WidgetPlot")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(330, 420, 93, 28))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Main Window"))
self.pushButton.setText(_translate("MainWindow", "Start"))
from pyqtgraph import PlotWidget
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
I'd like to change variable in class Worker2 from class Worker and vice versa.
Many thanks in advance!
Andrew
but for the other class there is no way to call/change variables...
Member variables* in Python are public by default.** Suppose you have
class P:
def __init__(self):
self.q = "whatever"
def printit(self):
print(self.q)
You can do this in the repl:
>>> p = P()
p = P()
>>> p.printit()
p.printit()
whatever
>>> p.q
p.q
'whatever'
>>> p.q = 'new value'
p.q = 'new value'
>>> p.printit()
p.printit()
new value
>>> p.q
p.q
'new value'
You can do the same from anywhere in Python code that has a reference to an object belonging to class P.
* "Attributes?" "fields?" I wrote a lot of Python programs that were ancillary to projects that I worked on—mostly for my own use. I haven't talked a lot with other Python programmers, and I don't really know the lingo.
** See https://towardsdatascience.com/private-protected-attributes-in-python-demystified-once-and-for-all-9456d4e56414 for more about "public" vs. "private."

Passing retrived serial data from worker class variable to main gui class

I have spent a few days trying to get a separate thread to read the incoming serial data as if I loop through a function in the GUI class with a timer the GUI gets unresponsive as it tries to receive the data. I have managed to print the incoming serial on the console even though it has been done using a while loop, which I do not like too much, and I plan to replace it with a timer.
Now I have troubles passing the variable decoded_serial from the WorkerThread class to the displaySerial function in the main GUI class.
This is the code I have been working on:
from PyQt6 import QtCore, QtGui, QtWidgets
from PyQt6.QtCore import QThread, QTimer
import serial
class WorkerThread(QThread):
def run(self):
ser= serial.Serial(port='COM5', baudrate=9600)
while (True):
if (ser.inWaiting() > 0):
data_string = ser.read(ser.inWaiting()).decode('utf-8')
decoded_serial = (data_string)
print(decoded_serial)
class Ui_MainWindow(object):
def workerSignal(self):
self.worker.start()
print('Worker started')
def setupUi(self, MainWindow):
self.worker = WorkerThread()
MainWindow.setObjectName('MainWindow')
MainWindow.resize(250, 300)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName('centralwidget')
self.groupBox = QtWidgets.QGroupBox(self.centralwidget)
self.groupBox.setGeometry(QtCore.QRect(30, 100, 201, 161))
self.groupBox.setObjectName('groupBox')
self.LineEdit = QtWidgets.QLineEdit(self.groupBox)
self.LineEdit.setGeometry(QtCore.QRect(10, 50, 181, 20))
self.LineEdit.setObjectName('LineEdit')
self.Label = QtWidgets.QLabel(self.groupBox)
self.Label.setGeometry(QtCore.QRect(10, 30, 181, 10))
self.Label.setObjectName('Label')
MainWindow.setCentralWidget(self.centralwidget)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate('MainWindow', 'TestApp'))
self.groupBox.setTitle(_translate('MainWindow', 'Serial'))
self.Label.setText(_translate('MainWindow', 'Serial'))
def displaySerial(self):
self.LineEdit.setText(decoded_serial)
if __name__ == '__main__':
import sys
def eventTimer():
ui.workerSignal()
def serialLoop():
ui.displaySerial()
app = QtWidgets.QApplication(sys.argv)
QtCore.QTimer.singleShot(0, eventTimer)
serial_timer = QtCore.QTimer()
serial_timer.timeout.connect(serialLoop)
serial_timer.start(1000)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec())
To try to pass the value to the main GUI class I have tried something like:
class WorkerThread(QThread):
def run(self):
WorkerThread.data = decoded_serial
class Ui_MainWindow(object, WorkerThread):
print(WorkerThread.data)
Although I do not seem to be doing it the right way, and it returns some errors.
What would be the best approach in order to send the retrieved serial string to the line edit field?
I have had a look at multiple solutions presented in the past, but I do not seem to understand how to implement those in my code.
Any guidance is highly appreciated!

Python - Change plot parameters in QThread

I'm looking for a solution to solve the following issue: My program starts with a plot of all data, later when I start a function a worker is plotting the same graph according to it times. So there are two lines, first a red one that shows how the plot will look like, later the plot that follows the first graph, done by a worker.
Unfortunately, the second plot is very thin. I've created a variable called "plotsize" in my example. This can change the first plot, but I have no idea how to change the characteristics of the second one within the threading with a worker. I'm using QThread.
Here my example codes, two data. Name of the GUI file is just GUI.py
#GUI.py
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(739, 532)
MainWindow.setStyleSheet("")
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.WidgetPlot = PlotWidget(self.centralwidget)
self.WidgetPlot.setGeometry(QtCore.QRect(100, 40, 541, 341))
self.WidgetPlot.setObjectName("WidgetPlot")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(330, 420, 93, 28))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "Main Window"))
self.pushButton.setText(_translate("MainWindow", "Start"))
from pyqtgraph import PlotWidget
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
And here the script:
#PROGRAM/SCRIPT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import datetime
import pyqtgraph
import time
plotsize = 2
# =============================================================================
# Threading for not freezing the GUI while running
# =============================================================================
class Worker(QtCore.QObject):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
widgetplot = QtCore.pyqtSignal(list, list)
def __init__(self):
super().__init__()
def start(self):
self._run()
def _run(self):
self._count = 0
self.x = plot_time[:0]
self.y = plot_value[:0]
self.widgetplot.emit(self.x, self.y)
self._start_time = datetime.datetime.now()
while 0 <= self._count < 100:
self._count_prev = self._count
QtCore.QThread.usleep(10000)
self._diff = datetime.datetime.now() - self._start_time
self._count = int((self._diff.total_seconds() * 10))
if(self._count != self._count_prev):
print(self._count)
self.x = plot_time[:self._count]
self.y = plot_value[:self._count]
self.widgetplot.emit(self.x, self.y)
class my_class(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(my_class, self).__init__(parent)
self.setupUi(self)
self.thread = QtCore.QThread(self)
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.start)
self.worker.finished.connect(self.thread.quit)
self.worker.widgetplot.connect(self.WidgetPlot.plot)
self.pushButton.clicked.connect(self.my_function)
self.WidgetPlot.setMouseEnabled(x=False, y=False)
font=QtGui.QFont()
font.setPixelSize(20)
font.setBold(True)
self.WidgetPlot.getAxis("bottom").setTickFont(font)
self.WidgetPlot.getAxis("left").setTickFont(font)
def my_function(self):
global plot_time
plot_time = []
global plot_value
plot_value = []
for i in range(100):
plot_time.append(i)
plot_value.append(i)
self.start()
def preview_plot(self):
self.WidgetPlot.clear()
self.WidgetPlot.setXRange(0, 105, padding=0)
self.WidgetPlot.setYRange(0, 105, padding=0)
self.preview_line = self.WidgetPlot.plot(plot_time, plot_value, pen=pyqtgraph.mkPen('r', width=plotsize))
def start(self):
self.preview_plot()
self.thread.start()
def main():
app = QApplication(sys.argv)
form = my_class()
form.show()
app.exec_()
if __name__ == '__main__':
Many Thanks in advance!
Andrew
Based on what I have inferred from your request (i.e. you want just to use two different size for overlapping plot lines) I've rewritten your code with a series of improvements (in my humble opinion).
At the very core of the problem, what I've done is to create a separate pyqtgraph.PlotDataItem curve, plot it on top of your preview_line and handle a different size for it (completely randomic, please adjust the size as you desire). Thus, it's all about using
self.second_line.setData(x, y)
instead of recreating it every time with self.WidgetPlot.plot(...)
All the details are into the comments.
#PROGRAM/SCRIPT
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import QApplication
import sys
import GUI
import datetime
import pyqtgraph
import time
plotsize = 20
# =============================================================================
# Threading for not freezing the GUI while running
# =============================================================================
class Worker(QtCore.QThread):
progress = QtCore.pyqtSignal(int)
finished = QtCore.pyqtSignal()
widgetplot = QtCore.pyqtSignal(list, list)
def __init__(self, plot_time, plot_value):
QtCore.QThread.__init__(self, objectName='WorkerThread')
# AS LONG AS YOU DON'T MODIFY plot_time and plot_value INTO THIS THREAD, they are thread-safe.
# If you intend to modify them in the future (into this thread), you need to implement a mutex/lock system to avoid races
# Note that lists are mutable: you're storing the reference to the actual object that will always be edited into the MAIN THREAD
self.plot_time = plot_time
self.plot_value = plot_value
def run(self):
# This is naturally a LOCAL variable
# self._count = 0
_count = 0
# --- This is useless, it plots ([], [])
# self.widgetplot.emit(self.x, self.y)
# self.x = plot_time[:0]
# self.y = plot_value[:0]
# -----------------------------------
_start_time = datetime.datetime.now()
while 0 <= _count < 100:
# Use local variable!
_count_prev = _count
QtCore.QThread.usleep(10000)
_diff = datetime.datetime.now() - _start_time
_count = int((_diff.total_seconds() * 10))
if(_count != _count_prev):
print(_count)
x = self.plot_time[:_count]
y = self.plot_value[:_count]
# Since plot_time and plot_value are managed by main thread, you would just need to emit _count variable.
# But I'll stick with your code
self.widgetplot.emit(x, y)
class my_class(QtWidgets.QMainWindow, GUI.Ui_MainWindow):
def __init__(self, parent=None):
super(my_class, self).__init__(parent)
self.setupUi(self)
# In your version, the range is constant irrespective of the data being updated. It can be moved here
self.WidgetPlot.setXRange(0, 105, padding=0)
self.WidgetPlot.setYRange(0, 105, padding=0)
# Create and store ONE line, you will change just the underlying data, not the object itself. WAY MORE efficient
# Different pen with different plot size. Is this what you're seeking?
self.second_line = pyqtgraph.PlotDataItem(pen=pyqtgraph.mkPen('w', width=plotsize*2))
# Here class variable initialization goes
self.plot_time = []
self.plot_value = []
# You don't need `moveToThread`. You can just subclass QThread
self.worker_thread = Worker(self.plot_time, self.plot_value)
# I changed the name just to highlight the fact it is just an update and not a plot rebuilt
self.worker_thread.widgetplot.connect(self.update_second_line_plot)
self.pushButton.clicked.connect(self.my_function)
self.WidgetPlot.setMouseEnabled(x=False, y=False)
font=QtGui.QFont()
font.setPixelSize(20)
font.setBold(True)
self.WidgetPlot.getAxis("bottom").setTickFont(font)
self.WidgetPlot.getAxis("left").setTickFont(font)
def my_function(self):
# Use class variable instead, see the __init__
# ...Not efficient
# for i in range(100):
# plot_time.append(i)
# plot_value.append(i)
# Better:
_l = list(range(100))
self.plot_time.extend(_l)
self.plot_value.extend(_l)
# DON'T DO self.plot_time = list(range(100)) --> it will recreate a new object, but this one is shared with the worker thread!
self.start()
def update_second_line_plot(self, plot_time, plot_value):
# Just update the UNDERLYING data of your curve
self.second_line.setData(plot_time, plot_value)
def start(self):
# First plot preview_plot. done ONCE
self.WidgetPlot.plot(self.plot_time, self.plot_value, pen=pyqtgraph.mkPen('r', width=plotsize))
# Add NOW the new line to be drawn ON TOP of the preview one
self.WidgetPlot.addItem(self.second_line)
# It automatically will do the job. You don't need any plotfunction
self.worker_thread.start()
def main():
app = QApplication(sys.argv)
form = my_class()
form.show()
app.exec_()
if __name__ == '__main__':
main()
The result:
You currently have a signal connected to the plot method through self.worker.widgetplot.connect(self.WidgetPlot.plot), but you'll have a better time if you make a persistent plot - with a specific pen set, at your desired thickness - and then connect your widgetplot signal to that plot's setData method.
# ... Ui_MainWindow.__init__
self.plot_curve = self.WidgetPlot.plot(pen=mkPen("w", width=plotsize))
# ... in my_class.__init__
self.worker.widgetplot.connect(self.plot_curve.setData)

QTextBrowser text broken (with QThread for python) - Solved [duplicate]

I have a GUI made in Designer (pyqt5). A function in my main class needs to work on a separate thread. I also catch the stdout on a QtextEdit LIVE during operations. Everything so far works.
Right now I'm trying to implement a ProgressBar onto my main GUI form. The bar needs to show live progression just like it does on the textEdit.
The example code below works on Linux without any warnings. But on Windows I get the error:
QObject::setParent: Cannot set parent, new parent is in a different thread
I know that this is due to me having a ui element modification within my threaded function. I did my research but all the answers point to using QThreads (just when I started to understand basic threading!). I would prefer a way to update my GUI without having to change the current threading system below.
Here is the example code:
import sys
import threading
import time
from PyQt5.QtWidgets import QApplication, QMainWindow
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtGui import QTextCursor
from ui_form import Ui_Form
class EmittingStream(QObject):
textWritten = pyqtSignal(str)
def write(self, text):
self.textWritten.emit(str(text))
class Form(QMainWindow):
finished = pyqtSignal()
def __init__(self, parent=None):
super(Form, self).__init__(parent)
# Install the custom output stream
sys.stdout = EmittingStream(textWritten=self.normalOutputWritten)
self.ui = Ui_Form()
self.ui.setupUi(self)
self.ui.pushButton_run.clicked.connect(self.start_task)
self.finished.connect(self.end_task)
def start_task(self):
self.thread = threading.Thread(target=self.run_test)
self.thread.start()
self.ui.pushButton_run.setEnabled(False)
def end_task(self):
self.ui.pushButton_run.setEnabled(True)
def __del__(self):
# Restore sys.stdout
sys.stdout = sys.__stdout__
def normalOutputWritten(self, text):
"""Append text to the QTextEdit."""
cursor = self.ui.textEdit.textCursor()
cursor.movePosition(QTextCursor.End)
cursor.insertText(text)
self.ui.textEdit.setTextCursor(cursor)
self.ui.textEdit.ensureCursorVisible()
def run_test(self):
for i in range(100):
per = i + 1
self.ui.progressBar.setValue(per)
print("%%%s" % per)
time.sleep(0.15) # simulating expensive task
print("Task Completed!")
time.sleep(1.5)
self.ui.progressBar.reset()
self.finished.emit()
def main():
app = QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
the ui:
# -*- coding: utf-8 -*-
# Form implementation generated from reading ui file 'form.ui'
#
# Created: Mon Apr 30 13:43:19 2018
# by: PyQt5 UI code generator 5.2.1
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_Form(object):
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(Form)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_run = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_run.setGeometry(QtCore.QRect(40, 20, 311, 191))
self.pushButton_run.setObjectName("pushButton_run")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(40, 230, 721, 241))
self.textEdit.setObjectName("textEdit")
self.progressBar = QtWidgets.QProgressBar(self.centralwidget)
self.progressBar.setGeometry(QtCore.QRect(40, 490, 721, 23))
self.progressBar.setObjectName("progressBar")
Form.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(Form)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 25))
self.menubar.setObjectName("menubar")
Form.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(Form)
self.statusbar.setObjectName("statusbar")
Form.setStatusBar(self.statusbar)
self.retranslateUi(Form)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "MainWindow"))
self.pushButton_run.setText(_translate("Form", "RUN"))
Somehow I need to -instantly- inform the gui thread (from my running thread) that the progress bar value is changing (a process that could take up minutes to complete).
Define a custom signal that sends updates to the progress-bar:
class Form(QMainWindow):
finished = pyqtSignal()
updateProgress = pyqtSignal(int)
def __init__(self, parent=None):
super(Form, self).__init__(parent)
...
self.updateProgress.connect(self.ui.progressBar.setValue)
def run_test(self):
for i in range(100):
per = i + 1
self.updateProgress.emit(per)
...

Using PyQt4.QtGui.QMouseEvent in a QWidget

I am using a PyQt4.QMainWindow as my application interface, and I want to get the x and y coordinates of the mouse inside of a QWidget and set them continuously in 2 textBrowsers in the MainWindow.
The documentation for QWidget is here.
and the documentation for QMouseEvent is here.
Here is the code
from PyQt4 import QtGui
from PyQt4.QtGui import QApplication
import sys
class Ui_MainWindow(object):
def setupUI(self, MainWindow):
self.textBrowser_1 = QtGui.QTextBrowser(self.tab)
self.textBrowser_2 = QtGui.QTextBrowser(self.tab)
self.widget_1 = QtGui.QWidget(self.tab)
self.widget_1.setMouseTracking(True)
class MyMainScreen(QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow() # This is from a python export from QtDesigner
# There is a QWidget inside that is self.ui.widget_1
# and 2 textBrowsers, textBrowser_1 and textBrowser_2
# I want to populate these 2 textBrowsers with the current x,y
# coordinates.
if __name__ == "__main__":
app = QApplication(sys.argv)
mainscreen = MyMainScreen()
mainscreen.show()
app.exec_()
When you apply setMouseTracking it only applies to that widget, and not to your children, so you must manually, in the next solution:
def setMouseTracking(self, flag):
def recursive_set(parent):
for child in parent.findChildren(QtCore.QWidget):
child.setMouseTracking(flag)
recursive_set(child)
QtGui.QWidget.setMouseTracking(self, flag)
recursive_set(self)
complete code:
from PyQt4 import QtCore
from PyQt4 import QtGui
from PyQt4.QtGui import QApplication, QMainWindow
import sys
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.resize(800, 132)
self.centralwidget = QtGui.QWidget(MainWindow)
self.horizontalLayout = QtGui.QHBoxLayout(self.centralwidget)
self.textBrowser_1 = QtGui.QTextBrowser(self.centralwidget)
self.horizontalLayout.addWidget(self.textBrowser_1)
self.textBrowser_2 = QtGui.QTextBrowser(self.centralwidget)
self.horizontalLayout.addWidget(self.textBrowser_2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtGui.QStatusBar(MainWindow)
MainWindow.setStatusBar(self.statusbar)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
class MyMainScreen(QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self, parent)
self.ui = Ui_MainWindow() # This is from a python export from QtDesigner
self.ui.setupUi(self)
self.setMouseTracking(True)
self.ui.textBrowser_1.setMouseTracking(True)
self.ui.textBrowser_2.setMouseTracking(True)
self.ui.menubar.setMouseTracking(True)
self.ui.statusbar.setMouseTracking(True)
def setMouseTracking(self, flag):
def recursive_set(parent):
for child in parent.findChildren(QtCore.QWidget):
child.setMouseTracking(flag)
recursive_set(child)
QtGui.QWidget.setMouseTracking(self, flag)
recursive_set(self)
def mouseMoveEvent(self, event):
pos = event.pos()
self.ui.textBrowser_1.append(str(pos.x()))
self.ui.textBrowser_2.append(str(pos.y()))
QtGui.QMainWindow.mouseMoveEvent(self, event)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainscreen = MyMainScreen()
mainscreen.show()
app.exec_()
This is my output:

Categories

Resources