Python Qt - How to insert items in Table widget from another Thread? - python

I want to insert text items from a Worker Qthread to a QTableWidget UI in Main thread ?.
I want to know the Syntax to create the signal in Main thread , so I can insert the text as well as the row and column from Worker thread by sending via signal
class Example(QWidget):
def __init__(self):
super().__init__()
self.myclass2 = myclass2()
self.myclass2.start()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Icon')
self.setWindowIcon(QIcon('web.png'))
self.show()
class myclass2(QThread):
def __init__(self, parent=None):
super(myclass2, self).__init__(parent)
def run(self):
while True:
time.sleep(.1)
print(" in thread \n")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

You already know that you must use a signal, now the question is: what data do you need to send?, You can think that you should send the row, the column and the text, so the signal must send 2 whole and one string, then you connect it to a slot and in it you insert it as if the data was created in the main thread:
import sys
import time
import random
from PyQt5 import QtCore, QtWidgets
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
lay = QtWidgets.QVBoxLayout(self)
self.table = QtWidgets.QTableWidget(10, 10)
lay.addWidget(self.table)
self.myclass2 = myclass2()
self.myclass2.new_signal.connect(self.some_function)
self.myclass2.start()
#QtCore.pyqtSlot(int, int, str)
def some_function(self, r, c, text):
it = self.table.item(r, c)
if it:
it.setText(text)
else:
it = QtWidgets.QTableWidgetItem(text)
self.table.setItem(r, c, it)
class myclass2(QtCore.QThread):
new_signal = QtCore.pyqtSignal(int, int, str)
def run(self):
while True:
time.sleep(.1)
r = random.randint(0, 9)
c = random.randint(0, 9)
text = "some_text: {}".format(random.randint(0, 9))
self.new_signal.emit(r, c, text)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = Example()
ex.showMaximized()
sys.exit(app.exec_())

Related

Using QThread to Prevent Freeze when Clicking on QTreeWidget

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()

How to return value from the QWidget

I have a widget that ends with pressing OK button. I want that the widget returns a value to the main program (which is in simple example below the obtained value increased by 1). How can I do this?
Also, is there any more elegant way to show the same QWidget with the different title?
MWE:
import sys
from PyQt5 import QtGui, QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self,val):
self.val=val
super(MainWindow, self).__init__()
self.initUI()
def initUI(self):
self.End= QtWidgets.QPushButton('OK', self)
self.End.clicked.connect(self.end)
MainLayout = QtWidgets.QVBoxLayout()
MainLayout.addWidget(self.End)
self.setLayout(MainLayout)
self.setWindowTitle(str(self.val))
self.show()
def end(self):
# return self.val+1
self.close()
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = MainWindow(i)
ex.show()
res = app.exec_()
print(res)
sys.exit()
if __name__ == '__main__':
main()
If you want to use a widget to get some value after some processing, the correct thing is to use QDialog, that prevents any other window from opening if you use exec_():
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, val, parent=None):
super(Dialog, self).__init__(parent)
self.val = val
self.initUI()
def initUI(self):
endButton = QtWidgets.QPushButton('OK')
endButton.clicked.connect(self.on_clicked)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(endButton)
self.setWindowTitle(str(self.val))
#QtCore.pyqtSlot()
def on_clicked(self):
self.val += 1
self.accept()
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = Dialog(i)
ex.setAttribute(QtCore.Qt.WA_DeleteOnClose)
if ex.exec_() == QtWidgets.QDialog.Accepted:
print(ex.val)
if __name__ == '__main__':
main()
It is unclear what exactly your intention is with this code. If val is supposed to be a counter for the number of MainWindow instances, then you must declare it as a static:
class MainWindow(QtWidgets.QWidget):
val = 0
def __init__(self):
super(MainWindow, self).__init__()
MainWindow.val += 1
self.initUI()
def initUI(self):
# ...
self.setWindowTitle(str(MainWindow.val))
# ...
def main():
# ...
for i in range(10):
ex = MainWindow()
# ...
# ...
There is nothing wrong in calling QWidget::setWindowTitle() so I don't see a problem here.
If you really want to have it as a non-static class member and each MainWindow uses it as some sort of ID, you can easily call the value right after the ex.show():
def main():
app = QtWidgets.QApplication(sys.argv)
for i in range(10):
ex = MainWindow()
ex.show()
print(MainWindow.val)
res = app.exec_()
print(res)
sys.exit()
Just because you close a widget doesn't mean you have destroyed it so calling the values stored inside the object is not an issue.
However it appears you are not familiar what QApplication::exec() is intended for. It runs the main loop of the given application and returns the exit status (you can set it when calling exit() as the parameter passed to that function). If you simply want to get the value of val upon closing a widget you can simply override the closeEvent handler and emit a signal. Create a QObject instance and connect each MainWindow instance's custom close signal to a slot and do with the value whatever you want to.

