Borrowing code from : Progress Bar Does not Render Until Job is Complete , I tried to to find way to quit/kill a Qthread while it is working, here my code, you can quit the main window while progress bar is working stopping files to be copied:
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets
class myProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, parent=None):
super(myProgressDialog, self).__init__(parent=parent)
def closeEvent(self, event):
"""Get the name of active window about to close
"""
print('cant close')
event.ignore()
class MainWindow(QtWidgets.QMainWindow):
startMoveFilesSignal = QtCore.pyqtSignal(str, str)
def __init__(self):
super(MainWindow, self).__init__()
# srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
# dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
srcdir = "in"
dstdir = "out"
self.le_src = QtWidgets.QLineEdit(srcdir)
self.le_dst = QtWidgets.QLineEdit(dstdir)
self.button = QtWidgets.QPushButton("Copy")
# self.button.clicked.connect(self.archiveEntry)
self.button.clicked.connect(self.archiveEntry2)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QFormLayout(central_widget)
lay.addRow("From: ", self.le_src)
lay.addRow("To: ", self.le_dst)
lay.addRow(self.button)
print('self,thread :', self.thread)
def archiveEntry2(self):
print('connected')
self.progressbar = myProgressDialog(self)
# RIMUOVO Cancel Button
self.progressbar.setCancelButton(None)
self.progressbar.hide()
self.thread = QtCore.QThread(self)
self.thread.start()
self.helper = MoveFileHelper()
self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
self.helper.progressChanged.connect(self.progressbar.setValue)
self.helper.finished.connect(self.on_finished)
self.helper.started.connect(self.progressbar.show)
self.helper.errorOccurred.connect(self.on_errorOcurred)
self.helper.moveToThread(self.thread)
self.archiveEntry()
## Questo funziona
def closeEvent(self, event):
"""Get the name of active window about to close
"""
print('killing thread')
try:
if self.thread.isRunning():
print('killing running thread', self.thread.isRunning())
# self.thread.terminate() ## ---------> error Qt has caught an exception thrown from an event handler.
self.thread.quit() ### funziona ma non in SPYDER
except Exception as Exceptionz:
print('Exception :', Exceptionz)
try:
print('killing running thread after quit :', self.thread.isRunning())
except:
print('quitted')
event.accept()
#QtCore.pyqtSlot()
def archiveEntry(self):
self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
self.progressbar.hide()
#QtCore.pyqtSlot()
def on_finished(self):
self.button.setText('Finished')
#QtCore.pyqtSlot(str)
def on_errorOcurred(self, msg):
QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
class MoveFileHelper(QtCore.QObject):
progressChanged = QtCore.pyqtSignal(int)
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
errorOccurred = QtCore.pyqtSignal(str)
def calculateAndUpdate(self, done, total):
progress = int(round((done / float(total)) * 100))
self.progressChanged.emit(progress)
#staticmethod
def countFiles(directory):
count = 0
if os.path.isdir(directory):
for path, dirs, filenames in os.walk(directory):
count += len(filenames)
return count
#staticmethod
def makedirs(dest):
if not os.path.exists(dest):
os.makedirs(dest)
#QtCore.pyqtSlot(str, str)
def moveFilesWithProgress(self, src, dest):
numFiles = MoveFileHelper.countFiles(src)
# if os.path.exists(dest):
# self.errorOccurred.emit("Dest exist")
# return
if numFiles > 0:
self.started.emit()
MoveFileHelper.makedirs(dest)
numCopied = 0
for path, dirs, filenames in os.walk(src):
for directory in dirs:
destDir = path.replace(src, dest)
MoveFileHelper.makedirs(os.path.join(destDir, directory))
for sfile in filenames:
srcFile = os.path.join(path, sfile)
destFile = os.path.join(path.replace(src, dest), sfile)
shutil.copy(srcFile, destFile)
numCopied += 1
self.calculateAndUpdate(numCopied, numFiles)
for i in range(100000):
i = i*10
self.finished.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.resize(640, ex.sizeHint().height())
ex.show()
sys.exit(app.exec_())
Not sure if it is the right way to kill a Qthread but seems to work.
Even if after stopping the Qthread: self.thread.quit() I stop the copying of files
but the self.thread.isRunning() still returns True.
When trying to split the code and add another window using:
main.py
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets
from mod007b_import import Windowz, MoveFileHelper, myProgressDialog
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MainWindow, self).__init__()
QtWidgets.QMainWindow.__init__(self)
self.layout = QtWidgets.QHBoxLayout()
self.lineEdit = QtWidgets.QLineEdit()
self.lineEdit.setText("Just to fill up the dialog")
self.layout.addWidget(self.lineEdit)
self.button = QtWidgets.QPushButton('pppppp')
self.layout.addWidget(self.button)
self.widget = QtWidgets.QWidget()
self.widget.setLayout(self.layout)
self.setCentralWidget(self.widget)
self.setWindowTitle('Simple')
self.button.clicked.connect(self.newWindow)
self.listz = []
def newWindow(self):
print('newwindow')
self.pippo = Windowz() ########## RIVEDERE PARENT CHILD RELATIONSHIP
self.pippo.show()
# self.listz.append(self.pippo)
pw = self.pippo.parentWidget()
print('list : ', self.listz)
print(pw)
if pw is not None:
print('self :', self)
print('pw : ', pw, pw.layout)
print('pippo :', self.pippo)
# print(' central_widget :', central_widget, type( central_widget))
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
# ex.setWindowTitle('Simple**************')
ex.resize(640, ex.sizeHint().height())
ex.show()
sys.exit(app.exec_())
and
mod007b_import.py
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets
class myProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, parent=None):
super(myProgressDialog, self).__init__(parent=parent)
def closeEvent(self, event):
"""Get the name of active window about to close
"""
print('cant close')
event.ignore()
class Windowz(QtWidgets.QWidget):
# class Windowz(QtWidgets.QMainWindow):
startMoveFilesSignal = QtCore.pyqtSignal(str, str)
# def __init__(self,parent=None):
# # super(Windowz, self).__init__(parent=parent)
# super(Windowz, self).__init__(parent=parent)
def __init__(self):
# super(Windowz, self).__init__(parent=parent)
super().__init__()
# srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
# dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
srcdir = "in"
dstdir = "out"
self.le_src = QtWidgets.QLineEdit(srcdir)
self.le_dst = QtWidgets.QLineEdit(dstdir)
self.button = QtWidgets.QPushButton("Copy")
# self.button.clicked.connect(self.archiveEntry)
self.button.clicked.connect(self.archiveEntry2)
### spostati in Main
# central_widget2 = QtWidgets.QWidget()
# self.setCentralWidget(central_widget2)
# lay = QtWidgets.QFormLayout(central_widget2)
self.lay = QtWidgets.QFormLayout(self)
self.lay.addRow("From: ", self.le_src)
self.lay.addRow("To: ", self.le_dst)
self.lay.addRow(self.button)
print('self,thread :', self.thread)
# self.show()
def archiveEntry2(self):
print('connected')
self.progressbar = myProgressDialog(self)
# RIMUOVO Cancel Button
self.progressbar.setCancelButton(None)
self.progressbar.hide()
self.thread = QtCore.QThread(self)
self.thread.start()
self.helper = MoveFileHelper()
self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
self.helper.progressChanged.connect(self.progressbar.setValue)
self.helper.finished.connect(self.on_finished)
self.helper.started.connect(self.progressbar.show)
self.helper.errorOccurred.connect(self.on_errorOcurred)
self.helper.moveToThread(self.thread)
self.archiveEntry()
## Questo funziona
def closeEvent(self, event):
"""Get the name of active window about to close
"""
print('killing thread')
try:
if self.thread.isRunning():
print('killing running thread', self.thread.isRunning())
# self.thread.terminate() ## ---------> error Qt has caught an exception thrown from an event handler.
self.thread.quit() ### doesnt work
# self.progressbar.hide() ### hides the bar
# self.progressbar.close() ### doesnt work
try:
print('killing running thread after quit :', self.thread.isRunning())
except:
print('quitted')
except Exception as Exceptionz:
print('Exception :', Exceptionz)
event.accept()
#QtCore.pyqtSlot()
def archiveEntry(self):
self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
self.progressbar.hide()
#QtCore.pyqtSlot()
def on_finished(self):
self.button.setText('Finished')
#QtCore.pyqtSlot(str)
def on_errorOcurred(self, msg):
QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
class MoveFileHelper(QtCore.QObject):
progressChanged = QtCore.pyqtSignal(int)
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
errorOccurred = QtCore.pyqtSignal(str)
def calculateAndUpdate(self, done, total):
progress = int(round((done / float(total)) * 100))
self.progressChanged.emit(progress)
#staticmethod
def countFiles(directory):
count = 0
if os.path.isdir(directory):
for path, dirs, filenames in os.walk(directory):
count += len(filenames)
return count
#staticmethod
def makedirs(dest):
if not os.path.exists(dest):
os.makedirs(dest)
#QtCore.pyqtSlot(str, str)
def moveFilesWithProgress(self, src, dest):
numFiles = MoveFileHelper.countFiles(src)
# if os.path.exists(dest):
# self.errorOccurred.emit("Dest exist")
# return
if numFiles > 0:
self.started.emit()
MoveFileHelper.makedirs(dest)
numCopied = 0
for path, dirs, filenames in os.walk(src):
for directory in dirs:
destDir = path.replace(src, dest)
MoveFileHelper.makedirs(os.path.join(destDir, directory))
for sfile in filenames:
srcFile = os.path.join(path, sfile)
destFile = os.path.join(path.replace(src, dest), sfile)
shutil.copy(srcFile, destFile)
numCopied += 1
self.calculateAndUpdate(numCopied, numFiles)
for i in range(100000):
i = i*10
self.finished.emit()
I get a first window, pressing the 'pppppp' button it goes to a second one that is the same as the single file script above: press 'copy' button to start the copying/Qthread, but when I close this window even if the QThread seems to be stopped, progress bar doesnt disappear, I can hide the progress bar but cant close it and in any case the copying process reach completion.
Any idea what is going on ?
PS
in order to have the script working and a having a visible progress bar files need to be in a directory toghether with a 'in' folder with enough files to have a slow process.
OK thanks to #musicamante and to Stopping an infinite loop in a worker thread in PyQt5 the simplest way
I figured out what was wrong in the first of my two codes, here the first one re-written with a flag set to terminate the copying loop when main window is closed, important
commenting out or not :
# self.thread.quit()
# self.thread.wait()
in Mainwindow def closeEvent(self, event): just after the setting of the flag to True (self.ctrl['break'] = True) will end up having the QThread running / not running before the script terminates anyway. For the flag see the Solution 2: Passing a mutable as a control variable in Stopping an infinite loop in a worker thread in PyQt5 the simplest way
import os
import sys
import shutil
from PyQt5 import QtCore, QtWidgets
class myProgressDialog(QtWidgets.QProgressDialog):
def __init__(self, parent=None):
super(myProgressDialog, self).__init__(parent=parent)
def closeEvent(self, event):
"""Get the name of active window about to close
"""
print('cant close')
event.ignore()
class MainWindow(QtWidgets.QMainWindow):
startMoveFilesSignal = QtCore.pyqtSignal(str, str)
def __init__(self):
super(MainWindow, self).__init__()
# srcdir = "/media/zachlab/Windows/LinuxStorage/old/embryos"
# dstdir = "/media/zachlab/Windows/LinuxStorage/old/out"
srcdir = "in"
dstdir = "out"
self.le_src = QtWidgets.QLineEdit(srcdir)
self.le_dst = QtWidgets.QLineEdit(dstdir)
self.button = QtWidgets.QPushButton("Copy")
# self.button.clicked.connect(self.archiveEntry)
self.button.clicked.connect(self.archiveEntry2)
central_widget = QtWidgets.QWidget()
self.setCentralWidget(central_widget)
lay = QtWidgets.QFormLayout(central_widget)
lay.addRow("From: ", self.le_src)
lay.addRow("To: ", self.le_dst)
lay.addRow(self.button)
self.ctrl = {'break': False} # dict with your control variable
print('id of ctrl in MainWindow:', id(self.ctrl))
def archiveEntry2(self):
print('connected')
self.progressbar = myProgressDialog(self)
self.progressbar.setWindowTitle('coopying files')
# RIMUOVO Cancel Button
self.progressbar.setCancelButton(None)
self.progressbar.hide()
self.thread = QtCore.QThread(self)
self.thread.start()
self.helper = MoveFileHelper(self.ctrl)
self.startMoveFilesSignal.connect(self.helper.moveFilesWithProgress)
self.helper.progressChanged.connect(self.progressbar.setValue)
self.helper.finished.connect(self.on_finished)
self.helper.started.connect(self.progressbar.show)
self.helper.errorOccurred.connect(self.on_errorOcurred)
self.helper.moveToThread(self.thread)
self.archiveEntry()
## Questo funziona
def closeEvent(self, event):
"""Get the name of active window about to close
"""
try:
print('killing thread')
print('self.thread.isRunning() before quit :', self.thread.isRunning())
if self.thread.isRunning():
print("quitted ----> self.ctrl['break'] = True")
self.ctrl['break'] = True
self.thread.quit()
self.thread.wait()
except Exception as Exceptionz:
print('Exception :', Exceptionz)
try:
print('self.thread.isRunning() after quit :', self.thread.isRunning())
except:
print('quitted')
# event.accept() # not needed implicit
# event.ignore()
#QtCore.pyqtSlot()
def archiveEntry(self):
self.startMoveFilesSignal.emit(self.le_src.text(), self.le_dst.text())
self.progressbar.hide()
#QtCore.pyqtSlot()
def on_finished(self):
print('on_finished self.ctrl inside worker : ', self.ctrl)
print('self.thread.isRunning() after quit on_finished :', self.thread.isRunning())
self.button.setText('Finished')
#QtCore.pyqtSlot(str)
def on_errorOcurred(self, msg):
QtWidgets.QMessageBox.critical(self, "Error Ocurred", msg)
class MoveFileHelper(QtCore.QObject):
progressChanged = QtCore.pyqtSignal(int)
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
errorOccurred = QtCore.pyqtSignal(str)
def __init__(self, ctrl):
super().__init__()
self.ctrl = ctrl # dict with your control var
print('self.ctrl inside worker MoveFileHelper : ', self.ctrl)
print('Entered run in worker thread')
print('id of ctrl in worker:', id(self.ctrl))
self.ctrl['break'] = False
def calculateAndUpdate(self, done, total):
progress = int(round((done / float(total)) * 100))
self.progressChanged.emit(progress)
#staticmethod
def countFiles(directory):
count = 0
if os.path.isdir(directory):
for path, dirs, filenames in os.walk(directory):
count += len(filenames)
return count
#staticmethod
def makedirs(dest):
if not os.path.exists(dest):
os.makedirs(dest)
#QtCore.pyqtSlot(str, str)
def moveFilesWithProgress(self, src, dest):
numFiles = MoveFileHelper.countFiles(src)
# if os.path.exists(dest):
# self.errorOccurred.emit("Dest exist")
# return
if numFiles > 0:
self.started.emit()
MoveFileHelper.makedirs(dest)
numCopied = 0
for path, dirs, filenames in os.walk(src):
for directory in dirs:
destDir = path.replace(src, dest)
MoveFileHelper.makedirs(os.path.join(destDir, directory))
for sfile in filenames:
if self.ctrl['break'] : # == True : is implicit
self.finished.emit()
return
else:
srcFile = os.path.join(path, sfile)
destFile = os.path.join(path.replace(src, dest), sfile)
shutil.copy(srcFile, destFile)
numCopied += 1
self.calculateAndUpdate(numCopied, numFiles)
for i in range(100000):
i = i*10
self.finished.emit()
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
ex = MainWindow()
ex.resize(640, ex.sizeHint().height())
ex.show()
sys.exit(app.exec_())
Related
I'm new to PyQt and handlers in general, I tried to read from every repo I found but I can't figure out how to fix my issue, as you can see in my code, I'm executing logging thread in the background and I'm trying to show the logs in my QPlainTextEdit console - for some reason I can see the logs in my terminal and the text_box doesn't get the logs at all,
I will appreciate a lot your smart help.
import pandas as pd
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import time
import os
import threading
import json
class ConsolePanelHandler(logging.Handler):
def __init__(self, stream):
# super().__init__()
logging.Handler.__init__(self)
# logging.StreamHandler.__init__(self, stream)
self.stream = stream
def handle(self, record):
rv = self.filter(record)
if rv:
self.acquire()
try:
self.emit(record)
finally:
self.release()
return rv
def emit(self, record):
try:
stream = self.stream
stream(self.format(record))
except RecursionError:
raise
except Exception:
self.handleError(self.format(record))
def thread():
for index in range(20):
logging.warning('scheiBe '+str(index))
time.sleep(1)
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
self.continue_ = QPushButton("Continue")
self.continue_.setStyleSheet("background-color: green")
self.continue_.setFont(QFont('SansSerif', 10))
self.continue_.setFixedSize(QSize(300, 22))
self.pause = QPushButton("Pause")
self.pause.setStyleSheet("background-color: orange")
self.pause.setFont(QFont('SansSerif', 10))
self.pause.setFixedSize(QSize(300, 22))
self.stop = QPushButton("Stop")
self.stop.setStyleSheet("background-color: #FD4B4B")
self.stop.setFont(QFont('SansSerif', 10))
self.stop.setFixedSize(QSize(300, 22))
self.text_box = QPlainTextEdit()
self.text_box.setPlaceholderText("Bugs will be printed here")
self.text_box.setReadOnly(True)
logging.getLogger().addHandler(self.text_box)
logging.getLogger().setLevel(logging.DEBUG)
ConsolePanelHandler(self.appendDebug , logging.DEBUG)
self.text_box.moveCursor(QTextCursor.End)
layout.addWidget(self.continue_)
layout.addWidget(self.pause)
layout.addWidget(self.stop)
layout.addWidget(self.text_box)
self.w = QWidget()
self.w.setLayout(layout)
self.setCentralWidget(self.w)
thread1 = threading.Thread(target=thread, args=(), daemon=True)
thread1.start()
self.show()
def closeEvent(self, event):
close = QMessageBox()
close.setText("Are you sure want to stop and exit?")
close.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
close = close.exec()
if close == QMessageBox.Yes:
sys.exit()
else:
event.ignore()
def appendDebug(self, string):
self.text_box.appendPlainText(string +'\n')
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
sys.exit(app.exec())
First, you should not pass the function that updates the text_area instead you should create a pyqtSignal which updates the text_area and pass it to ConsolePanelHandler.
Add ConsolePanelHandler as handler not text_area to logging.getLogger().addHandler()
and it is recommended to use QThread instead of threading.Thread
here is the complete updated code.
# import pandas as pd
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import time
import os
import sys
import threading
import json
import logging
class ConsolePanelHandler(logging.Handler):
def __init__(self, sig):
# super().__init__()
logging.Handler.__init__(self)
# logging.StreamHandler.__init__(self, stream)
self.stream = sig
def handle(self, record):
rv = self.filter(record)
if rv:
self.acquire()
try:
self.emit(record)
finally:
self.release()
return rv
def emit(self, record):
try:
self.stream.emit(self.format(record))
except RecursionError:
raise
except Exception:
self.handleError(record)
class thread(QThread):
def run(self) -> None:
for index in range(20):
logging.warning('scheiBe ' + str(index))
self.sleep(1)
class MainWindow(QMainWindow):
sig = pyqtSignal(str)
def __init__(self):
super().__init__()
self.layout = QVBoxLayout()
self.continue_ = QPushButton("Continue")
self.continue_.setStyleSheet("background-color: green")
self.continue_.setFont(QFont('SansSerif', 10))
self.continue_.setFixedSize(QSize(300, 22))
self.pause = QPushButton("Pause")
self.pause.setStyleSheet("background-color: orange")
self.pause.setFont(QFont('SansSerif', 10))
self.pause.setFixedSize(QSize(300, 22))
self.stop = QPushButton("Stop")
self.stop.setStyleSheet("background-color: #FD4B4B")
self.stop.setFont(QFont('SansSerif', 10))
self.stop.setFixedSize(QSize(300, 22))
self.c = ConsolePanelHandler(self.sig)
self.text_box = QPlainTextEdit()
self.text_box.setPlaceholderText("Bugs will be printed here")
self.text_box.setReadOnly(True)
logging.getLogger().addHandler(self.c)
logging.getLogger().setLevel(logging.DEBUG)
self.sig.connect(self.appendDebug)
self.text_box.moveCursor(QTextCursor.End)
self.layout.addWidget(self.continue_)
self.layout.addWidget(self.pause)
self.layout.addWidget(self.stop)
self.layout.addWidget(self.text_box)
self.w = QWidget()
self.w.setLayout(self.layout)
self.setCentralWidget(self.w)
self.thread1 = thread(self) # self is parent for Qthread so Qthread will be destroyed when it's parent no longer exist
self.thread1.start()
self.show()
def closeEvent(self, event):
close = QMessageBox()
close.setText("Are you sure want to stop and exit?")
close.setStandardButtons(QMessageBox.Yes | QMessageBox.Cancel)
close = close.exec()
if close == QMessageBox.Yes:
self.thread1.terminate()
sys.exit()
else:
event.ignore()
#pyqtSlot(str)
def appendDebug(self, string):
self.text_box.appendPlainText(string + '\n')
if __name__ == "__main__":
app = QApplication([])
window = MainWindow()
sys.exit(app.exec())
I'd like to indicate that my program is running and didn't freeze with a pulsing progress bar. The command that would run in the background is this:
os.system('apt install program etc...')
It'll start on button press and I'd like to show a popup progress dialog during the process.
Here's the code of the releated part:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import wx
import threading
import common as g
class myThread (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
my_thread()
print ("Exiting " + self.name)
class myThread2 (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
my_thread2()
print ("Exiting " + self.name)
def my_thread():
os.system('apt update')
def my_thread2():
dlg = wx.ProgressDialog('Test', 'Please wait..', style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT | wx.STAY_ON_TOP)
test = OtherFrame(title='progres')
counter = 1
while g.th.isAlive():
print('HEEYY')
wx.MilliSleep(300)
dlg.Pulse("Doing computation %d"%counter)
test.Show()
counter += 1
class OtherFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title, size=(700, 400))
self.Centre()
self.InitUI()
self.Show()
def InitUI(self):
gs = wx.GridSizer(1, 1, 7, 7)
update = wx.Button(self,label = 'Check for system Updates')
gs.Add(update,28,wx.EXPAND)
update.Bind(wx.EVT_BUTTON, self.OnUpdate)
self.SetSizer(gs)
def OnUpdate(self, e):
g.th = myThread(1, "Thread-1")
thread2 = myThread2(2, "Thread-2")
thread2.start()
g.th.start()
g.th.join()
thread2.join()
def main():
app = wx.App()
f1 = OtherFrame(title='frame')
f1.Show()
app.MainLoop()
if __name__ == '__main__':
main()
The text 'HEEYY' appears in the right time on the right place, but the dialog doesn't show up.
You appear to be trying to use 1 thread to determine if the other one is still running.
There is a simpler way, see below:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import os
import wx
import threading
#import common as g
class myThread (threading.Thread):
def __init__(self, threadID, name):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
def run(self):
print ("Starting " + self.name)
os.system('apt update')
print ("Exiting " + self.name)
class OtherFrame(wx.Frame):
def __init__(self, title, parent=None):
wx.Frame.__init__(self, parent=parent, title=title, size=(700, 400))
self.Centre()
self.InitUI()
self.Show()
def InitUI(self):
gs = wx.GridSizer(1, 1, 7, 7)
update = wx.Button(self,label = 'Check for system Updates')
gs.Add(update,28,wx.EXPAND)
update.Bind(wx.EVT_BUTTON, self.OnUpdate)
self.SetSizer(gs)
def OnUpdate(self, e):
t1 = myThread(1, "Thread-1")
t1.start()
counter = 0
dlg = wx.ProgressDialog('Test', 'Please wait..', style=wx.PD_APP_MODAL | wx.PD_ELAPSED_TIME | wx.PD_CAN_ABORT | wx.STAY_ON_TOP)
while t1.isAlive():
print('HEEYY')
wx.MilliSleep(300)
dlg.Pulse("Doing computation %d"%counter)
wx.GetApp().Yield()
counter += 1
del dlg
t1.join()
def main():
app = wx.App()
f1 = OtherFrame(title='frame')
f1.Show()
app.MainLoop()
if __name__ == '__main__':
main()
I'm trying the following
Create a GUI with tkinter where the user will choose a directory to watch
Close the window
Pass the path to the directory to watchdog so it can watch for file changes
How does one go about combining both scripts into one app ?
This below post has a script which does nothing when I add a *.jpg file to my temp folder (osx).
https://stackoverflow.com/a/41684432/11184726
Can someone point me towards a course or tutorial that will help me understand how to combine whats going on.
1. GUI :
from tkinter import *
from tkinter.filedialog import askopenfilename
from tkinter import filedialog
from tkinter.messagebox import showerror
globalPath = ""
class MyFrame(Frame):
def __init__(self):
Frame.__init__(self)
self.master.title("Example")
self.master.rowconfigure(5, weight=1)
self.master.columnconfigure(5, weight=1)
self.grid(sticky=W+E+N+S)
self.button = Button(self, text="Browse", command=self.load_file, width=10)
self.button.grid(row=1, column=0, sticky=W)
def load_file(self):
fname = askopenfilename(filetypes=(("Text File", "*.txt"),("All files", "*.*") ))
global globalPath
globalPath = fname
# fname = askopenfilename(filetypes=(("Text File", "*.txt"),("All files", "*.*") ))
if fname:
try:
print("""here it comes: self.settings["template"].set(fname)""")
print (fname)
except: # <- naked except is a bad idea
showerror("Open Source File", "Failed to read file\n'%s'" % fname)
return
if __name__ == "__main__":
MyFrame().mainloop() # All above code will run inside window
print(__name__)
print("the path to the file is : " + globalPath)
2. Watchdog :
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
def on_created(event):
# This function is called when a file is created
print(f"hey, {event.src_path} has been created!")
def on_deleted(event):
# This function is called when a file is deleted
print(f"what the f**k! Someone deleted {event.src_path}!")
def on_modified(event):
# This function is called when a file is modified
print(f"hey buddy, {event.src_path} has been modified")
#placeFile() #RUN THE FTP
def on_moved(event):
# This function is called when a file is moved
print(f"ok ok ok, someone moved {event.src_path} to {event.dest_path}")
if __name__ == "__main__":
# Create an event handler
patterns = "*" #watch for only these file types "*" = any file
ignore_patterns = ""
ignore_directories = False
case_sensitive = True
# Define what to do when some change occurs
my_event_handler = PatternMatchingEventHandler(patterns, ignore_patterns, ignore_directories, case_sensitive)
my_event_handler.on_created = on_created
my_event_handler.on_deleted = on_deleted
my_event_handler.on_modified = on_modified
my_event_handler.on_moved = on_moved
# Create an observer
path = "."
go_recursively = False # Will NOT scan sub directories for changes
my_observer = Observer()
my_observer.schedule(my_event_handler, path, recursive=go_recursively)
# Start the observer
my_observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
my_observer.stop()
my_observer.join()
Below is the proposed sample code to merge the two scripts (not exactly copy the two scripts into one, but showing the concept of what you request):
from tkinter import *
from tkinter import filedialog
from watchdog.observers import Observer
from watchdog.events import PatternMatchingEventHandler
class Watchdog(PatternMatchingEventHandler, Observer):
def __init__(self, path='.', patterns='*', logfunc=print):
PatternMatchingEventHandler.__init__(self, patterns)
Observer.__init__(self)
self.schedule(self, path=path, recursive=False)
self.log = logfunc
def on_created(self, event):
# This function is called when a file is created
self.log(f"hey, {event.src_path} has been created!")
def on_deleted(self, event):
# This function is called when a file is deleted
self.log(f"what the f**k! Someone deleted {event.src_path}!")
def on_modified(self, event):
# This function is called when a file is modified
self.log(f"hey buddy, {event.src_path} has been modified")
def on_moved(self, event):
# This function is called when a file is moved
self.log(f"ok ok ok, someone moved {event.src_path} to {event.dest_path}")
class GUI:
def __init__(self):
self.watchdog = None
self.watch_path = '.'
self.root = Tk()
self.messagebox = Text(width=80, height=10)
self.messagebox.pack()
frm = Frame(self.root)
Button(frm, text='Browse', command=self.select_path).pack(side=LEFT)
Button(frm, text='Start Watchdog', command=self.start_watchdog).pack(side=RIGHT)
Button(frm, text='Stop Watchdog', command=self.stop_watchdog).pack(side=RIGHT)
frm.pack(fill=X, expand=1)
self.root.mainloop()
def start_watchdog(self):
if self.watchdog is None:
self.watchdog = Watchdog(path=self.watch_path, logfunc=self.log)
self.watchdog.start()
self.log('Watchdog started')
else:
self.log('Watchdog already started')
def stop_watchdog(self):
if self.watchdog:
self.watchdog.stop()
self.watchdog = None
self.log('Watchdog stopped')
else:
self.log('Watchdog is not running')
def select_path(self):
path = filedialog.askdirectory()
if path:
self.watch_path = path
self.log(f'Selected path: {path}')
def log(self, message):
self.messagebox.insert(END, f'{message}\n')
self.messagebox.see(END)
if __name__ == '__main__':
GUI()
hello I have QTheard worker that start with click at QAction.
....
self.start_update = QAction('&Start', self)
self.start_update.triggered.connect(self._start_thread)
self.stop_update.setVisible(True)
self.stop_update.triggered.connect(self._stop_thread)
self.stop_update.setVisible(False)
...
...
def _start_thread(self):
self.start_update.setVisible(False)
self.stop_update.setVisible(True)
self.myworker.start()
...
...
def _stop_thread(self):
self.myworker.stop() # That close app with error
self.stop_update.setVisible(False)
self.start_update.setVisible(True)
...
Please help how to close terminate Worker correctly. Atm worker have __del__ and run (logic) method. Maybe need to add some method for correct worker closing?
Your application might look like this:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class AThread(QThread):
threadSignalAThread = pyqtSignal(int)
def __init__(self):
super().__init__()
def run(self):
count = 0
while count < 1000:
QThread.msleep(200)
count += 1
self.threadSignalAThread.emit(count)
class MsgBoxAThread(QDialog):
def __init__(self):
super().__init__()
layout = QVBoxLayout(self)
self.label = QLabel("")
layout.addWidget(self.label)
close_btn = QPushButton("Close window")
layout.addWidget(close_btn)
close_btn.clicked.connect(self.close)
self.setGeometry(900, 300, 400, 80)
self.setWindowTitle('MsgBox AThread(QThread)')
class Example(QMainWindow):
def __init__(self, parent=None, *args):
super().__init__(parent, *args)
self.setWindowTitle("Pyqt5 stop QThread worker on QAction")
self.setGeometry(550, 300, 300, 300)
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
layout = QVBoxLayout(centralWidget)
self.lbl = QLabel("Start")
layout.addWidget(self.lbl)
bar = self.menuBar()
barThread = bar.addMenu('Thread')
quit = bar.addMenu('Quit')
quit.aboutToShow.connect(app.quit)
self.start_update = QAction('&Start', self)
self.start_update.setShortcut('Ctrl+S')
self.start_update.triggered.connect(self._start_thread)
self.stop_update = QAction('Sto&p', self)
self.stop_update.setShortcut('Ctrl+P')
self.stop_update.setVisible(False)
self.stop_update.triggered.connect(self._stop_thread)
barThread.addAction(self.start_update)
barThread.addAction(self.stop_update)
self.msg = MsgBoxAThread()
self.myworker = None
self.counter = 0
self.timer = QTimer()
self.timer.setInterval(1000)
self.timer.timeout.connect(self.recurring_timer)
self.timer.start()
self.show()
def recurring_timer(self):
self.counter += 1
self.lbl.setText(" Do something in the GUI: <b> %d </b>" % self.counter)
def _start_thread(self):
self.start_update.setVisible(False)
self.stop_update.setVisible(True)
self.myworker = AThread()
self.myworker.threadSignalAThread.connect(self.on_threadSignalAThread)
self.myworker.finished.connect(self.finishedAThread)
self.myworker.start()
def finishedAThread(self):
self.myworker = None
self.start_update.setVisible(True)
self.stop_update.setVisible(False)
def on_threadSignalAThread(self, value):
self.msg.label.setText(str(value))
if not self.msg.isVisible():
self.msg.show()
def _stop_thread(self):
self.stop_update.setVisible(False)
self.start_update.setVisible(True)
self.myworker.terminate()
self.myworker = None
def closeEvent(self, event):
reply = QMessageBox.question(self, 'Question',
"Are you sure you want to close the application?",
QMessageBox.Yes,
QMessageBox.No)
if reply == QMessageBox.Yes:
if self.myworker:
self.myworker.quit()
del self.myworker
self.msg.close()
super(Example, self).closeEvent(event)
else:
event.ignore()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Example()
#ex.show()
sys.exit(app.exec_())
I'm having an issue when I try to subclass my worker class the main UI thread locks up and becomes unresponsive until the Worker Thread has finished. I'm at a loss here and suspect something with how the GIL interacts with inheritance?
In MainWindow.clicked() these two lines:
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
Here is my sample code that reproduces this:
from PySide import QtGui, QtCore
import sys
import time
class Worker(QtCore.QObject):
started = QtCore.Signal()
progress = QtCore.Signal(int)
finished = QtCore.Signal(bool)
def __init__(self, parent=None):
QtCore.QObject.__init__(self, parent)
self.lock = QtCore.QMutex()
#QtCore.Slot()
def start(self):
self.flag_to_quit = False
try:
self.started.emit()
time.sleep(5)
time.sleep(0.5)
for i in range(10):
self.progress.emit(i)
time.sleep(1)
with QtCore.QMutexLocker(self.lock):
print(self.flag_to_quit)
if self.flag_to_quit:
raise RuntimeError("Requested to quit.")
self.finished.emit(True)
except Exception as ex:
print(ex)
self.finished.emit(False)
finally:
print("start() finished!")
##QtCore.Slot()
def stop(self):
print("Stopping...")
with QtCore.QMutexLocker(self.lock):
self.flag_to_quit = True
#QtCore.Slot()
def __quit_handler(self):
self.flag_to_quit = True
class SubWorker(Worker):
def __init__(self):
Worker.__init__(self)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.label = QtGui.QLabel("")
self.layout().addWidget(self.label)
self.button = QtGui.QPushButton("Start Thread")
self.layout().addWidget(self.button)
self.button.clicked.connect(self.clicked)
def closeEvent(self, event):
if hasattr(self, "_thread") and self._thread:
self.worker.flag_to_quit = True
self._thread.quit()
print("waiting for stop...")
self._thread.wait()
self._thread.deleteLater()
#QtCore.Slot()
def worker_started(self):
self.label.setText("Started!")
#QtCore.Slot(int)
def worker_progress(self, interval):
self.label.setText(str(interval))
#QtCore.Slot()
def worker_finished(self, success):
msg = "Finished "
msg += "successfully!" if success else "with errors"
self.label.setText(msg)
#QtCore.Slot()
def clicked(self):
if hasattr(self, "_thread") and self._thread:
self.worker.stop()
self._thread.quit()
print("waiting for stop...")
self._thread.wait()
self._thread.quit()
self._thread.deleteLater()
self._thread = QtCore.QThread()
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
self.worker.started.connect (self.worker_started, QtCore.Qt.QueuedConnection)
self.worker.progress.connect(self.worker_progress, QtCore.Qt.QueuedConnection)
self.worker.finished.connect(self.worker_finished, QtCore.Qt.QueuedConnection)
self.worker.moveToThread(self._thread)
self._thread.started.connect(self.worker.start)
self._thread.finished.connect(self.worker.stop)
self._thread.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
sys.exit()
Version information:
Python 3.4.4 (v3.4.4:737efcadf5a6, Dec 20 2015, 19:28:18) [MSC v.1600 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
edit:
Looks like QThread::moveToThread() is failing with Subclasses. this is output of QtCore.QThread.currentThread() in various places:
#self.worker = Worker():
Main:<PySide.QtCore.QThread object at 0x7ff89df384d0>
Real thread: <PySide.QtCore.QThread object at 0x7ff89df383f8>
Worker:<PySide.QtCore.QThread object at 0x7ff89df384d0>
#self.worker = SubWorker()
Main:<PySide.QtCore.QThread object at 0x7f968602c3b0>
Real thread: <PySide.QtCore.QThread object at 0x7f968602c488>
Worker:<PySide.QtCore.QThread object at 0x7f968602c488>
Okay I managed to fix this by doing it the "Wrong" way by inheriting QThread and overriding the run method. I'm not sure why or how but it works.
from PySide import QtGui, QtCore
import sys
import time
class Worker(QtCore.QThread):
_started = QtCore.Signal()
progress = QtCore.Signal(int)
_finished = QtCore.Signal(bool)
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
self.lock = QtCore.QMutex()
#QtCore.Slot()
def run(self):
self.flag_to_quit = False
print("Worker:" + str(QtCore.QThread.currentThread()))
try:
self._started.emit()
time.sleep(2)
for i in range(10):
self.progress.emit(i)
time.sleep(1)
with QtCore.QMutexLocker(self.lock):
if self.flag_to_quit:
raise RuntimeError("Requested to quit.")
self._finished.emit(True)
except Exception as ex:
print(ex)
self._finished.emit(False)
finally:
print("start() finished!")
#QtCore.Slot()
def stop(self):
with QtCore.QMutexLocker(self.lock):
self.flag_to_quit = True
#QtCore.Slot()
def __quit_handler(self):
self.flag_to_quit = True
class SubWorker(Worker):
def __init__(self, parent=None):
Worker.__init__(self, parent)
#QtCore.Slot()
def start(self):
Worker.start(self)
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setLayout(QtGui.QVBoxLayout())
self.label = QtGui.QLabel("")
self.layout().addWidget(self.label)
self.button = QtGui.QPushButton("Start Thread")
self.layout().addWidget(self.button)
self.button.clicked.connect(self.clicked)
def closeEvent(self, event):
self.hide()
if hasattr(self, "worker") and self.worker:
self.worker.stop()
self.worker.quit()
self.worker.wait()
self.worker.deleteLater()
#QtCore.Slot()
def worker_started(self):
self.label.setText("Started!")
#QtCore.Slot(int)
def worker_progress(self, interval):
self.label.setText(str(interval))
#QtCore.Slot()
def worker_finished(self, success):
msg = "Finished "
msg += "successfully!" if success else "with errors!"
self.label.setText(msg)
def clicked(self):
if hasattr(self, "worker") and self.worker:
self.worker.stop()
self.worker.quit()
self.worker.wait()
self.worker.deleteLater()
print("Main:" + str(QtCore.QThread.currentThread()))
#self.worker = Worker() # This works
self.worker = SubWorker() # This locks up main thread
self.worker._started.connect (self.worker_started)
self.worker.progress.connect(self.worker_progress)
self.worker._finished.connect(self.worker_finished)
self.worker.start()
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
main_window = MainWindow()
main_window.show()
app.exec_()
sys.exit()