Using QThread to Prevent Freeze when Clicking on QTreeWidget - python

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

Related

How to get a child thread to close when main GUI window is closed in pyqt5 / python 3?

I am writing a GUI using pyqt5 (Python 3.6). I am trying to run another thread in parallel of the main GUI. I would like this child thread to terminate when I close the main application. In this example, the child thread is a simple counter. When I close the main GUI, the counter still keeps going. How can I get the thread to end when the GUI window is closed? In the real case I may have a thread that is running operations that takes a few minutes to execute. I am reluctant to use a flag within the thread to assess if it should end because it may take minutes for the thread to close after the GUI window has been closed. I would prefer the thread to end right away. Any suggestions?
Thank you.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.worker = threading.Thread(target=Worker)
self.worker.daemon = True
self.worker.start()
def Worker(count=1):
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
I tried to use the QThread but this locks up the main GUI. I am not sure if I am implementing it correctly.
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.worker = Worker()
self.worker.run()
def closeEvent(self,event):
print('Closing')
self.worker.terminate()
event.accept()
class Worker(QThread):
def __init__(self):
QThread.__init__(self)
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread,QObject
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.my_thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.worker.run)
self.my_thread.start()
def closeEvent(self,event):
print('Closing')
class Worker(QObject):
def __init__(self):
super().__init__()
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
hope self.my_thread.requestInterruption() won't qualify as flag, otherwise is not the sought answer :
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
from PyQt5.QtCore import QThread,QObject
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
def on_click(self):
self.my_thread = QThread()
self.worker = Worker(self.my_thread)
self.worker.moveToThread(self.my_thread)
self.my_thread.started.connect(self.worker.run)
self.my_thread.start()
def closeEvent(self,event):
self.my_thread.requestInterruption()
print('Closing')
self.my_thread.quit()
self.my_thread.wait()
print('is thread running ?', self.my_thread.isRunning())
class Worker(QObject):
def __init__(self, thread):
super().__init__()
self.thread = thread
def run(self):
count=1
while count>0:
print(count)
time.sleep(2)
count+=1
if self.thread.isInterruptionRequested():
break
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
see PySide6.QtCore.QThread.isInterruptionRequested()
Using threading.thread like in your question, I used threading.Event() and I set it (= True) to stop your Worker loop, but once again I hope is not a flag:
from PyQt5 import QtWidgets
from PyQt5.QtWidgets import (QWidget, QApplication,QPushButton,
QVBoxLayout)
import time, threading, sys
class testScriptApp(QtWidgets.QWidget):
def __init__(self, parent=None):
# initialize th widget
QtWidgets.QWidget.__init__(self, parent)
# set the window title
self.setWindowTitle("Scripting")
# manage the layout
self.mainGrid = QVBoxLayout()
self.button = QPushButton('Start')
self.button.clicked.connect(self.on_click)
self.mainGrid.addWidget(self.button)
self.setLayout(self.mainGrid)
self.event = threading.Event()
def on_click(self):
self.worker = threading.Thread(target=Worker, args = (self.event,))
self.worker.daemon = True
self.worker.start()
def closeEvent(self, event):
if 'worker' in self.__dict__:
self.event.set() ## sets event: threading.Event() = True
self.worker.join() ## waits self.worker to complete ***
print('is worker alive, self.worker.is_alive() : ', self.worker.is_alive())
pass
def Worker(event, count=1 ):
print(event)
while count>0:
print(count)
time.sleep(2)
count+=1
if event.is_set():
break
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
myapp = testScriptApp()
myapp.show()
app.exec_()
try the code commenting and uncommentig out self.event.set() & self.worker.join() in
def closeEvent(self, event):
if 'worker' in self.__dict__:
self.event.set() ## sets event: threading.Event() = True
self.worker.join() ## waits self.worker to complete ***
print('is worker alive, self.worker.is_alive() : ', self.worker.is_alive())
pass
*** Wait until the thread terminates. This blocks the calling thread until the thread whose join() method is called terminates –

How to show another window

