PyQt QThread MultiThreading does not work - python

I have 2 QListWidget lists, List2 is being filled when some item has been selected from List1
The problem is that, before filling List2 i have to do alot of tasks which freezes my UI for about 5 seconds which is too annoying, i want to make it fill List2 with QThread but it's not working since before initilizing whole class I'm getting an annoying error
from ui import Ui_Win
from PyQt4 import QtGui, QtCore
class GenericThread(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def __del__(self):
self.quit()
self.wait()
def run(self):
self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
return
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
...
genericThread = GenericThread(self)
self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
genericThread.start()
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text()) # ERROR HERE
Traceback:
# AttributeError: 'NoneType' object has no attribute 'text'
This accures because self.ui.List1.currentItem().text() is None
Why this fucntion is being called before triggering itemSelectionChanged signal?

from ui import Ui_Win ## ui.py is a file that has been generated from Qt Designer and it contains main GUI objects like QListWidget
from PyQt4 import QtGui, QtCore
class GenericThread(QtCore.QThread):
def __init__(self, parent=None, listIndex=0):
QtCore.QThread.__init__(self, parent)
self.listIndex = listIndex
def __del__(self):
self.quit()
self.wait()
def run(self):
if self.listIndex == 2:
for addStr in Something:
#Some long stuff
self.emit( QtCore.SIGNAL('fillListWithItems(QString, int'), addStr, self.listIndex)
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
...
self.ui.List1.list1SelectedItem.connect(self.fill_List2)
#QtCore.pyqtSlot(QString, int)
def self.fillListWithItems(addStr, lstIdx):
if lstIdx==2:
self.ui.List2.addItem(addStr)
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text())
genericThread = GenericThread(self, listIndex=2)
self.connect(genericThread, QtCore.SIGNAL("fillListWithItems(QString, int)"), self.fillListWithItems )
genericThread.start()
Thanks #ekhumoro