PyQt5, QThread: How to keep GUI responsive while running non-loop function in thread? [duplicate]

I want to access the parent class widgets in the QThread class
This line gives hangs GUI "Example().setWindowTitle("Window")"
How can I do that?
class Example(QWidget):
def __init__(self):
super().__init__()
self.myclass2 = myclass2()
self.myclass2.start()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Icon')
self.setWindowIcon(QIcon('web.png'))
self.show()
class myclass2(QThread):
def __init__(self, parent=None):
super(myclass2, self).__init__(parent)
def run(self):
while True:
time.sleep(.1)
print(" in thread \n")
Example().setWindowTitle("Window")
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())
You must understand the following expression:
Example().setWindowTitle("Window")
it is equivalent to:
obj = Example()
obj.setWindowTitle("Window")
That is, you are creating another Example object other than ex = Example(), and that object is creating another myclass2 object, and that other myclass2 object is creating another Example, and an infinite loop is clearly being created.
Another thing that in the future could cause you problems is to establish the same name for different things, although in this case it is not a problem but in future occasions it could bring you problems, the code to which I refer is:
self.myclass2 = myclass2()
For example, it is recommended that classes should start with capital letters.
Another error that is valid only in Qt is that the GUI can not be created or manipulated in a thread other than the main thread. So you can not change the title directly in the other thread but there are 2 methods:
1. QMetaObject::invokeMethod(...)
But for this we must pass the GUI through a property:
class Example(QWidget):
def __init__(self):
super().__init__()
self.myclass2 = MyClass()
self.myclass2.gui = self
self.myclass2.start()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Icon')
self.setWindowIcon(QIcon('web.png'))
self.show()
class MyClass(QThread):
def run(self):
while True:
time.sleep(.1)
print(" in thread \n")
QMetaObject.invokeMethod(self.gui, "setWindowTitle", Qt.QueuedConnection, Q_ARG(str, "Window"))
2. signals & slots
class Example(QWidget):
def __init__(self):
super().__init__()
self.myclass2 = MyClass()
self.myclass2.titleChanged.connect(self.setWindowTitle)
self.myclass2.start()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Icon')
self.setWindowIcon(QIcon('web.png'))
self.show()
class MyClass(QThread):
titleChanged = pyqtSignal(str)
def run(self):
while True:
time.sleep(.1)
print(" in thread \n")
self.titleChanged.emit("Window")
PLUS:
You should not modify the GUI from the thread directly but send the data through a signal:
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
class Example(QtWidgets.QWidget):
def __init__(self):
super().__init__()
lay = QtWidgets.QVBoxLayout(self)
le = QtWidgets.QLineEdit()
lay.addWidget(le)
self.myclass2 = MyClass()
self.myclass2.titleChanged.connect(self.setWindowTitle)
self.myclass2.infoChanged.connect(le.setText) # <-- connect signal
self.myclass2.start()
self.initUI()
def initUI(self):
self.setGeometry(300, 300, 300, 220)
self.setWindowTitle('Icon')
self.setWindowIcon(QtGui.QIcon('web.png'))
self.show()
class MyClass(QtCore.QThread):
titleChanged = QtCore.pyqtSignal(str)
infoChanged = QtCore.pyqtSignal(str) # <-- create signal
def run(self):
counter = 0
while True:
QtCore.QThread.msleep(100)
print(" in thread \n")
self.titleChanged.emit("Window")
self.infoChanged.emit("{}".format(counter)) # <-- emit signal
counter += 1
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
ex = Example()
sys.exit(app.exec_())

PYQT5 crashing when calling .text() on a QLineEdit widget

When I run the code below my program crashes, and I believe this is to-do with the .text() called when the Line edit has something typed in it. I need to assign a variable to what is entered here.
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtWidgets
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
usernameBox = QtWidgets.QLineEdit()
usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = usernameBox.text()
print(x)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = loginScreen()
sys.exit(app.exec_())
If you observe usernameBox it is created as a local variable so it can not be accessed by the other methods of the class, in your case there are 2 solutions:
Make a usernameBox attribute of the class.
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.usernameBox = QtWidgets.QLineEdit()
self.usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(self.usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = self.usernameBox.text()
print(x)
Or use sender() that obtains the object that emits the signal, in your case the QLineEdit.
class loginScreen(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
usernameBox = QtWidgets.QLineEdit()
usernameBox.textChanged.connect(self.myfunc)
vArrangement = QtWidgets.QVBoxLayout()
vArrangement.addWidget(usernameBox)
self.setLayout(vArrangement)
self.show()
def myfunc(self):
x = self.sender().text()
print(x)

Pyqt: Change icon from button

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_())

Categories