The project occurs loging in and signing in.
Im trying a transition from registerwindow to mainwindow and when we submit the window is automatically transit to mainwindow. There is only way to do this (at least for me) i have to import two python doc which named mainwindow.py and register.py, they are in same doc by the way.
This is the mainmenu.py
from PyQt5 import QtCore,QtGui,QtWidgets
from window.register import Ui_Form
class Ui_MainWindow(object):
def login(self):
self.window = QtWidgets.QWidget()
self.ui = Ui_Form()
self.ui.setupUi(self.window)
self.window.show()
MainWindow.hide()
and this is register.py
from PyQt5 import QtCore, QtGui, QtWidgets
from window.mainmenu import Ui_MainWindow
import sqlite3
class Ui_Form(object):
def submit(self):
sorgu2 = "Select * From users where nickname = ?"
sorgu = "INSERT INTO users values(?,?)"
self.cursor.execute(sorgu,(self.lineEdit.text(),self.lineEdit.text()))
self.connect.commit()
Form.hide()
self.window2 = QtWidgets.QMainWindow()
self.ui2 = Ui_MainWindow()
self.ui2.setupUi(self.window2)
self.window2.show()
Its supposed to be when i clicked to the button the register window will be hidden and mainmenu window will be show. Same thing for the mainmenu but the direct opposite
I know i am doing circular dependent imports but there is no other way but importing them to each other
If second window will be QDialog then you can hide main window, use exec() for QDialog and main window will wait till you close QDialog, and when it returns to main window then you can show it again.
from PyQt5 import QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton("Show Second Window", self)
self.button.clicked.connect(self.show_second_window)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.show()
def show_second_window(self):
self.hide() # hide main window
self.second = SecondWindow()
self.second.exec() # will wait till you close second window
self.show() # show main window again
class SecondWindow(QtWidgets.QDialog): # it has to be dialog
def __init__(self):
super().__init__()
self.button = QtWidgets.QPushButton("Close It", self)
self.button.clicked.connect(self.show_second_window)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.show()
def show_second_window(self):
self.close() # go back to main window
app = QtWidgets.QApplication([])
main = MainWindow()
app.exec()
The other popular method is to create two widgets with all contents and replace widgets in one window.
from PyQt5 import QtWidgets
class MainWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.button = QtWidgets.QPushButton("Show Second Window", self)
self.button.clicked.connect(self.show_second_window)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.show()
def show_second_window(self):
self.close()
self.parent.set_content("Second")
class SecondWidget(QtWidgets.QWidget):
def __init__(self, parent):
super().__init__()
self.parent = parent
self.button = QtWidgets.QPushButton("Close It", self)
self.button.clicked.connect(self.show_second_window)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.show()
def show_second_window(self):
self.close()
self.parent.set_content("Main")
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.layout = QtWidgets.QVBoxLayout(self)
self.set_content("Main")
self.show()
def set_content(self, new_content):
if new_content == "Main":
self.content = MainWidget(self)
self.layout.addWidget(self.content)
elif new_content == "Second":
self.content = SecondWidget(self)
self.layout.addWidget(self.content)
app = QtWidgets.QApplication([])
main = MainWindow()
app.exec()
EDIT: Change window's content using QStackedLayout
from PyQt5 import QtWidgets
class FirstWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton("Show Second Stack", self)
self.button.clicked.connect(self.change_stack)
layout.addWidget(self.button)
def change_stack(self):
self.parent().stack.setCurrentIndex(1)
class SecondWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent=parent)
layout = QtWidgets.QVBoxLayout(self)
self.button = QtWidgets.QPushButton("Show First Stack", self)
self.button.clicked.connect(self.change_stack)
layout.addWidget(self.button)
def change_stack(self):
self.parent().stack.setCurrentIndex(0)
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.stack = QtWidgets.QStackedLayout(self)
self.stack1 = FirstWidget(self)
self.stack2 = SecondWidget(self)
self.stack.addWidget(self.stack1)
self.stack.addWidget(self.stack2)
self.show()
app = QtWidgets.QApplication([])
main = MainWindow()
app.exec()

How do i open a class from within another class by selecting an item on ToolBar?? Python/PyQt

I am trying to run class AddTQuestions from a def in class AddTest but it wont work!! It opens the window AddTQuestions for a split-second then closes it straight away?!
The code is shown here:
import sys
from PyQt4 import QtCore, QtGui
class Example(QtGui.QMainWindow):
def __init__(self):
super(Example, self).__init__()
self.initUI()
def initUI(self):
RunClassAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
RunClassAction.triggered.connect(self.run)
self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(RunClassAction)
self.setGeometry(300, 300, 300, 200)
self.setWindowTitle('Why Wont this Woooorkkkkk')
self.show()
def run(self):
AddQuestion = AddTQuestions()
AddQuestion.show()
class AddTQuestions(QtGui.QMainWindow):
def __init__(self, parent=None):
super(AddTQuestions, self).__init__(parent)
self.welldone = QtGui.QLabel('WellDone')
self.button = QtGui.QPushButton('Press Me')
layout = QtGui.QVBoxLayout()
layout.addWidget(self.welldone)
layout.addWidget(self.button)
self.setLayout(layout)
print("hello")
if __name__ == '__main__':
app = QtGui.QApplication([])
window = Example()
window.show()
app.exec_()
The object get's garbage collected, since you don't hold any reference to it when the function ends.
add them as class variables like this and the window stays open.
self.AddQuestion = AddTQuestions()
self.AddQuestion.show()

How to set focus on a widget at app startup?