Your problem is that simply starting the thread is firing the itemSelectionChanged() signal (because it is in the run function), which you have connected to your fill_List2() function. You need to connect the itemSelectionChanged event on List1 to a SLOT that will trigger the thread to do the heavy computation and update List2.
I've had to make a number of assumptions about what ui is and what List1 and List2 are and how they're set up, but here is a working example. I've replaced the heavy computation in your GenericThread with a simple 2 second delay.
If I've misinterpreted / made bad assumptions, please update the question and leave a comment
test_slotting.py
from ui import Ui_Win
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import pyqtSlot
from PyQt4.QtGui import *
import time
class GenericThread(QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def __del__(self):
self.quit()
self.wait()
def run(self):
#Do all your heavy processing here
#I'll just wait for 2 seconds
time.sleep(2)
self.emit( QtCore.SIGNAL('itemSelectionChanged()'))
return
class MainUI(QtGui.QMainWindow):
def __init__(self, parent=None):
QtGui.QMainWindow.__init__(self)
self.ui = Ui_Win()
self.ui.setupUi(self)
self.ui.List1 = QListWidget(self)
self.ui.List2 = QListWidget(self)
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(self.ui.List1)
hbox.addWidget(self.ui.List2)
self.ui.centralWidget.setLayout(hbox)
self.ui.List1.addItems(['alpha','beta','gamma','delta','epsilon'])
self.ui.List2.addItems(['Item1','Item2'])
self.ui.List1.itemSelectionChanged.connect(self.start_heavy_processing_thread)
#pyqtSlot()
def start_heavy_processing_thread(self):
genericThread = GenericThread(self)
self.connect(genericThread, QtCore.SIGNAL("itemSelectionChanged()"), self.fill_List2 )
genericThread.start()
def fill_List2(self):
self.ui.List2.clear()
list1SelectedItem = str(self.ui.List1.currentItem().text())
self.ui.List2.addItem(list1SelectedItem)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = MainUI()
MainWindow.show()
sys.exit(app.exec_())
ui.py
from PyQt4 import QtCore, QtGui
class Ui_Win(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(416, 292)
self.centralWidget = QtGui.QWidget(MainWindow)
self.centralWidget.setObjectName("centralWidget")
MainWindow.setCentralWidget(self.centralWidget)
if __name__ == "__main__":
import sys
app = QtGui.QApplication(sys.argv)
MainWindow = QtGui.QMainWindow()
ui = Ui_Win()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())

Related

PyQt5 Access MainWindow from Another Window

I am newbie and in that project I am stuck at one point. How can I access functions in MainWindow class from another Window.
For example I need to access Main.list_refresh when Child_EditAccount.btn_EDIT_ACCOUNT_3.clicked . I tried something like signal & slot but doesn't work. I maked 3 different Window with pyQt Designer and I need to link the three together.
new.py
# Designs
from design import Ui_MainWindow
from design_addAccount import Ui_MainWindow_Add
from design_editAccount import Ui_MainWindow_Edit
# Modules
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal, pyqtSlot
import sqlite3
con = sqlite3.connect('shorts.sqlite3')
cur = con.cursor()
class Main(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.account_process = AccountProcess()
self.ui.lst_accounts.itemClicked.connect(self.account_process.edit_account)
self.ui.btn_delete_account.clicked.connect(self.account_process.delete_account)
self.ui.btn_edit_account.clicked.connect(self.account_process.edit_account)
self.ui.btn_add_account.clicked.connect(self.account_process.add_account)
self.ui.btn_refresh.clicked.connect(self.list_refresh)
self.refresh_trigger = Child_EditAccount()
self.refresh_trigger.buttonClicked.connect(self.list_refresh)
def list_refresh(self):
self.ui.lst_accounts.clear()
for row in cur.execute("SELECT * FROM users"):
self.ui.lst_accounts.addItem('%s' % (str(row[3])))
class Child_AddAccount(QtWidgets.QMainWindow,Ui_MainWindow_Add):
def __init__(self, parent=None):
super(Child_AddAccount, self).__init__()
self.ui = Ui_MainWindow_Add()
self.ui.setupUi(self)
class Child_EditAccount(QtWidgets.QMainWindow,Ui_MainWindow_Edit):
buttonClicked = pyqtSignal()
def __init__(self, parent=None):
super(Child_EditAccount, self).__init__()
self.ui = Ui_MainWindow_Edit()
self.ui.setupUi(self)
def edit_infos(self, username, password, nickname, id):
self.id = id
self.ui.txtBox_username_3.insert(username)
self.ui.txtBox_password_3.insert(password)
self.ui.txtBox_nickname_3.insert(nickname)
self.ui.btn_EDIT_ACCOUNT_3.clicked.connect(self.update_by_id)
def update_by_id(self):
cur.execute('UPDATE users SET username="%s", password="%s", nickname="%s" WHERE id=%s' % (self.ui.txtBox_username_3.text(), self.ui.txtBox_password_3.text(), self.ui.txtBox_nickname_3.text(), self.id))
con.commit()
self.buttonClicked.emit()
class AccountProcess(QtCore.QObject):
def add_account(self):
self.child_add = Child_AddAccount()
self.child_add.show()
print('Pressed edit button')
def delete_account(self):
print('Pressed delete button')
def edit_account(self, item):
self.child_edit = Child_EditAccount()
for i in cur.execute(f"SELECT * FROM users WHERE nickname=\"{item.text()}\";"):
self.child_edit.edit_infos(str(i[1]), str(i[2]), str(i[3]), str(i[0]))
self.child_edit.show()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
main = Main()
main.show()
sys.exit(app.exec_())
You can use a pyqtSignal to do what you want.
in the Child_EditAccount class add a pyqtsignal
class Child_EditAccount(QtWidgets.QMainWindow,Ui_MainWindow_Add):
buttonClicked = pyqtSignal()
def __init__(self, parent=None):
before the __init__, then when you need to trigger the function use
buttonClicked.emit()
in the function where the button is used in the Child_EditAccount class
Then in the main window, you can create a connection to a function via the pyqtsignal
self.Child_EditAccount.buttonClicked.connect(self.myfunction)
in the __init__ of the main class. where self.Child_EditAccount is the instance of your Child_EditAccount class and self.function is the function you want to trigger Main.list_refresh
You can also create a signal from QtDesigner itself when you create the Ui_MainWindow_Edit
https://doc.qt.io/qt-5/designer-connection-mode.html
be carefull, Child_EditAccount inherite from Ui_MainWindow_Add instead of Ui_MainWindow_Edit probably.
____
what you can also do is link the sigal of the button directly in the main program as in this little example
# Modules
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtCore import pyqtSignal, pyqtSlot
class Main2(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Main2, self).__init__()
self.centralWidget = QtWidgets.QWidget()
self.setCentralWidget(self.centralWidget)
self.but= QtWidgets.QPushButton('tdvfdbt')
self.l = QtWidgets.QHBoxLayout()
self.l.addWidget(self.but)
self.centralWidget.setLayout(self.l)
class Main(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__()
self.win = Main2()
self.a = self.win.show()
self.win.but.clicked.connect(self.myfunction)
def myfunction(self):
print('called from sub window')
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
ex = Main(app)
ex.setWindowTitle('Model micro GUI')
# ex.showMaximized()
ex.show()
sys.exit(app.exec())
if you know the name of the button widget of the Child_EditAccount class you can link it via
self.Child_EditAccount.btn_EDIT_ACCOUNT_3.clicked.connect(self.myfunction)
##______
Put the pystsignal in the AccountProcess class
class AccountProcess(QtCore.QObject):
buttonClicked = pyqtSignal()
def add_account(self):
self.child_add = Child_AddAccount()
self.child_add.show()
print('Pressed edit button')
def delete_account(self):
print('Pressed delete button')
def edit_account(self, item):
self.child_edit = Child_EditAccount()
for i in cur.execute(f"SELECT * FROM users WHERE nickname=\"{item.text()}\";"):
self.child_edit.edit_infos(str(i[1]), str(i[2]), str(i[3]), str(i[0]))
self.child_edit.buttonClicked.connect(self.EmitAgain)
self.child_edit.show()
def EmitAgain(self):
self.buttonClicked.emit()
Then use it in the main class
class Main(QtWidgets.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.account_process = AccountProcess()
self.account_process.buttonClicked.connect(self.list_refresh)

PyQT: How to call a function on main thread and get the result (not store the result beforehand)? [duplicate]

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

Pyside2 how to open the dialog but not always on top

I create a main frame and a pushbutton ,
and let button clicked to open a dialog , but the dialog always on top ,
I try to used setWindowsFlag but not work.
from PySide2.QtWidgets import QApplication,QMainWindow,QTabWidget,QWidget
from PySide2.QtWidgets import QMessageBox,QFileDialog,QErrorMessage
from PySide2 import QtCore, QtGui, QtWidgets
class UI_Test20(object):
def setupUi(self, Test202):
Test202.setObjectName("Test202")
Test202.resize(100,100)
self.centralwidget = QtWidgets.QWidget(Test202)
self.centralwidget.setObjectName("centralwidget")
self.pb = QtWidgets.QPushButton(self.centralwidget)
self.pb.setText('push button!')
Test202.setCentralWidget(self.centralwidget)
self.pb.clicked.connect(self.btnClicked)
self.retranslateUi(Test202)
QtCore.QMetaObject.connectSlotsByName(Test202)
def retranslateUi(self, Test202):
Test202.setWindowTitle(QtWidgets.QApplication.translate("Test202", "MainWindow", None, -1))
def btnClicked(self):
ui = Ui_Dialog1(self)
ui.show()
class Test20(QMainWindow, UI_Test20) :
def __init__(self, parent):
super(Test20, self).__init__(parent)
self.setupUi(self)
and the dialog code
class Ui_Dialog1(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Ui_Dialog1, self).__init__(parent)
self.p = parent
self.setupUi(self)
def setupUi(self, Dialog1):
Dialog1.setObjectName("Dialog1")
Dialog1.resize(333, 173)
main
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
ui = Test20(None)
ui.show()
sys.exit(app.exec_())
The problem is caused by the fact that the main window is the parent of the QDialog, so it must be removed, but if that is done, the garbage collector will delete it, so the QDialog member of the class must be made:
def btnClicked(self):
self.ui = Ui_Dialog1()
self.ui.show()
Plus: the correct thing is not to modify the design so I move the connection and the slot associated with the clicked button pb and if we want it to close when the window closes we overwrite the closeEvent() method:
class Test20(QMainWindow, UI_Test20):
def __init__(self, parent):
super(Test20, self).__init__(parent)
self.setupUi(self)
self.pb.clicked.connect(self.btnClicked)
def btnClicked(self):
self.ui = Ui_Dialog1()
self.ui.show()
def closeEvent(self, *args, **kwargs):
self.ui.close()
QMainWindow.closeEvent(self, *args, **kwargs)

the widget does not close/quit with the main window

I have the following python code where the main window has a widget using PyQt4
import os
import sys
from PyQt4 import QtGui, QtCore, Qt
class Widget(QtGui.QLabel):
def __init__(self):
super(FringeFrame, self).__init__()
self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.showFullScreen()
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.widget = Widget()
def main():
app = QtGui.QApplication(sys.argv)
mywin = MainWindow()
mywin.show()
sys.quit(app.exec_ ())
if __name__ == '__main__':
main()
the issue here is that i want widget and mywin to have their own window, it works that way, but when I close mywin, the widget is not closed with mywin.
How should i do it?
You can just override the QMainWindow's closeEvent:
class MainWindow(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.widget = Widget()
def closeEvent(self, event):
self.widget.close()

PySide (Qt) signal not reaching my slot

I have this simplified code that doesn't work and I can't understand why... I expect MySlot.slt() to be called every time i press a key in my QTextEdit but it doesn't! Could you please have a look?
#!/usr/bin/env python2
import sys
from PySide import QtGui, QtCore
class MySlot(object):
def __init__(self, qte):
qte.textChanged.connect(self.soc)
def slt(self):
print("got signal")
class MainWid(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWid, self).__init__(parent)
self.initgui()
def initgui(self):
lay = QtGui.QVBoxLayout()
txt = QtGui.QTextEdit(self)
MySoc(txt)
lay.addWidget(txt)
self.setLayout(lay)
self.show()
def main():
app = QtGui.QApplication(sys.argv)
wid = MainWid()
sys.exit(app.exec_())
if __name__=="__main__":
main()
Your MySoc object in initgui has local scope and is therefore destroyed at the end of initgui.
Assign the object to a variable:
...
self.soc = MySoc(txt);
...
and you will see the "got signal" output each time you press a key.

Categories

Resources