I need to change the color of QPushButton, but an error occurred: "AttributeError: type object 'ProyectoTFM' has no attribute 'ui'".
I don't know hoy to acced to a ui variable from my thread.
This is my code:
import sys
import OpenOPC
import time
import threading
from proyectoQt import *
def actualizarDatosOPC():
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
ProyectoTFM.ui.AP08Button.setStyleSheet("background-color: red")
return
class ProyectoTFM(QtGui.QMainWindow):
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC)
threadQt.start()
def clienteOPC():
opc=OpenOPC.client()
opc.connect('Kepware.KEPServerEX.V6')
global itemsOPC
while 1:
itemsOPC = opc.read(opc.list('PLC.PLC.TAGS'))
time.sleep(5)
return
threads = list()
threadOPC = threading.Thread(target=clienteOPC)
threads.append(threadOPC)
threadOPC.start()
time.sleep(5)
if __name__== "__main__":
app=QtGui.QApplication(sys.argv)
myapp = ProyectoTFM()
myapp.show()
sys.exit(app.exec_())
threadOPC.__delete()
Sorry for my English and thanks.
It is not correct to modify the view from a different thread to the main one, a way to solve the problem without using QThread is to create a signal that connects to some slot that changes the color of the button. To be able to emit the signal from the new thread we must pass the object to him through the parameter args.
def actualizarDatosOPC(obj):
while 1:
time.sleep(5)
if(itemsOPC[15])[1]!=0:
#Error on next line
obj.sendChangeColor.emit()
return
class ProyectoTFM(QtGui.QMainWindow):
sendChangeColor = QtCore.pyqtSignal()
def __init__(self,parent=None):
QtGui.QMainWindow.__init__(self,parent)
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.startTheThread()
print('Init')
self.sendChangeColor.connect(lambda: self.ui.AP08Button.setStyleSheet("background-color: red"))
def startTheThread(self):
threadQt = threading.Thread(target = actualizarDatosOPC, args=(self,))
threadQt.start()
Even if you got this to work, you can't modify the UI from a thread directly.
A few things:
You never actually pass the UI to the function actualizarDatosOPC
so it doesn't know it exists.
Is there any reason you can't use PyQt's built in threading tools? If you are going to use PyQt it might make sense to buy into the whole framework.
def startTheThread(self):
self.threadQt = QThread()
d = actualizarDatosOPC(self)
d.moveToThread(self.threadQt)
self.threadQt.start()
def actualizarDatosOPC(widget):
.... widget.AP08Button.setStyleSheet("background-color: red")
If you do choose to go this route, I'd take a look at this thread which has a good example:
How to use QThread correctly in pyqt with moveToThread()?
Additionally, while the way you initialize your Window works, this is the more standard way to do it:
class ProyectoTFM(QMainWindow, Ui_MainWindow):
def __init__(self, parent):
# General Init Stuff
super(Login, self).__init__(parent)
self.setupUi(self)
After that, whenever you want to refer to something in the UI all you need to do is refer to self._____. For example, if you have a button named buttonA, self.buttonA would be the appropriate reference.
Edit:
As mentioned in another answer, the proper way to actually change the button color would be to emit a trigger that to the main thread which could then respond by changing the button color.
Related
First of all, I want to figure out how to check database status every second. so that the user will able to tell if the database is up or not without even clicking or triggering anything. I've read that this will create a problem as mentioned in the comments here
so here's my minimal reproducible example:
import sys
import os
import shiboken2
from PySide2 import QtCore, QtGui, QtWidgets
from PySide2.QtWidgets import QMainWindow, QFileDialog, QMessageBox, QWidget, QDialog, QProxyStyle
from sqlalchemy import create_engine, inspect
class MyWidget(QtWidgets.QWidget):
def __init__(self):
QtWidgets.QWidget.__init__(self)
self.resize(200, 200)
self.path = os.path.abspath(os.path.dirname(sys.argv[0]))
self.button = QtWidgets.QPushButton("Open File")
self.labelFile = QtWidgets.QLabel("empty")
self.labelData = QtWidgets.QLabel("None")
self.layout = QtWidgets.QVBoxLayout()
self.layout.addWidget(self.button)
self.layout.addWidget(self.labelFile)
self.layout.addWidget(self.labelData)
self.setLayout(self.layout)
self.button.clicked.connect(self.open_file)
self.process = None
self.CreateEngine = CreateEngine(self)
self.CreateEngine.result.connect(self.start_timer)
self.CreateEngine.start()
def open_file(self):
x = QFileDialog.getOpenFileName(self,"Just To Spice This Code",self.path,"CSV Files (*.csv)")
self.labelFile.setText(x[0]) #just to check that GUI doesn't freeze
def start_timer(self,engine): #callback from CreateEngine
self.timer = QtCore.QTimer(self)
self.timer.timeout.connect(lambda: self.continuously_check(engine))
self.timer.start(1000) #check connetion every second, as real-time as possible
def continuously_check(self,engine): #this gonna get called every second, yes it isn't effective i know
self.process = CheckConnection(self,engine)
self.process.result.connect(self.update_connection_label)
self.process.start()
def update_connection_label(self,x): #update connection status on GUI
self.labelData.setText("DB Status: "+str(x))
def closeEvent(self,event): #to handle QThread: Destroyed while thread is still running
print("begin close event")
if(self.process is not None):
if(shiboken2.isValid(self.process)): #to check whether the object is deleted. ->
self.process.wait() #-> this will get messy when the DB connection is down
self.process.quit() #-> (IMO):since i stack so many CheckConnection objects maybe?
print("end close event")
class CreateEngine(QtCore.QThread): #creating engine on seperate thread so that it wont block GUI
result = QtCore.Signal(object)
def __init__(self, parent):
QtCore.QThread.__init__(self, parent)
self.engine = None
def run(self):
self.engine = create_engine('mysql+pymysql://{}:{}#{}:{}/{}'.format("root","","localhost","3306","adex_admin"))
self.result.emit(self.engine)
class CheckConnection(QtCore.QThread): #constantly called every second, yes its not a good approach ->
result = QtCore.Signal(str) #-> i wonder how to replace all this with something appropriate
def __init__(self, parent,engine):
QtCore.QThread.__init__(self, parent)
self.engine = engine
def run(self):
try:
self.engine.execute('SELECT 1').fetchall()
self.result.emit("Connected")
except:
self.result.emit("Not Connected")
self.deleteLater() #somehow this doesn't do it job very well. maybe blocked?
#-> especially when the connection is busted. this thread gets stuck quite long to finish
if __name__ == "__main__":
#idk why when you start this without connection it's running really slow on showing the status of DB
#you must wait like 4 seconds until the connection status is showed up, which is really bad
#but once it's live. it could read database status really fast
app = QtWidgets.QApplication(sys.argv)
widget = MyWidget()
widget.show()
sys.exit(app.exec_())
I've created this example just to reproduce the same problem I'm facing in my real app. so the problem is that closeEvent takes too long to terminate the checking process and also blocking the GUI. The reason why I create 'closeEvent' is that I had this problem which produce [QThread: Destroyed while thread is still running] when the app is closed.
also, whenever the database isn't reachable it makes the QThread finishes way longer than it should unlike when the database is reachable. but we can retrieve the status pretty much like we want (every second of live DB Status). I also tried a silly approach like this
...
def continuously_check(self,engine):
self.process = CheckConnection(self,engine)
self.process.result.connect(self.update_connection_label)
self.process.finished.connect(lambda: QtCore.QTimer.singleShot(1000,self.continuously_check))
self.process.start()
...
hoping that it won't keep creating objects before the thread even finished (ps: obviously this won't work). so what's the best approach when it comes to this? sorry for multiple problems at a time.
I have a simple PySide Gui which I want to show, however there currently is a blocking task which is a long process, populating the dropdown list with items. Which means the UI does not show until the dropdown list is finished being populated. I want to know is there a way to force show the UI before it attempts to populate the list. I would prefer the dialog show so users know they opened the tool before assuming it's crashed or something.
I'm using Qt since my application needs to run in both PySide and PySide2. I was initially trying to use the qApp.processEvents() but it doesn't seem to be available in the Qt wrapper, or I may have been missing something. If you know what the equivalent would be, I'm fine with the process events being the solution. Optionally if there is an elegant way to populate the list from a background thread somehow...
from Qt import QtGui, QtCore, QtWidgets
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
self.resize(200,50)
self.items = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.items)
self.setLayout(layout)
# init
self.long_process()
def long_process(self):
for i in range(30000):
self.items.addItem('Item {}'.format(i))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
form = Form()
form.show()
sys.exit(app.exec_())
A good option for these cases is always to use QTimer:
class Form(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Form, self).__init__(parent)
[...]
# init
timer = QtCore.QTimer(self)
timer.timeout.connect(self.on_timeout)
timer.start(0)
def on_timeout(self):
self.items.addItem('Item {}'.format(self.counter))
self.counter += 1
if self.counter == 30000:
self.sender().stop()
I have a GUI which needs to perform work that takes some time and I want to show the progress of this work, similar to the following:
import sys
import time
from PyQt4 import QtGui, QtCore
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# start loop with signal
self.button = QtGui.QPushButton('loop', self)
self.connect(self.button, QtCore.SIGNAL('clicked()'), self.loop)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def update(self):
self.pbar.setValue(self.pbar.value() + 1)
def loop(self):
for step in range(100):
self.update()
print step
time.sleep(1)
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
view.loop() # call loop directly to check whether view is displayed
sys.exit(app.exec_())
When I execute the code the loop method is called and it prints out the values as well as updates the progress bar. However the view widget will be blocked during the execution of loop and although this is fine for my application it doesn't look nice with Ubuntu. So I decided to move the work to a separate thread like this:
import sys
import time
from PyQt4 import QtGui, QtCore
class Worker(QtCore.QObject):
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
def loop(self):
for step in range(10):
print step
time.sleep(1)
class MyProgress(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
# test button
self.test_button = QtGui.QPushButton('test')
self.connect(self.test_button, QtCore.SIGNAL('clicked()'), self.test)
self.pbar = QtGui.QProgressBar(self)
self.pbar.setMinimum(0)
self.pbar.setMaximum(100)
# layout
vbox = QtGui.QVBoxLayout()
vbox.addWidget(self.test_button)
vbox.addWidget(self.pbar)
self.setLayout(vbox)
self.show()
def test(self):
if self.test_button.text() == 'test':
self.test_button.setText('ok')
else:
self.test_button.setText('test')
app = QtGui.QApplication(sys.argv)
view = MyProgress()
work = Worker()
thread = QtCore.QThread()
work.moveToThread(thread)
# app.connect(thread, QtCore.SIGNAL('started()'), work.loop) # alternative
thread.start()
work.loop() # not called if thread started() connected to loop
sys.exit(app.exec_())
When I run this version of the script the loop starts running (the steps are displayed in the terminal) but the view widget is not shown. This is the first thing I can't quite follow. Because the only difference from the previous version here is that the loop runs in a different object however the view widget is created before and therefore should be shown (as it was the case for the previous script).
However when I connected the signal started() from thread to the loop function of worker then loop is never executed although I start the thread (in this case I didn't call loop on worker). On the other hand view is shown which makes me think that it depends whether app.exec_() is called or not. However in the 1st version of the script where loop was called on view it showed the widget although it couldn't reach app.exec_().
Does anyone know what happens here and can explain how to execute loop (in a separate thread) without freezing view?
EDIT: If I add a thread.finished.connect(app.exit) the application exits immediately without executing loop. I checked out the 2nd version of this answer which is basically the same what I do. But in both cases it finishes the job immediately without executing the desired method and I can't really spot why.
The example doesn't work because communication between the worker thread and the GUI thread is all one way.
Cross-thread commnunication is usually done with signals, because it is an easy way to ensure that everything is done asynchronously and in a thread-safe manner. Qt does this by wrapping the signals as events, which means that an event-loop must be running for everything to work properly.
To fix your example, use the thread's started signal to tell the worker to start working, and then periodically emit a custom signal from the worker to tell the GUI to update the progress bar:
class Worker(QtCore.QObject):
valueChanged = QtCore.pyqtSignal(int)
def loop(self):
for step in range(0, 10):
print step
time.sleep(1)
self.valueChanged.emit((step + 1) * 10)
...
thread = QtCore.QThread()
work.moveToThread(thread)
thread.started.connect(work.loop)
work.valueChanged.connect(view.pbar.setValue)
thread.start()
sys.exit(app.exec_())
I am trying to offload a heavy background job to a multiprocessing process. I just want the separate process to be able to report it's progress to my GUI. Here's my last try, the GUI is simple, a couple of buttons and a progress bar:
from PySide.QtGui import *
from PySide.QtCore import *
import sys
from multiprocessing import Process, Pipe
import time
class WorkerClass:
#This class has the job to run
def worker(self, pipe):
for i in range(101):
pipe.send(i)
time.sleep(.02)
class WorkStarter(QThread):
#this thread takes a widget and updates it using progress sent from
#process via Pipe
def __init__(self, progressBar):
super().__init__()
self.progress_bar = progressBar
def run(self):
worker_obj = WorkerClass()
myend, worker_end = Pipe(False)
self.p = Process(target=worker_obj.worker, args=(worker_end,))
self.p.start()
while True:
val = myend.recv()
self.progress_bar.setValue(val)
if val == 100:
break
class WorkingWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Blue collar widget')
layout = QHBoxLayout()
start_btn = QPushButton('Start working')
start_btn.clicked.connect(self.startWorking)
end_btn = QPushButton('End working')
end_btn.clicked.connect(self.endWorking)
layout.addWidget(start_btn)
layout.addWidget(end_btn)
self.progress_bar = QProgressBar()
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def startWorking(self):
self.thread = WorkStarter(self.progress_bar)
self.thread.start()
def endWorking(self):
self.thread.terminate()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = WorkingWidget()
main.show()
sys.exit(app.exec_())
I cannot pass any QObject as an argument to the process, since that is not pickleable:
#cannot do the following
...
def startWorking(self):
self.worker_obj = WorkerClass()
#pass the progress bar to the process and the process updates the bar
self.p = Process(target=self.worker_obj.worker, args=(self.progress_bar,))
The problem is that this gui some times works, other times it freezes (So please press 'start' multiple times until it freezes :) ), and here on Windows it says : pythonw.exe has stopped working...
Any clue what's the reason for that?. I cannot figure it out by myself. Thanks
You are not supposed to create the object inside "run" method of QThread, emit signal from "run", implement a function say "callerFunction" create object in this function and finally call this function on signal which is emitted by the "run" function.
You can emit the signal in the while loop that you have already created.
Have a look at this solution
don't create a python process, QThread is sufficient for this job
I am pretty much a beginner when it comes to GUI programming.
I am using QT in combination with python bindings (PyQT4).
What I am trying to do:
Setting up a QThread to read from & write to a Serial Port with
pyserial.
The main application should be able to emit new serial data via a
signal to the running QThread. and receive serial data from the
QThread with a signal.
I started my own test implementation based on this code (Link).
Prior to this I read the basics about QThreads and tried to understand how they are intended to be used.
The following test code is what I have come up with. I m sorry, I tried to keep it minmal,
but it is still 75 lines of code:
from PyQt4 import QtCore, QtGui
import time
import sys
class SerialData(QtCore.QObject):
def __init__(self, message):
super(SerialData, self).__init__()
self.__m = message
def getMsg(self):
return self.__m
class SerialCon(QtCore.QObject):
finished = QtCore.pyqtSignal()
received = QtCore.pyqtSignal(SerialData)
def init(self):
super(SerialCon, self).__init__()
# TODO setup serial connection:
# setting up a timer to check periodically for new received serial data
self.timer = QtCore.QTimer()
self.timer.setInterval(400)
self.timer.timeout.connect(self.readData)
self.timer.start(200)
# self.finished.emit()
def readData(self):
self.received.emit(SerialData("New serial data!"))
print "-> serial.readLine() ..."
#QtCore.pyqtSlot(SerialData)
def writeData(self, data):
print "-> serial.write(), ", data.getMsg()
class MyGui(QtGui.QWidget):
serialWrite = QtCore.pyqtSignal(SerialData)
def __init__(self):
super(MyGui, self).__init__()
self.initUI()
def initUI(self):
bSend = QtGui.QPushButton("Send",self)
bSend.clicked.connect(self.sendData)
self.show()
#QtCore.pyqtSlot(SerialData)
def updateData(self, data):
print "Gui:", data.getMsg()
def sendData(self, pressed):
data = SerialData("Send me!")
self.serialWrite.emit(data)
def usingMoveToThread():
app = QtGui.QApplication(sys.argv)
guui = MyGui()
thread = QtCore.QThread()
serialc = SerialCon()
serialc.moveToThread(thread)
# connecting signals to slots
serialc.finished.connect(thread.quit)
guui.serialWrite.connect(serialc.writeData)
serialc.received.connect(guui.updateData)
thread.started.connect(serialc.init)
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
if __name__ == "__main__":
usingMoveToThread()
My Problems:
In test code the signal emitted from the SerialCon object (which has
been moved to the QThread) seems to be not received by the
corresponding slot (in MyGui, updateData)
Sooner or later the running test code always causes a Segmentation
fault (core dumped). Which makes me believe that I missed some
important bits.
What could cause this?
Maybe I m taking a completely wrong approach? -
so if you have a better idea how to achieve this, I d be very grateful to hear about it!
Thanks a lot!
At first I was only focussing on the new way, how QThreads should be used since QT4
(Link),
by creating a QObject, and then invoking moveToThread(), pretty much like in my first code sample (at least thats how I understood it).
However I just could not figure out, why I was not able to pass signals from the
QThread to the main application.
As I really needed a fast solution to my problem, I desperately tried varius things.
Here is some second code, that does seem to work the way I wanted:
from PyQt4 import QtCore, QtGui
import time
import sys
import math
class SerialCon(QtCore.QThread):
received = QtCore.pyqtSignal(object)
def __init__(self, parent=None):
QtCore.QThread.__init__(self)
# specify thread context for signals and slots:
# test: comment following line, and run again
self.moveToThread(self)
# timer:
self.timer = QtCore.QTimer()
self.timer.moveToThread(self)
self.timer.setInterval(800)
self.timer.timeout.connect(self.readData)
def run(self):
self.timer.start()
#start eventloop
self.exec_()
def readData(self):
# keeping the thread busy
# figure out if the GUI remains responsive (should be running on a different thread)
result = []
for i in range(1,1000000):
result.append(math.pow(i,0.2)*math.pow(i,0.1)*math.pow(i,0.3))
#
self.received.emit("New serial data!")
#QtCore.pyqtSlot(object)
def writeData(self, data):
#print(self.currentThreadId())
print(data)
class MyGui(QtGui.QWidget):
serialWrite = QtCore.pyqtSignal(object)
def __init__(self, app, parent=None):
self.app = app
super(MyGui, self).__init__(parent)
self.initUI()
def initUI(self):
self.bSend = QtGui.QPushButton("Send",self)
self.bSend.clicked.connect(self.sendData)
self.show()
def closeEvent(self, event):
print("Close.")
self.serialc.quit();
#QtCore.pyqtSlot(object)
def updateData(self, data):
print(data)
def sendData(self, pressed):
self.serialWrite.emit("Send Me! Please?")
def usingMoveToThread(self):
self.serialc = SerialCon()
# binding signals:
self.serialc.received.connect(self.updateData)
self.serialWrite.connect(self.serialc.writeData)
# start thread
self.serialc.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
guui = MyGui(app)
guui.usingMoveToThread()
sys.exit(app.exec_())
I consider it a workaround for now, but it does not really answer the question for me.
Plus, as mentioned in the previously linked QT blog entry,
it is not really the intended way to use the QThread.
So I am still wondering how to get the first code in my question to work as expected.
If you have some ideas about what is wrong with my first code, please let my know!