I am trying to display a loading graphic(for now just a label) at the beginning of my pyside program while another function is running. After it's done it should continue and load the main GUI. I have this so far
from PySide import QtCore
from PySide import QtGui
class DoStuff:
def __init__(self):
pass
def ReturnInformation(self):
time.sleep(20) #Sleep to simulate processing time
return "information"
class Main(QtGui.QWidget):
def __init__(self):
super(Main, self).__init__()
self.initQ = queue.Queue()
self.initthread = threading.Thread(target=self.InitThread)
self.initthread.daemon = True
self.initthread.start()
self.setStyleSheet("background-color: black;")
self.setCursor(QtCore.Qt.BlankCursor)
self.loaddisplay = QtGui.QLabel(self)
self.loaddisplay.move(20, 20)
self.loaddisplay.setText("Loading...")
self.show()
self.initthread.join()
self.MainDisplay()
self.show()
def InitThread(self):
self.dostuff = DoStuff()
def MainDisplay(self):
self.display = QtGui.QLabel(self)
self.display.setStyleSheet("font: 70pt Helvetica; color: white;")
self.display.move(20, 20)
self.display.setText(self.dostuff.ReturnInformation())
self.manager = QtCore.QTimer(self)
self.manager.timeout.connect(self.Update)
self.manager.start(100000)
def Update(self): #Update the information once in a while
self.timedisplay.setText(self.dostuff.ReturnInformation())
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
GUI = Main()
sys.exit(app.exec_())
The problem is that only the load graphic is displayed and the GUI from MainDisplay() is never displayed. I'm pretty sure this has something to do with how I'm calling the show()function. Is that the problem, and how do I fix it?
Also, how would I delete the loading label once it has finished loading?
P.S. (I asked this question before, but it got no answers or comments and low views so I deleted it and am asking the question again)
Although python provides several ways to execute task through threads these do not necessarily conform to the rules of Qt, it is appropriate to use the tools of the framework such as QThread:
class DoStuffThread(QtCore.QThread):
displaySignal = QtCore.Signal(str)
timeSignal = QtCore.Signal(str)
def __init__(self, *args, **kwargs):
QtCore.QThread.__init__(self, *args, **kwargs)
self.timer = QtCore.QTimer()
self.timer.moveToThread(self)
self.timer.timeout.connect(self.onTimeout)
self.stuff = DoStuff()
def onTimeout(self):
data = self.stuff.ReturnInformation()
self.timeSignal.emit(data)
def run(self):
data = self.stuff.ReturnInformation()
self.displaySignal.emit(data)
self.timer.start(20000)
loop = QtCore.QEventLoop()
loop.exec_()
class DoStuff:
def ReturnInformation(self):
time.sleep(2) # Sleep to simulate processing time
return "information-{}".format(QtCore.QTime.currentTime().toString("hh:mm:ss"))
class Main(QtGui.QWidget):
def __init__(self):
super(Main, self).__init__()
self.setStyleSheet("background-color: black;")
self.setCursor(QtCore.Qt.BlankCursor)
self.setLayout(QtGui.QVBoxLayout())
self.loaddisplay = QtGui.QLabel(self)
self.display = QtGui.QLabel(self)
self.timedisplay = QtGui.QLabel(self)
self.layout().addWidget(self.loaddisplay)
self.layout().addWidget(self.display)
self.layout().addWidget(self.timedisplay)
self.thread = DoStuffThread(self)
self.thread.displaySignal.connect(self.display.setText, QtCore.Qt.QueuedConnection)
self.thread.timeSignal.connect(self.timedisplay.setText, QtCore.Qt.QueuedConnection)
self.thread.start()
self.loaddisplay.move(20, 20)
self.loaddisplay.setText("Loading...")
self.display.setStyleSheet("font: 70pt Helvetica; color: white;")
self.display.move(20, 20)
def closeEvent(self, event):
self.thread.quit()
QtGui.QWidget.closeEvent(self, event)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
GUI = Main()
GUI.show()
sys.exit(app.exec_())
Related
Every time the user clicks on an item in QTreeWidget, the computer starts to perform some heavy duty tasks. My only objective is to use QThread to prevent freeze.
Consider the following code:
from PyQt5.QtWidgets import*
from PyQt5.QtCore import*
import time
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QVBoxLayout()
self.treewidget = MyTreeWidget()
self.treewidget.itemClicked.connect(self.tree_click)
self.layout.addWidget(self.treewidget)
self.label = QLabel("Label", self)
self.layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
self.show()
#pyqtSlot(QTreeWidgetItem)
def tree_click(self, item):
time.sleep(2)
self.label.setText(item.text(0))
class MyTreeWidget(QTreeWidget):
def __init__(self):
super().__init__()
item_1 = QTreeWidgetItem()
item_1.setText(0, 'one item')
item_2 = QTreeWidgetItem()
item_2.setText(0,'another item')
self.addTopLevelItem(item_1)
item_1.addChild(item_2)
self.setHeaderHidden(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
main = MainWindow()
app.exec()
How to modify the above code to prevent freeze and what is the most appropriate way of doing it?
I tried by following the protocol given in Background thread with QThread in PyQt, the second answer:
from PyQt5.QtWidgets import*
from PyQt5.QtCore import*
import time
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QVBoxLayout()
self.treewidget = MyTreeWidget()
self.treewidget.itemClicked.connect(self.tree_click)
self.layout.addWidget(self.treewidget)
self.label = QLabel("Label", self)
self.layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
self.show()
#pyqtSlot(QTreeWidgetItem)
def tree_click(self, item):
self.obj = Worker()
self.thread = QThread()
self.obj.finished.connect(self.change_text)
self.obj.moveToThread(self.thread)
self.obj.finished.connect(self.thread.quit)
self.thread.started.connect(self.obj.work)
self.thread.start()
def change_text(self):
self.label.setText(item.text(0))
class Worker(QObject):
finished = pyqtSignal()
#pyqtSlot()
def work(self):
time.sleep(2)
finished = pyqtSignal()
class MyTreeWidget(QTreeWidget):
def __init__(self):
super().__init__()
item_1 = QTreeWidgetItem()
item_1.setText(0, 'one item')
item_2 = QTreeWidgetItem()
item_2.setText(0,'another item')
self.addTopLevelItem(item_1)
item_1.addChild(item_2)
self.setHeaderHidden(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
main = MainWindow()
app.exec()
It does not look like I am doing the right thing.
I made a couple of minor adjustments to make your worker class run properly. This will prevent the rest of the gui from freezing but the label text that depends on the long running process will continue to take that long to update.
Also if the task implemented in the separate thread is repeated frequently you may want to consider using a QThreadPool.
I also included some inline comments to explain some of the changes.
from PyQt5.QtWidgets import*
from PyQt5.QtCore import*
import time
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle('MainWindow')
self.layout = QVBoxLayout()
self.treewidget = MyTreeWidget()
self.treewidget.itemClicked.connect(self.tree_click)
self.layout.addWidget(self.treewidget)
self.label = QLabel("Label", self)
self.layout.addWidget(self.label)
widget = QWidget()
widget.setLayout(self.layout)
self.setCentralWidget(widget)
self.show()
self.threads = [] # make shift threads container
#pyqtSlot(QTreeWidgetItem)
def tree_click(self, item):
self.obj = Worker()
thread = QThread()
self.obj.text = item.text(0) # the obj needs a copy of the text to emit
self.obj.textReady.connect(self.change_text)
self.obj.moveToThread(thread)
self.obj.finished.connect(thread.quit)
thread.finished.connect(thread.deleteLater) # delete the threads once finished
thread.started.connect(self.obj.work)
thread.start()
self.threads.append(thread)
def change_text(self, text):
self.label.setText(text)
class Worker(QObject):
finished = pyqtSignal()
textReady = pyqtSignal([str])
#pyqtSlot()
def work(self):
time.sleep(2)
self.textReady.emit(self.text) # emit the text that needs to be changed
self.finished.emit()
class MyTreeWidget(QTreeWidget):
def __init__(self):
super().__init__()
item_1 = QTreeWidgetItem()
item_1.setText(0, 'one item')
item_2 = QTreeWidgetItem()
item_2.setText(0,'another item')
self.addTopLevelItem(item_1)
item_1.addChild(item_2)
self.setHeaderHidden(True)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
main = MainWindow()
app.exec()
I have the following code but it's complaining that I cannot access the UI data from my thread. In my example code below, What is the best way I can access the userInputString value so my threading can run?
self.nameField is a PyQt QLineEdit.
QObject::setParent: Cannot set parent, new parent is in a different thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QWidget::repaint: Recursive repaint detected
import myUI
class MainUIClass(QtGui.QMainWindow, myUI.Ui_MainWindow):
def __init__(self, parent=None):
super(MainUIClass, self).__init__(parent)
self.setupUi(self)
self.startbutton.clicked.connect(self.do_work)
self.workerThread = WorkerThread()
self.connect(self.workerThread, SIGNAL("myThreading()"), self.myThreading, Qt.DirectConnection)
def do_work(self):
self.userInputString = self.nameField.Text()
self.workerThread.start()
def myThreading(self):
if userInputString is not None:
#Do something
class WorkerThread(QThread):
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
def run(self):
self.emit(SIGNAL("myThreading()"))
if __name__ == '__main__':
a = QtGui.QApplication(sys.argv)
app = MainUIClass()
app.show()
a.exec_()
Not sure if it's what you need but here is a working QThread exemple using Qt5
import time
import sys
from PyQt5 import QtWidgets, QtGui, QtCore
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.worker_thread = WorkerThread()
self.worker_thread.job_done.connect(self.on_job_done)
self.create_ui()
def create_ui(self):
self.button = QtWidgets.QPushButton('Test', self)
self.button.clicked.connect(self.start_thread)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
def start_thread(self):
self.worker_thread.gui_text = self.button.text()
self.worker_thread.start()
def on_job_done(self, generated_str):
print("Generated string : ", generated_str)
self.button.setText(generated_str)
class WorkerThread(QtCore.QThread):
job_done = QtCore.pyqtSignal('QString')
def __init__(self, parent=None):
super(WorkerThread, self).__init__(parent)
self.gui_text = None
def do_work(self):
for i in range(0, 1000):
print(self.gui_text)
self.job_done.emit(self.gui_text + str(i))
time.sleep(0.5)
def run(self):
self.do_work()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
test = MainWindow()
test.show()
app.exec_()
I'm just started with pyqt and i want to change the icon from a button.
But i'm dowing it from another class and pyqt don't like that.
The error is: QPixmap: It is not safe to use pixmaps outside the GUI thread
I know i must using singal en emits.
But i don't know how i must use it for change the icon from a button.
This my code now:
import sys
import time
from PyQt4 import QtGui, QtCore
from pymodbus.exceptions import ConnectionException
from sqlalchemy.orm import sessionmaker
from sqlalchemy import create_engine
from data.database import Tags, Query_Tags
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
self.status = 0
self.initUI()
def initUI(self):
self.button = QtGui.QPushButton('Aan', self)
self.button.clicked.connect(self.run)
self.button.move(50,50)
Uit = QtGui.QPushButton('Uit', self)
Uit.clicked.connect(self.off)
Uit.move(150,50)
self.setGeometry(300, 300, 500, 150)
self.setWindowTitle('Quit button')
self.show()
self.worker = WorkThread(self)
self.worker.start()
def run(self):
add_tag = Query_Tags('18', '1')
self.db_session.add(add_tag)
self.db_session.commit()
def off(self):
add_tag = Query_Tags('18', '0')
self.db_session.add(add_tag)
self.db_session.commit()
self.status = 0
print self.store
def change(self):
print "test"
#self.button.setIcon(QtGui.QIcon("/home/stijnb/test/icon.png"))
#self.button.setIconSize(QtCore.QSize(16,16))
def database(self, store):
self.store = store
"""
Thread
"""
class WorkThread(QtCore.QThread):
def __init__(self, layout):
QtCore.QThread.__init__(self)
self.layout = layout
def __del__(self):
self.wait()
def run(self):
self.database = {}
while True:
self.db_engine = create_engine('mysql://modbususer:modbususer#localhost/modbus')
self.db_session = sessionmaker(bind=self.db_engine)
self.db_session = self.db_session()
for result in self.db_session.query(Tags):
self.database[int(result.id)] = {'naam': result.name, 'value': result.value}
self.change_icons()
time.sleep(1)
return
self.terminate()
def change_icons(self):
print self.database[21]['value']
if self.database[21]['value'] == '1':
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/aan.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
else:
self.layout.button.setIcon(QtGui.QIcon("/home/stijnb/test/uit.png"))
self.layout.button.setIconSize(QtCore.QSize(16,16))
def main():
app = QtGui.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You cannot touch GUI elements from any other thread other than the main GUI Thread. That's why Qt introduced messaging (Signals and Slots). You can connect to a signal from your worker thread that will be caught in the main thread, and then in the main thread you can change any element you want.
Here is a very simple example where I demonstrate the concept.
import sys
from PyQt4 import QtGui, QtCore
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
# Create a button and set it as a central widget
self.button = QtGui.QPushButton('My Button', self)
self.setCentralWidget(self.button)
# Start the thread
self.worker = WorkThread(self)
self.worker.start()
# Connect the thread's signal "change_text" to
# "self.change_thread" function
self.worker.change_text.connect(self.change_text)
def change_text(self):
# Slots are always executed in the GUI thread so it's safe to change
# anything you want in a slot function. Here we just flip the button
# text from foo to bar and back. But you can change the image or do
# whatever you want.
# Slots can also receive parameters so you can emit from your thread
# which text should be set
self.button.setText('bar' if self.button.text() == 'foo' else 'foo')
class WorkThread(QtCore.QThread):
# Define the signal
change_text = QtCore.pyqtSignal()
def __init__(self, layout):
QtCore.QThread.__init__(self)
def run(self):
while True:
# emit the signal every 3 seconds
self.sleep(3)
self.change_text.emit()
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
I want to ask how is it possible to refresh QLCDNumbers after I have started some measures.
I created an GUI thread to connect the signals to QLCDNumbers like that:
class BtDialog(QtGui.QDialog, Dlg):
def __init__(self):
QtGui.QDialog.__init__(self)
self.setupUi(self)
self.thread = WorkerThread()
#Configure slots
self.connect(self.startButton, QtCore.SIGNAL("clicked()"), self.onStart)
self.connect(self.stopButton, QtCore.SIGNAL("clicked()"), self.onStop)
#QLCDNumber Slot
self.connect(self.thread, self.thread.voltage, self.lcdVoltage.display)
def onStart(self):
self.thread.start()
def onStop(self):
self.emit(self.thread.voltage, 0) #Trying to refresh
abort()
Here I connect two buttons, one for starting the worker thread and the other to stop the process. When I stop the process I want to refresh the QLCDNumber by displaying '0' but it doesn't work.
In worker thread i initialize the signal like that:
def __init__(self, parent = None):
QtCore.QThread.__init__(self, parent)
self.voltage = QtCore.SIGNAL("voltage")
And when the process runs I emit the signal with
self.emit(self.voltage, volt_act)
after measuring. That works so far. But after stopping when I want to start the worker process again the signal doesn't emit to QLCDNumber again. For that I have to restart the GUI. How can I fix the two problems of mine for that I want to refresh QLCDNumber and over that after stopping and refreshing emitting the signal again?
Can't tell where the issue is from the code you posted, but this should help you modify it, also checkout the docs for new-style signal/slot connections and further reference (modal dialogs, timers, etc):
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import time
from PyQt4 import QtGui, QtCore
class MyThread(QtCore.QThread):
countChange = QtCore.pyqtSignal(int)
countReset = QtCore.pyqtSignal(int)
def __init__(self, parent=None):
super(MyThread, self).__init__(parent)
self.stopped = QtCore.QEvent(QtCore.QEvent.User)
def start(self):
self.stopped.setAccepted(False)
self.count = 0
super(MyThread, self).start()
def run(self):
while not self.stopped.isAccepted():
self.count += 1
self.countChange.emit(self.count)
time.sleep(1)
self.countReset.emit(0)
def stop(self):
self.stopped.setAccepted(True)
class MyWindow(QtGui.QDialog):
def __init__(self, parent=None):
super(MyWindow, self).__init__(parent)
self.lcdNumber = QtGui.QLCDNumber(self)
self.pushButtonStart = QtGui.QPushButton(self)
self.pushButtonStart.setText("Start")
self.pushButtonStart.clicked.connect(self.on_pushButtonStart_clicked)
self.pushButtonStop = QtGui.QPushButton(self)
self.pushButtonStop.setText("Stop")
self.pushButtonStop.clicked.connect(self.on_pushButtonStop_clicked)
self.pushButtonDone = QtGui.QPushButton(self)
self.pushButtonDone.setText("Done")
self.pushButtonDone.clicked.connect(self.on_pushButtonDone_clicked)
self.layoutHorizontal = QtGui.QHBoxLayout(self)
self.layoutHorizontal.addWidget(self.lcdNumber)
self.layoutHorizontal.addWidget(self.pushButtonStart)
self.layoutHorizontal.addWidget(self.pushButtonStop)
self.layoutHorizontal.addWidget(self.pushButtonDone)
self.thread = MyThread(self)
self.thread.countChange.connect(self.lcdNumber.display)
self.thread.countReset.connect(self.lcdNumber.display)
#QtCore.pyqtSlot()
def on_pushButtonStart_clicked(self):
self.thread.start()
#QtCore.pyqtSlot()
def on_pushButtonStop_clicked(self):
self.thread.stop()
#QtCore.pyqtSlot()
def on_pushButtonDone_clicked(self):
sys.exit()
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
app.setApplicationName('MyWindow')
main = MyWindow()
main.exec_()
sys.exit(app.exec_())
I have some trouble with creating window application using PyQt. For example: we have the following class for GUI:
class SampleMainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
...
def hideEvent(self, event):
...
def showEvent(self, event):
...
def activate(self):
...
def closeEvent(self, event):
...
What if we want to use it just for showing results and acept user inputs, and for all control logic we have another class, which use the instance of SampleMainWindow as a field:
class Programm:
def __init__(self):
self.window = SampleMainWindow()
....
app = QtGui.QApplication(sys.argv)
prog = Programm()
prog.window.show()
app.exec_()
So, if we create the instance of Programm, the application will run well, but on close we'll get an error that python.exe process could not be completed normally. But if the instance of SampleMainWindow is not a field of class:
class Programm:
def __init__(self):
win = SampleMainWindow()
....
application close well. What's the reason?
Here are the full version of Gui class:
class SampleMainWindow(QtGui.QMainWindow):
def initTray(self):
self.icon = QtGui.QSystemTrayIcon()
self.icon.setIcon(QtGui.QIcon('Tray.png'))
self.icon.show()
self.icon.activated.connect(self.activate)
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
moduleglobalconstants.APP_RUNNING = True
self.initTray()
self.newSession = True
self.setGeometry(300, 300, 600, 400)
self.setWindowTitle('Eye: Recently Added Lines')
self.statusBar().showMessage('Ready')
exitAction = QtGui.QAction(QtGui.QIcon('Exit.png')
,'Exit'
,self)
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')
self.connect(exitAction
,QtCore.SIGNAL('triggered()')
,QtCore.SLOT('close()'))
menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)
self.textEdit = QtGui.QTextEdit()
self.scrollPosition = self.textEdit.textCursor()
self.textEdit.setReadOnly(True)
self.setCentralWidget(self.textEdit)
def hideEvent(self, event):
self.hidden = True
self.setWindowFlags(QtCore.Qt.ToolTip)
self.setVisible(False)
def showEvent(self, event):
self.hidden = False
self.setVisible(True)
def activate(self):
self.setWindowFlags(QtCore.Qt.Window)
self.show()
self.setWindowState(self.windowState() & ~QtCore.Qt.WindowMinimized | QtCore.Qt.WindowActive)
self.activateWindow()
def questionDialog(self, caption = 'Message',text = "Are you sure?"):
self.activate()
msgBox = QtGui.QMessageBox(self)
msgBox.setWindowTitle("Eye")
msgBox.setText(caption);
msgBox.setInformativeText(text);
msgBox.setStandardButtons(QtGui.QMessageBox.Yes| QtGui.QMessageBox.No);
msgBox.setDefaultButton(QtGui.QMessageBox.Yes);
return msgBox.exec_()
def criticalDialog(self,app, caption = 'Eye: Critical Error',text = "Impossible to continue working!"):
self.activate()
msgBox = QtGui.QMessageBox.critical(self,caption,text)
moduleglobalconstants.APP_RUNNING = False
def closeEvent(self, event):
reply = self.questionDialog()
if reply == QtGui.QMessageBox.Yes:
moduleglobalconstants.APP_RUNNING = False
event.accept()
else:
event.ignore()
def showNewLines(self, singleLine = None, lines = None, mode='insert'):
if (mode == 'rewrite'):
self.textEdit.clear()
if lines:
for line in lines:
self.textEdit.insertPlainText(line.strip() + "\n")
elif singleLine:
self.textEdit.insertPlainText(singleLine.strip()+"\n")
This has to do with python's garbage collecting - as (almost) soon an object isn't referenced anymore it destroys it, so I guess the process doesn't terminate as long as the window wasn't garbage-collected.
You can try to add at the end del Programm.window or something like that (there's probably a better way of doing this, but I never programmed QT myself so I can't help you here)
see this:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import sys
class MyWindow(QWidget):
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.label = QLabel("Hi, I'm a window", self)
self.button = QPushButton("&Quit", self)
self.connect(self.button, SIGNAL("clicked()"), QCoreApplication.instance(), SLOT("quit()"))
lay = QVBoxLayout()
lay.addWidget(self.label)
lay.addWidget(self.button)
self.setLayout(lay)
def closeEvent(self, event):
print "Closing the app"
self.deleteLater()
if __name__ == '__main__':
app = QApplication(sys.argv)
mw = MyWindow()
mw.show()
sys.exit(app.exec_())
the solution is to implement closeEvent method