I have a program with two QTreeView. When I press a button, I need to allow the user to select several elements, and when user press Escape, to transfer the selected elements to a waiting function, which will then pass to the handler function.
I've tried to use threads, gevent, and asyncio.
this function in main class, i run this function when i need get some files.
import sys
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtWidgets import *
import ui
import exampleQTV
import asyncio
class PyMap(QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.select.clicked.connect(self.selectAction)
def selectAction(self):
self.getSomeFiles("Example", "Select some files, and press Escape", self.leftView)
def getSomeFiles(self, title, path, view):
# return QFileDialog.getOpenFileNames(self, title, path) ### older, and ugly variant
buttonReply = QMessageBox.information(self, "Information", "Select needed files",
QMessageBox.Ok)
loop = asyncio.get_event_loop()
tasks = [loop.create_task(view.getFiles())]
wait_tasks = asyncio.wait(tasks)
result = loop.run_until_complete(asyncio.gather(*tasks))
print (result)
# result = view.getFiles()
return result
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.rightView = exampleQTV.exampleQTV()
self.rightView.setObjectName("rightView")
self.gridLayout.addWidget(self.rightView, 1, 1, 1, 1)
self.leftView = exampleQTV.exampleQTV()
self.leftView.setObjectName("leftView")
self.gridLayout.addWidget(self.leftView, 1, 0, 1, 1)
self.select = QtWidgets.QPushButton(self.centralwidget)
self.select.setObjectName("select")
self.gridLayout.addWidget(self.select, 0, 0, 1, 2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "MainWindow"))
self.select.setText(_translate("MainWindow", "Select"))
def main():
app = QtWidgets.QApplication(sys.argv)
window = PyMap()
window.show()
app.exec_()
if __name__ == '__main__':
main()
this functions in QTreeView's class
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from multiprocessing.pool import ThreadPool
import asyncio
class exampleQTV(QTreeView):
def __init__(self):
QTreeView.__init__(self)
self.model = QFileSystemModel()
self.model.setRootPath("/") # i'm on linux if you not change from / to for example D:\\
self.setModel(self.model)
self.eventCalled = False
self.requestForEscape = False
self.setColumnHidden(1, True)
self.setColumnHidden(2, True)
self.setColumnHidden(3, True)
def keyPressEvent(self, event):
if event.key() == Qt.Key_Escape:
self.eventCalled = false
def getFiles_thread(self):
while True:
if self.requestForEscape == True:
if self.eventCalled == False:
return self.selectedIndexes()
async def getFiles(self):
self.setSelectionMode(QAbstractItemView.SelectionMode.MultiSelection)
self.eventCalled = True
self.requestForEscape = True
pool = ThreadPool(processes=1)
self.printMessage(1)
async_result = pool.apply_async(self.getFiles_thread)
self.printMessage(2)
result = await async_result.get()
self.printMessage(3)
# ### Sorry if it not corrent, i'm just copied from doc
# tasks = [self.getFiles_thread()]
# loop = asyncio.get_event_loop()
# result = loop.run_until_complete(asyncio.gather(*tasks))
# task = [gevent.spawn(self.getFiles_thread(), 2)]
# result = gevent.joinall(task)
# result = await self.getFiles_thread()
self.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection)
return result
def printMessage(self, message):
print(message)
output:
1
2
The tasks of the GUI such as the selection of items, listening to keyboard events, etc. do not need to be executed in another thread, nor in another process.
Your way of programming is procedural but in the GUI the paradigm of Event-driven Programming is used, in the case of Qt it is implemented through the signals, slot and events. Only tasks that synchronously consume a lot of time must be executed in another thread, for example I emulated the task of copying with QtCore.QThread.sleep(...).
Considering the above I have implemented the logic of enabling the selection, listen to the keyPressEvent, emit a signal with the selected indexes, and call the heavy function with the data.
main.py
import sys
from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets
import exampleQTV
class PyMap(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.select.clicked.connect(self.selectAction)
self.leftView.selectedIndexesSignal.connect(
self.on_selectedIndexesSignal
)
thread = QtCore.QThread(self)
thread.start()
self.m_worker = Worker()
self.m_worker.moveToThread(thread)
#QtCore.pyqtSlot()
def selectAction(self):
buttonReply = QtWidgets.QMessageBox.information(
self,
"Information",
"Select needed files",
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel,
)
if buttonReply == QtWidgets.QMessageBox.Ok:
self.leftView.setEnableMultiSelection(True)
#QtCore.pyqtSlot(list)
def on_selectedIndexesSignal(self, indexes):
paths = []
for ix in indexes:
path = ix.data(QtWidgets.QFileSystemModel.FilePathRole)
paths.append(path)
print(paths)
wrapper = partial(self.m_worker.heavyTask, paths)
QtCore.QTimer.singleShot(0, wrapper)
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtWidgets.QGridLayout(self.centralwidget)
self.gridLayout.setObjectName("gridLayout")
self.rightView = exampleQTV.ExampleQTV()
self.rightView.setObjectName("rightView")
self.gridLayout.addWidget(self.rightView, 1, 1, 1, 1)
self.leftView = exampleQTV.ExampleQTV()
self.leftView.setObjectName("leftView")
self.gridLayout.addWidget(self.leftView, 1, 0, 1, 1)
self.select = QtWidgets.QPushButton(self.centralwidget)
self.select.setObjectName("select")
self.gridLayout.addWidget(self.select, 0, 0, 1, 2)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 22))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "MainWindow"))
self.select.setText(_translate("MainWindow", "Select"))
class Worker(QtCore.QObject):
#QtCore.pyqtSlot(list)
def heavyTask(self, paths):
print("started")
# emulate heavy task
QtCore.QThread.sleep(5)
print(paths)
print("finished")
def main():
app = QtWidgets.QApplication(sys.argv)
window = PyMap()
window.show()
app.exec_()
if __name__ == "__main__":
main()
exampleQTV.py
from PyQt5 import QtCore, QtGui, QtWidgets
class ExampleQTV(QtWidgets.QTreeView):
selectedIndexesSignal = QtCore.pyqtSignal(list)
def __init__(self, parent=None):
super(ExampleQTV, self).__init__(parent)
self.model = QtWidgets.QFileSystemModel(self)
self.model.setRootPath(QtCore.QDir.rootPath())
self.setModel(self.model)
for i in (1, 2, 3):
self.setColumnHidden(i, True)
self.setSelectionMode(QtWidgets.QAbstractItemView.NoSelection)
self.expandAll()
def setEnableMultiSelection(self, enable):
self.setSelectionMode(
QtWidgets.QAbstractItemView.MultiSelection
if enable
else QtWidgets.QAbstractItemView.NoSelection
)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
self.selectedIndexesSignal.emit(self.selectedIndexes())
self.selectionModel().clearSelection()
self.setEnableMultiSelection(False)
super(ExampleQTV, self).keyPressEvent(event)
Related
I tried to design a pyqt gui in when I press "x" will stop run function a counter , what should i do ?
I only know to use the following code
def closeEvent(self,event):
pass
Logic Code
from PyQt5 import QtWidgets, QtGui, QtCore
from PyQt5.QtCore import *
import sys
import time
from GUI import Ui_MainWindow
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super(MainWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.thread = MyThread(self)
self.thread.trigger.connect(self.textBrowser)
self.thread.start()
def textBrowser(self,a):
self.ui.textBrowser_4.clear()
self.ui.textBrowser_4.append(str(a))
def closeEvent(self,event):
pass
class MyThread(QThread):
trigger = pyqtSignal(str)
def run(self):
a = 0
while True:
a = a + 1
self.trigger.emit(str(a))
time.sleep(1)
if __name__ == "__main__":
app = QtWidgets.QApplication([])
MainWindow = MainWindow()
MainWindow.show()
sys.exit(app.exec_())
GUI Code
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(944, 587)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label_2 = QtWidgets.QLabel(self.centralwidget)
self.label_2.setGeometry(QtCore.QRect(30, 310, 181, 41))
self.label_2.setObjectName("label_2")
self.label_4 = QtWidgets.QLabel(self.centralwidget)
self.label_4.setGeometry(QtCore.QRect(30, 20, 141, 21))
self.label_4.setObjectName("label_4")
self.textBrowser_4 = QtWidgets.QTextBrowser(self.centralwidget)
self.textBrowser_4.setGeometry(QtCore.QRect(230, 300, 241, 61))
self.textBrowser_4.setObjectName("textBrowser_4")
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", "MainWindow"))
self.label_2.setText(_translate("MainWindow", "counter"))
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_())
Add an initiator and an is_running variable in your Thread class like so :
def __init__(self):
self.is_running = True
Then make your loop depend on the truthness of this new variable :
def run(self):
a = 0
while self.is_running:
... your logic here
And finally when you want to exit in closeEvent() :
self.thread.is_running = False
Cheers
I'm trying to get an output from an arduino to be updated in two pyqt windows, one of which is a matplotlib plot. If I comment out the non-plot thread window, the plot works. But when I try to run both at the same time, I get the message
3 Device is already open
3 Device is already open
but nothing shows up in the plot, though the window is there. Whereas the non-plot window is updated with values. I'm not sure if I should use QThreadpool instead of QThread. On a side note, if I try to rerun the code, I get the message
2 Access is denied.
2 Access is denied.
2 Access is denied.
so I have to unplug and replug in the arduino, uploading its code again, before running the python script again, and neither window is updated with values.
The arduino code I use, to just give signals over 1 second intervals, to test it out, is
int PinOutput = 13;
int PinInput = A0;
int inph;
int inpl;
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
pinMode(PinInput, INPUT);
pinMode(PinOutput, OUTPUT);
}
void loop() {
// put your main code here, to run repeatedly:
inpl = analogRead(PinInput)/4;
Serial.println(inpl);
analogWrite(PinOutput,255);
delay(1000);
inph = analogRead(PinInput)/4;
Serial.println(inph);
analogWrite(PinOutput,0);
delay(1000);
}
Where pin 13 is connected to A0 on the arduino.
The python code is
import sys
from PyQt5 import QtCore, QtWidgets, QtSerialPort
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
seril_port_connection = QtSerialPort.QSerialPort("COM3")
seril_port_connection.setBaudRate(QtSerialPort.QSerialPort.Baud9600)
class MyThread2(QtCore.QThread):
ard_signal = QtCore.pyqtSignal(str)
def __init__(self):
QtCore.QThread.__init__(self)
self.serial_port = seril_port_connection
self.serial_port.errorOccurred.connect(self.handle_error)
self.serial_port.readyRead.connect(self.run)
self.serial_port.open(QtCore.QIODevice.ReadWrite)
def run(self):
while self.serial_port.canReadLine():
line = self.serial_port.readLine().data().decode().strip().strip('\x00')
try:
self.ard_signal.emit(str(line))
except ValueError as e:
print("error", e)
def handle_error(self, error):
if error == QtSerialPort.QSerialPort.NoError:
return
print(error, self.serial_port.errorString())
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(325, 237)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(110, 20, 61, 16))
self.label.setObjectName("label")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(90, 60, 104, 71))
self.textEdit.setObjectName("textEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(100, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 325, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "test window"))
self.label.setText(_translate("MainWindow", "pyqt5 tests"))
self.pushButton.setText(_translate("MainWindow", "test button"))
self.pushButton.clicked.connect(self.label_change)
self.thread_start = MyThread()
self.thread_start.ard_signal.connect(self.label.setText)
self.thread_start.start()
def label_change(self):
self.pushButton.setText('Button Clicked!')
self.textEdit.setText('taco')
class MainWindowm(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindowm, self).__init__(*args, **kwargs)
self.canvas = FigureCanvasQTAgg(Figure(figsize=(5, 4), dpi=100))
self.setCentralWidget(self.canvas)
self.axes = self.canvas.figure.subplots()
n_data = 10
self.xdata = list(range(n_data))
self.ydata = [0 for i in range(n_data)]
self.thread_start = MyThread2()
self.thread_start.ard_signal.connect(self.update_plot)
self.thread_start.start()
def handle_error(self, error):
if error == QtSerialPort.QSerialPort.NoError:
return
print(error, self.serial_port.errorString())
def update_plot(self, value):
self.ydata = self.ydata[1:] + [float(value)]
self.axes.cla()
self.axes.plot(self.xdata, self.ydata, "r")
self.canvas.draw()
class MyThread(QtCore.QThread):
ard_signal = QtCore.pyqtSignal(str)
def __init__(self):
QtCore.QThread.__init__(self)
self.serial_port = seril_port_connection
self.serial_port.errorOccurred.connect(self.handle_error)
self.serial_port.readyRead.connect(self.run)
self.serial_port.open(QtCore.QIODevice.ReadWrite)
def run(self):
while self.serial_port.canReadLine():
line = self.serial_port.readLine().data().decode().strip().strip('\x00')
try:
self.ard_signal.emit(str(line))
except ValueError as e:
print("error", e)
def handle_error(self, error):
if error == QtSerialPort.QSerialPort.NoError:
return
print(error, self.serial_port.errorString())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
w = MainWindowm()
w.show()
sys.exit(app.exec_())
So I'm trying to make the arduino output available globally, to then be used by different threads at the same time, being updated in two windows, one with a plot. Any info on how to do this would help, thanks.
I assume that the OP is using my old answer, and in that solution indicate that the use of QSerialPort avoids the unnecessary use of threads, and this also applies in this case. On the other hand, a serial port cannot be handled by several classes. In this case, you just have to create a class that manages the serial port and distributes the information to the other elements.
import sys
from PyQt5 import QtCore, QtWidgets, QtSerialPort
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(325, 237)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(110, 20, 61, 16))
self.label.setObjectName("label")
self.textEdit = QtWidgets.QTextEdit(self.centralwidget)
self.textEdit.setGeometry(QtCore.QRect(90, 60, 104, 71))
self.textEdit.setObjectName("textEdit")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setGeometry(QtCore.QRect(100, 150, 75, 23))
self.pushButton.setObjectName("pushButton")
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 325, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "test window"))
self.label.setText(_translate("MainWindow", "pyqt5 tests"))
self.pushButton.setText(_translate("MainWindow", "test button"))
class MainWindowm(QtWidgets.QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindowm, self).__init__(*args, **kwargs)
self.canvas = FigureCanvasQTAgg(Figure(figsize=(5, 4), dpi=100))
self.setCentralWidget(self.canvas)
self.axes = self.canvas.figure.subplots()
n_data = 10
self.xdata = list(range(n_data))
self.ydata = [0 for i in range(n_data)]
def update_plot(self, value):
self.ydata = self.ydata[1:] + [value]
self.axes.cla()
self.axes.plot(self.xdata, self.ydata, "r")
self.canvas.draw()
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super().__init__(parent)
self.setupUi(self)
def update_text(self, value):
self.label.setNum(value)
self.label.adjustSize()
class SerialManager(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(float)
def __init__(self, parent=None):
super().__init__(parent)
self.serial_port = QtSerialPort.QSerialPort("COM3")
self.serial_port.setBaudRate(QtSerialPort.QSerialPort.Baud9600)
self.serial_port.errorOccurred.connect(self.handle_error)
self.serial_port.readyRead.connect(self.handle_ready_read)
self.serial_port.open(QtCore.QIODevice.ReadWrite)
def handle_ready_read(self):
while self.serial_port.canReadLine():
codec = QtCore.QTextCodec.codecForName("UTF-8")
line = codec.toUnicode(self.serial_port.readLine()).strip().strip("\x00")
try:
print(line)
value = float(line)
except ValueError as e:
print("error", e)
else:
self.valueChanged.emit(value)
def handle_error(self, error):
if error == QtSerialPort.QSerialPort.NoError:
return
print(error, self.serial_port.errorString())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w1 = MainWindow()
w1.show()
w = MainWindowm()
w.show()
manager = SerialManager()
manager.valueChanged.connect(w1.update_text)
manager.valueChanged.connect(w.update_plot)
sys.exit(app.exec_())
first i created the functional parts of my code and later decided to add a interface to it, so i have linked the interface and and the main function of the previous code as bellow.
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(921, 988)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.sheet2 = QtWidgets.QLabel(self.centralwidget)
self.sheet2.setObjectName("sheet2")
self.verticalLayout.addWidget(self.sheet2)
self.sheet1 = QtWidgets.QLabel(self.centralwidget)
self.sheet1.setObjectName("sheet1")
self.verticalLayout.addWidget(self.sheet1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 921, 21))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
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", "MainWindow"))
# self.label_2.setText(_translate("MainWindow", "TextLabel"))
# self.label.setText(_translate("MainWindow", "TextLabel"))
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
def update_sheet2(self, Image):
qim = ImageQt(Image)
pix = QtGui.QPixmap.fromImage(qim)
pix = pix.scaled(self.sheet2.width(), self.sheet2.height(), QtCore.Qt.KeepAspectRatio)
self.sheet2.setPixmap(pix)
self.sheet2.setAlignment(QtCore.Qt.AlignCenter)
def update_sheet1(self, Image):
qim = ImageQt(Image)
pix = QtGui.QPixmap.fromImage(qim)
pix = pix.scaled(self.sheet1.width(), self.sheet1.height(), QtCore.Qt.KeepAspectRatio)
self.sheet1.setPixmap(pix)
self.sheet1.setAlignment(QtCore.Qt.AlignCenter)
def run_ui():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
w.background = main(w)
t = Process(target=w.background.process)
t.start()
sys.exit(app.exec_())
in the function named main(ui) in this code, the codes where it uses the ui is as follows. and i used the run_ui() function to run the code
def main(ui):
for i in range(100000000):
x=1
y = x*x*x
img = Image.open('XXX.png'.format(GRAY_PATH,1))
ui.update_sheet1(img)
if __name__ == '__main__':
run_ui()
and i have passed the ui 'w' as an argument to the main funciton, where it uses that reference to call the update_sheet1,2 functions with image data.
this lags the GUI and its always not responding and the images also do not appear on the GUI.
i think this has something to do with the way i liked the interface. but dont know how to fix it.
thanks for any help.
Qt does not support multiprocessing so to remove complexity from the problem use threading. In this case Qt also indicates that the GUI should not be modified from another thread, instead of that I create a QObject and export it to the other thread, this QObject has a signal that transports the image.
On the other hand when you do main(w) you are invoking the heavy task in the main process and that causes the GUI to freeze, instead you have to pass the name of the function, and the arguments of that function through of args:
from PyQt5 import QtCore, QtGui, QtWidgets
from PIL import Image
from PIL.ImageQt import ImageQt
from threading import Thread
# ...
class Signaller(QtCore.QObject):
imageSignal = QtCore.pyqtSignal(Image.Image)
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
def update_sheet2(self, Image):
# ...
#QtCore.pyqtSlot(Image.Image)
def update_sheet1(self, Image):
qim = ImageQt(Image)
pix = QtGui.QPixmap.fromImage(qim)
pix = pix.scaled(self.sheet1.width(), self.sheet1.height(), QtCore.Qt.KeepAspectRatio)
self.sheet1.setPixmap(pix)
self.sheet1.setAlignment(QtCore.Qt.AlignCenter)
def run_ui():
import sys
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
signaller = Signaller()
signaller.imageSignal.connect(w.update_sheet1)
t = Thread(target=main, args=(signaller,), daemon=True)
t.start()
sys.exit(app.exec_())
def main(signaller):
for i in range(100000000):
x=1
y = x*x*x
img = Image.open('XXX.png')
signaller.imageSignal.emit(img)
if __name__ == '__main__':
run_ui()
I am trying to use signals and slots to update an element in my program.
The first page opens and reads the config file to set some labels.
I have an "Options" page that you can update the config file.
What I want to happen is when you click "save" on the second window it saves to the config, and then on the first page runs a function (read_Config) that will then read the updated config file and update the label to change. I have tried multiple different methods and am failing to understand how the signals and slots work. Thanks for your help. Here is the code, it is two files. test.py and config.ini.
This is the test.py:
#!/bin/usr/env python
import sys
import configparser
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import QObject, pyqtSignal
from PyQt5.QtWidgets import QApplication
class TestApp(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(TestApp, self).__init__()
self.setupUi(self)
self.dialogs = []
self.read_Config()
self.window2Button.clicked.connect(self.goto_Pagetwo)
self.closeButton.clicked.connect(self.close)
def read_Config(self):
config = configparser.ConfigParser()
config.read('config.ini')
labelone = config['default']['labelone']
self.label.setText(labelone)
def goto_Pagetwo(self):
dialog = Pagetwo(self)
self.dialogs.append(dialog)
dialog.show()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(244, 113)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setAlignment(QtCore.Qt.AlignCenter)
self.label.setObjectName("label")
self.verticalLayout.addWidget(self.label)
self.window2Button = QtWidgets.QPushButton(self.centralwidget)
self.window2Button.setObjectName("window2Button")
self.verticalLayout.addWidget(self.window2Button)
self.closeButton = QtWidgets.QPushButton(self.centralwidget)
self.closeButton.setObjectName("closeButton")
self.verticalLayout.addWidget(self.closeButton)
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.label.setText(_translate("MainWindow", "This is a Label"))
self.window2Button.setText(_translate("MainWindow", "Window 2"))
self.closeButton.setText(_translate("MainWindow", "Close"))
class Pagetwo(QtWidgets.QMainWindow):
trigger = pyqtSignal()
def __init__(self, parent):
super(Pagetwo, self).__init__()
self.setupUi(self)
self.dialogs = []
self.saveButton.clicked.connect(self.save)
self.closeButton.clicked.connect(self.close)
def save(self):
string = self.lineEdit.text()
config = configparser.ConfigParser()
config.read('config.ini')
config.set('default', 'labelone', string)
with open('config.ini', 'w') as configfile:
config.write(configfile)
self.trigger.connect(self.parent().read_Config())
self.trigger.emit()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(246, 128)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.lineEdit = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit.setObjectName("lineEdit")
self.verticalLayout.addWidget(self.lineEdit)
self.saveButton = QtWidgets.QPushButton(self.centralwidget)
self.saveButton.setObjectName("saveButton")
self.verticalLayout.addWidget(self.saveButton)
self.closeButton = QtWidgets.QPushButton(self.centralwidget)
self.closeButton.setObjectName("closeButton")
self.verticalLayout.addWidget(self.closeButton)
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.saveButton.setText(_translate("MainWindow", "Save"))
self.closeButton.setText(_translate("MainWindow", "Close"))
def main():
app = QApplication(sys.argv)
main = TestApp()
main.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
And this is the config.ini file:
[default]
labelone = This is a label
Thanks to anyone who takes the time to help me with this.
UnclassedPenguin
You have the following errors:
The Pagetwo constructor does not use the parent parameter so parent() will be None:
class Pagetwo(QtWidgets.QMainWindow):
trigger = pyqtSignal()
def __init__(self, parent): # <-----
super(Pagetwo, self).__init__() # <---- You have to pass
# ...
It is recommended that the connection be made only once because the connection does not discriminate if there was already the connection, for example if you press n times the save button of Pagetwo then there will be n connections so it will call the same slot n times, in this case what It is better to do it in the constructor.
When it is connected, the name of the function is used, that is, you should not invoke it with the ().
Considering the above, the solution is:
class Pagetwo(QtWidgets.QMainWindow):
trigger = pyqtSignal()
def __init__(self, parent=None):
super(Pagetwo, self).__init__(parent) # <---
self.setupUi(self)
self.dialogs = []
self.saveButton.clicked.connect(self.save)
self.closeButton.clicked.connect(self.close)
self.trigger.connect(self.parent().read_Config) # <---
def save(self):
string = self.lineEdit.text()
config = configparser.ConfigParser()
config.read('config.ini')
config.set('default', 'labelone', string)
with open('config.ini', 'w') as configfile:
config.write(configfile)
self.trigger.emit()
# ...
I followed this example (PyQT4) to create a "custom widget" in PyQT5 and ended up with following code:
progress.py
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import (QApplication,QMainWindow)
class Ui_Form(QMainWindow):
def __init__(self, name, parent=None):
super(Ui_Form,self).__init__(parent)
#Form.setObjectName("Form")
self.resize(619, 202)
self.formLayoutWidget = QtWidgets.QWidget()
self.formLayoutWidget.setGeometry(QtCore.QRect(0, 0, 621, 201))
self.formLayoutWidget.setObjectName("formLayoutWidget")
self.formLayout = QtWidgets.QFormLayout(self.formLayoutWidget)
self.formLayout.setContentsMargins(0, 0, 0, 0)
self.formLayout.setObjectName("formLayout")
self.progressBar = QtWidgets.QProgressBar(self.formLayoutWidget)
self.progressBar.setProperty("value", 24)
self.progressBar.setObjectName("progressBar")
self.formLayout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.progressBar)
self.graphicsView = QtWidgets.QGraphicsView(self.formLayoutWidget)
self.graphicsView.setObjectName("graphicsView")
self.formLayout.setWidget(1, QtWidgets.QFormLayout.LabelRole, self.graphicsView)
self.label_Title = QtWidgets.QLabel(self.formLayoutWidget)
font = QtGui.QFont()
font.setFamily("Verdana")
font.setPointSize(22)
font.setBold(True)
font.setWeight(75)
self.label_Title.setFont(font)
self.label_Title.setObjectName("label_Title")
self.formLayout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.label_Title)
self.timer = QtCore.QBasicTimer()
self.step = 0
self.timer.start(100,self)
self.retranslateUi()
QtCore.QMetaObject.connectSlotsByName(self)
def timerEvent(self, e):
if self.step>= 100:
self.timer.stop()
return
self.step = self.step + 1
self.progressBar.setValue(self.step)
def retranslateUi(self):
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Form", "Form"))
self.label_Title.setText(_translate("Form", "TextLabel"))
new_ui.py
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(742, 538)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.pushButton_Start = QtWidgets.QPushButton(self.centralwidget)
self.pushButton_Start.setGeometry(QtCore.QRect(630, 20, 91, 31))
self.pushButton_Start.setObjectName("pushButton_Start")
self.lineEdit_URL = QtWidgets.QLineEdit(self.centralwidget)
self.lineEdit_URL.setGeometry(QtCore.QRect(20, 20, 601, 31))
self.lineEdit_URL.setObjectName("lineEdit_URL")
self.label = QtWidgets.QLabel(self.centralwidget)
self.label.setGeometry(QtCore.QRect(20, 460, 46, 13))
self.label.setObjectName("label")
self.label_status = QtWidgets.QLabel(self.centralwidget)
self.label_status.setGeometry(QtCore.QRect(73, 460, 651, 16))
self.label_status.setObjectName("label_status")
self.listWidget = QtWidgets.QListWidget(self.centralwidget)
self.listWidget.setGeometry(QtCore.QRect(20, 60, 701, 391))
self.listWidget.setObjectName("listWidget")
#MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 742, 21))
self.menubar.setObjectName("menubar")
#MainWindow.setMenuBar(self.menubar)
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", "My Downloader"))
self.pushButton_Start.setText(_translate("MainWindow", "Start"))
self.label.setText(_translate("MainWindow", "Status :"))
self.label_status.setText(_translate("MainWindow", "Ideal..."))
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_())
main.py
import sys
import threading
import logging
from PyQt5 import QtCore, QtGui, QtWidgets
import json
import urllib.request
from new_ui import Ui_MainWindow
import progress
import random
class MyForm(QtWidgets.QDialog):
def __init__(self, parent=None):
super(MyForm, self).__init__(parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton_Start.clicked.connect(self.thread_start)
def thread_start(self):
p = threading.Thread(name='worker', target=self.start_download)
#Do UI updates
self.ui.label_status.setText('Fetching information...')
#self.listWidget.addItem(prgstr)
p.start()
def start_download(self):
............
code to fetch information from web
............
#itm = self.ui.listWidget.addItem(json_data['entries'][0]['title'])
self.ui.label_status.setText(json_data['entries'][0]['title'])
item = QtWidgets.QListWidgetItem(self.ui.listWidget)
item_widget = progress.Ui_Form("It works")
item.setSizeHint(item_widget.sizeHint())
self.ui.listWidget.addItem(item)
self.ui.listWidget.setItemWidget(item,item_widget)
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
myapp = MyForm()
myapp.show()
sys.exit(app.exec_())
The above runs without any errors, but the following code fails to add any list item (custom widget), the label_status gets correctly populated with retrieved data - what am I missing here?
self.ui.label_status.setText(json_data['entries'][0]['title'])
item = QtWidgets.QListWidgetItem(self.ui.listWidget)
item_widget = progress.Ui_Form("It works")
item.setSizeHint(item_widget.sizeHint())
self.ui.listWidget.addItem(item)
self.ui.listWidget.setItemWidget(item,item_widget)
You should only ever update GUI elements from within the main GUI thread.
The worker thread should emit a signal when it's done, or perhaps do so periodically so that pending GUI events can be processed (i.e. by calling qApp.processEvents()).
If the worker thread generates data, use a Queue so that it can be accessed safely from the main thread.
UPDATE:
Here is a basic example (python3 only) of how to use QThread with a worker object. All communication between the GUI thread and the worker thread is done using signals and slots (see "Signals and Slots Across Threads" in the Qt docs for more on this).
import urllib.request
from PyQt4 import QtCore, QtGui
class Worker(QtCore.QObject):
finished = QtCore.pyqtSignal()
error = QtCore.pyqtSignal(object)
dataReady = QtCore.pyqtSignal(object)
#QtCore.pyqtSlot(str)
def process(self, url):
try:
response = urllib.request.urlopen(url, timeout=20)
try:
self.dataReady.emit(response.read())
finally:
response.close()
except urllib.error.URLError as exception:
self.error.emit(exception.reason)
except BaseException as exception:
self.error.emit(str(exception))
finally:
self.finished.emit()
class Window(QtGui.QWidget):
downloadRequest = QtCore.pyqtSignal(str)
def __init__(self):
QtGui.QWidget.__init__(self)
self.viewer = QtGui.QPlainTextEdit(self)
self.edit = QtGui.QLineEdit(self)
self.edit.setFocus()
self.button = QtGui.QPushButton('Download', self)
self.button.clicked.connect(self.handleButton)
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.viewer)
layout.addWidget(self.edit)
layout.addWidget(self.button)
# worker must not have a parent
self._worker = Worker()
self._thread = QtCore.QThread(self)
self._worker.moveToThread(self._thread)
self._worker.finished.connect(self._thread.quit)
self._worker.error.connect(self.handleError)
self._worker.dataReady.connect(self.handleDataReady)
self.downloadRequest.connect(self._worker.process)
def handleButton(self):
url = self.edit.text().strip()
if url and not self._thread.isRunning():
self.viewer.clear()
self._thread.start()
# safely communicate with worker via signal
self.downloadRequest.emit(url)
def handleError(self, reason):
self.viewer.clear()
print('ERROR:', reason)
def handleDataReady(self, data):
self.viewer.setPlainText(data.decode('utf-8'))
def closeEvent(self, event):
if self._thread.isRunning():
# Qt will complain if thread is still
# running, so quit it before closing
self._thread.quit()
self._thread.wait()
QtGui.QWidget.closeEvent(self, event)
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.edit.setText('http://stackoverflow.com')
window.setGeometry(500, 300, 300, 300)
window.show()
sys.exit(app.exec_())