I'm trying to set the focus on QLineEdit widget at app startup but for some reasons it fails. Calling the method which includes the QLineEdit_object.setFocus() and is bound to a button click, works perfectly. However on startup, it seems like it doesn't execute at all when set to initialize after widget creation.
Using PySide with Python.
# coding=utf-8
import sys
import PySide.QtGui as QG
import PySide.QtCore as QC
class GG(QG.QMainWindow):
def __init__(self):
super(GG, self).__init__()
self.move(0,0)
self.resize(400,300)
self.setWindowTitle('Demo')
self.tabw = QG.QTabWidget()
self.tab1 = Tab1()
self.tab2 = Tab2()
self.tabw.addTab(self.tab1, 'Tab1')
self.tabw.addTab(self.tab2, 'Tab2')
hbox = QG.QHBoxLayout()
hbox.addWidget(self.tabw)
self.setCentralWidget(self.tabw)
self.setLayout(hbox)
self.show()
class Tab1(QG.QWidget):
def __init__(self):
super(Tab1, self).__init__()
self.btns()
self.inputt()
self.layoutz()
self.inp.setFocus() # doesn't set the focus on startup ?
self.show()
def inputt(self):
self.inp = QG.QLineEdit('', self)
def btns(self):
self.btn1 = QG.QPushButton('Button1', self)
self.btn1.clicked.connect(self.focusit) # works just fine
def layoutz(self):
vbox = QG.QVBoxLayout()
vbox.addWidget(self.btn1)
vbox.addStretch(1)
vbox.addWidget(self.inp)
self.setLayout(vbox)
def focusit(self):
self.inp.setFocus() # works just fine
class Tab2(Tab1):
def __init__(self):
super(Tab2, self).__init__()
def main():
app = QG.QApplication(sys.argv)
a = GG()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
Well, after some playing I came up with this solution:
import sys
import PySide.QtGui as QG
import PySide.QtCore as QC
class GG(QG.QMainWindow):
def __init__(self):
super(GG, self).__init__()
self.move(0,0)
self.resize(400,300)
self.setWindowTitle('Demo')
self.tabw = QG.QTabWidget()
self.tab1 = Tab1()
self.tab2 = Tab2()
self.tabw.addTab(self.tab1, 'Tab1')
self.tabw.addTab(self.tab2, 'Tab2')
hbox = QG.QHBoxLayout()
hbox.addWidget(self.tabw)
self.setCentralWidget(self.tabw)
self.setLayout(hbox)
self.tab2.inp.setFocus() # setting focus right here
self.tab1.inp.setFocus() # and here; notice the order
self.show()
class Tab1(QG.QWidget):
def __init__(self):
super(Tab1, self).__init__()
self.btns()
self.inputt()
self.layoutz()
self.show()
def inputt(self):
self.inp = QG.QLineEdit('', self)
def btns(self):
self.btn1 = QG.QPushButton('Button1', self)
self.btn1.clicked.connect(self.focusit)
def layoutz(self):
vbox = QG.QVBoxLayout()
vbox.addWidget(self.btn1)
vbox.addStretch(1)
vbox.addWidget(self.inp)
self.setLayout(vbox)
def focusit(self):
self.inp.setFocus()
class Tab2(Tab1):
def __init__(self):
super(Tab2, self).__init__()
def main():
app = QG.QApplication(sys.argv)
a = GG()
sys.exit(app.exec_())
if __name__ == '__main__':
main()

PyQt: How to hide QMainWindow

Clicking Dialog_01's button hides its window and opens Dialog_02. Clicking Dialog_02's button should close its windows and unhide Dialog_01. How to achieve it?
import sys, os
from PyQt4 import QtCore, QtGui
class Dialog_02(QtGui.QMainWindow):
def __init__(self):
super(Dialog_02, self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
Button_02 = QtGui.QPushButton("Close THIS and Unhide Dialog 01")
Button_02.clicked.connect(self.closeAndReturn)
myBoxLayout.addWidget(Button_02)
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.setWindowTitle('Dialog 02')
def closeAndReturn(self):
self.close()
class Dialog_01(QtGui.QMainWindow):
def __init__(self):
super(Dialog_01, self).__init__()
myQWidget = QtGui.QWidget()
myBoxLayout = QtGui.QVBoxLayout()
Button_01 = QtGui.QPushButton("Hide THIS and Open Dialog 02")
Button_01.clicked.connect(self.callAnotherQMainWindow)
myBoxLayout.addWidget(Button_01)
myQWidget.setLayout(myBoxLayout)
self.setCentralWidget(myQWidget)
self.setWindowTitle('Dialog 01')
def callAnotherQMainWindow(self):
self.hide()
self.dialog_02 = Dialog_02()
self.dialog_02.show()
self.dialog_02.raise_()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
dialog_1 = Dialog_01()
dialog_1.show()
sys.exit(app.exec_())
Make the first window a parent of the second window:
class Dialog_02(QtGui.QMainWindow):
def __init__(self, parent):
super(Dialog_02, self).__init__(parent)
# ensure this window gets garbage-collected when closed
self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
...
def closeAndReturn(self):
self.close()
self.parent().show()
class Dialog_01(QtGui.QMainWindow):
...
def callAnotherQMainWindow(self):
self.hide()
self.dialog_02 = Dialog_02(self)
self.dialog_02.show()
If you want the same dialog to be shown each time, do something like:
def callAnotherQMainWindow(self):
self.hide()
if not hassattr(self, 'dialog_02'):
self.dialog_02 = Dialog_02(self)
self.dialog_02.show()
and hide() the child window, rather than closing it.

Categories