combining tkinter and watchdog - python-3.x

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

Related

TKinter Widget creation... Issue with arrangement of widgets

I was trying to create some nested widgets within file widget however exit is appearing above open. I tried changing the index but still the problems persists any idea how to fix this?
#!/usr/bin/python3
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import messagebox
import sys
class GuiBaseClass():
def __init__(self,root):
# create widgets
self.root=root
root.title('GUIBASECLASS V0.1')
root.option_add('*tearOff', False)
self.menu=dict()
self.menubar = tk.Menu(root)
#File
menu_file = tk.Menu(self.menubar)
self.menubar.add_cascade(menu=menu_file,label='File',underline=0)
menu_file.add_separator()
menu_file.add_command(label='Exit', command=self.Exit,underline=0)
#Edit
menu_edit = tk.Menu(self.menubar)
self.menubar.add_cascade(menu=menu_edit,label='Edit',underline=0)
#Help
menu_help = tk.Menu(self.menubar)
menu_help.add_command(label='About', command=self.About, underline=0)
self.menubar.add_cascade(menu=menu_help,label ='Help',underline=0)
#config
root.config(menu=self.menubar)
self.menu['menubar'] = self.menubar
self.menu['File'] = menu_file
self.menu['Edit'] = menu_edit
self.menu['Help'] = menu_help
self.frame = ttk.Frame(root)
self.frame.pack(fill='both',expand=True)
# public functions
def mainLoop(self):
self.root.mainloop()
def getFrame(self):
return(self.frame)
def getMenu(self,entry):
if entry in self.menu:
return (self.menu[entry])
else:
# we create a new one
last = self.menu['menubar'].index('end')
self.menu[entry]= tk.Menu(self.menubar)
self.menu['menubar'].insert_cascade(
last, menu=self.menu[entry],label=entry)
return(self.menu[entry])
# private functions
def Exit(self,ask=True):
res = tk.messagebox.askyesno(title='Are you sure',message='Really quit the application')
if res:
sys.exit(0)
pass
def About(self):
print('print GUIBASECLASS V0.1')
if __name__ == '__main__':
root=tk.Tk()
bapp = GuiBaseClass(root)
# example for using the BaseClass in other applications
mnu=bapp.getMenu('Edit')
mnu.add_command(label='Copy',command=lambda: print('Copy'))
# example for using getFrame
frm=bapp.getFrame()
btn=ttk.Button(frm,text="Button X",command=lambda: sys.exit(0))
btn.pack()
bapp.mainLoop()
#!/usr/bin/python3
import tkinter as tk
import tkinter.ttk as ttk
from tkinter import filedialog
from tkinter import messagebox
from GuiBaseClass import GuiBaseClass
import os , sys
class PumlEditor(GuiBaseClass):
def __init__(self, root):
super().__init__(root)
# Create the File menu and add a "Open..." option
mnu_file = self.getMenu('File')
mnu_file.add_command(0, label='Open...', underline=0, command=self.fileOpen)
mnu_file.add_separator()
# Create the main text area
frame = self.getFrame()
#insert tk.Text
self.text = tk.Text(frame, wrap='word', undo=True)
self.text.insert('end', 'Hello start writing, please...')
self.text.delete('1.0','end')
self.text.pack(fill='both', expand=True)
def fileOpen(self, filename=''):
# Open a file dialog to select a file to open
if filename == "":
filename = filedialog.askopenfilename()
if filename != "":
self.text.delete('1.0','end')
file = open(filename,'r')
for line in file:
self.text.insert('end',line)
if __name__ == '__main__':
root = tk.Tk()
root.geometry("300x200")
pedit = PumlEditor(root)
# Create the PumlEditor instance and start the main loop
root.title("PumlEditor 2022")
if len(sys.argv)>1:
if os.path.exits(sys.arg[1]):
pedit.fileOpen(sys.argv[1])
pedit.mainLoop()
Open to appear before exit in files widget

PyQt5 unable to stop/kill/exit from QThread

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

Tkinter buttons not changing back to the correct color after state changing to active

I am making this PDF tool, and I want the buttons to be disabled until a file or files are successfully imported. This is what the app looks like at the launch:
Right after running the callback for the import files button, the active state looks like this:
I want the colors of the buttons to turn maroon instead of the original grey. They only turn back to maroon once you hover the mouse over them. Any thoughts for how to fix this? Here is the callback for the import button:
def import_callback():
no_files_selected = False
global files
files = []
try:
ocr_button['state'] = DISABLED
merge_button['state'] = DISABLED
status_label.pack_forget()
frame.pack_forget()
files = filedialog.askopenfilenames()
for f in files:
name, extension = os.path.splitext(f)
if extension != '.pdf':
raise
if not files:
no_files_selected = True
raise
if frame.winfo_children():
for label in frame.winfo_children():
label.destroy()
make_import_file_labels(files)
frame.pack()
ocr_button['state'] = ACTIVE
merge_button['state'] = ACTIVE
except:
if no_files_selected:
status_label.config(text='No files selected.', fg='blue')
else:
status_label.config(text='Error: One or more files is not a PDF.', fg='red')
status_label.pack(expand='yes')
import_button = Button(root, text='Import Files', width=scaled(20), bg='#5D1725', bd=0, fg='white', relief='groove',
command=import_callback)
import_button.pack(pady=scaled(50))
I know this was asked quite a while ago, so probably already solved for the user. But since I had the exact same problem and do not see the "simplest" answer here, I thought I would post:
Just change the state from "active" to "normal"
ocr_button['state'] = NORMAL
merge_button['state'] = NORMAL
I hope this helps future users!
As I understand you right you want something like:
...
ocr_button['state'] = DISABLED
ocr_button['background'] = '#*disabled background*'
ocr_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
ocr_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
merge_button['state'] = DISABLED
merge_button['background'] = '#*disabled background*'
merge_button.bind('<Enter>', lambda e:ocr_button.configure(background='#...'))
merge_button.bind('<Leave>', lambda e:ocr_button.configure(background='#...'))
...
...
ocr_button['state'] = ACTIVE
ocr_button['background'] = '#*active background*'
ocr_button.unbind('<Enter>')
ocr_button.unbind('<Leave>')
merge_button['state'] = ACTIVE
merge_button['background'] = '#*active background*'
merge_button.unbind('<Enter>')
merge_button.unbind('<Leave>')
...
If there are any errors, since I wrote it out of my mind or something isnt clear, let me know.
Update
the following code reproduces the behavior as you stated. The reason why this happens is how tkinter designed the standart behavior. You will have a better understanding of it if you consider style of ttk widgets. So I would recommand to dont use the automatically design by state rather write a few lines of code to configure your buttons how you like, add and delete the commands and change the background how you like. If you dont want to write this few lines you would be forced to use ttk.Button and map a behavior you do like
import tkinter as tk
root = tk.Tk()
def func_b1():
print('func of b1 is running')
def disable_b1():
b1.configure(bg='grey', command='')
def activate_b1():
b1.configure(bg='red', command=func_b1)
b1 = tk.Button(root,text='B1', bg='red',command=func_b1)
b2 = tk.Button(root,text='disable', command=disable_b1)
b3 = tk.Button(root,text='activate',command=activate_b1)
b1.pack()
b2.pack()
b3.pack()
root.mainloop()
I've wrote this simple app that I think could help all to reproduce the problem.
Notice that the state of the button when you click is Active.
#!/usr/bin/python3
import sys
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox
class Main(ttk.Frame):
def __init__(self, parent, *args, **kwargs):
super().__init__()
self.parent = parent
self.init_ui()
def cols_configure(self, w):
w.columnconfigure(0, weight=0, minsize=100)
w.columnconfigure(1, weight=0)
w.rowconfigure(0, weight=0, minsize=50)
w.rowconfigure(1, weight=0,)
def get_init_ui(self, container):
w = ttk.Frame(container, padding=5)
self.cols_configure(w)
w.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)
return w
def init_ui(self):
w = self.get_init_ui(self.parent)
r = 0
c = 0
b = ttk.LabelFrame(self.parent, text="", relief=tk.GROOVE, padding=5)
self.btn_import = tk.Button(b,
text="Import Files",
underline=1,
command = self.on_import,
bg='#5D1725',
bd=0,
fg='white')
self.btn_import.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
self.parent.bind("<Alt-i>", self.switch)
r +=1
self.btn_ocr = tk.Button(b,
text="OCR FIles",
underline=0,
command = self.on_ocr,
bg='#5D1725',
bd=0,
fg='white')
self.btn_ocr["state"] = tk.DISABLED
self.btn_ocr.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_merge = tk.Button(b,
text="Merge Files",
underline=0,
command = self.on_merge,
bg='#5D1725',
bd=0,
fg='white')
self.btn_merge["state"] = tk.DISABLED
self.btn_merge.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
r +=1
self.btn_reset = tk.Button(b,
text="Reset",
underline=0,
command = self.switch,
bg='#5D1725',
bd=0,
fg='white')
self.btn_reset.grid(row=r, column=c, sticky=tk.N+tk.W+tk.E,padx=5, pady=5)
b.grid(row=0, column=1, sticky=tk.N+tk.W+tk.S+tk.E)
def on_import(self, evt=None):
self.switch()
#simulate some import
self.after(5000, self.switch())
def switch(self,):
state = self.btn_import["state"]
if state == tk.ACTIVE:
self.btn_import["state"] = tk.DISABLED
self.btn_ocr["state"] = tk.NORMAL
self.btn_merge["state"] = tk.NORMAL
else:
self.btn_import["state"] = tk.NORMAL
self.btn_ocr["state"] = tk.DISABLED
self.btn_merge["state"] = tk.DISABLED
def on_ocr(self, evt=None):
state = self.btn_ocr["state"]
print ("ocr button state is {0}".format(state))
def on_merge(self, evt=None):
state = self.btn_merge["state"]
print ("merge button state is {0}".format(state))
def on_close(self, evt=None):
self.parent.on_exit()
class App(tk.Tk):
"""Main Application start here"""
def __init__(self, *args, **kwargs):
super().__init__()
self.protocol("WM_DELETE_WINDOW", self.on_exit)
self.set_style()
self.set_title(kwargs['title'])
Main(self, *args, **kwargs)
def set_style(self):
self.style = ttk.Style()
#('winnative', 'clam', 'alt', 'default', 'classic', 'vista', 'xpnative')
self.style.theme_use("clam")
def set_title(self, title):
s = "{0}".format('Simple App')
self.title(s)
def on_exit(self):
"""Close all"""
if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
self.destroy()
def main():
args = []
for i in sys.argv:
args.append(i)
kwargs = {"style":"clam", "title":"Simple App",}
app = App(*args, **kwargs)
app.mainloop()
if __name__ == '__main__':
main()

Sending a signal from a button to a parent window

I'm making an app with PyQt5 that uses a FileDialog to get files from the user and saves the file names in a list. However I also want to be able to remove those entries after they are added, so I created a class that has a name(the file name) and a button. The idea is that when this button is clicked the widget disappears and the file entry is removed from the list. The disappearing part works fine but how to I get the widget to remove the entry form the list? How can i send a signal from one widget inside the window to the main app and tell it to remove the entry from the list?
I know the code is very bad, I'm still very new to PyQt and Python in general so any advice would be greatly appreciated.
from PyQt5 import QtWidgets as qw
import sys
class MainWindow(qw.QMainWindow):
def __init__(self):
super().__init__()
# List of opened files
self.files = []
# Main Window layout
self.layout = qw.QVBoxLayout()
self.file_display = qw.QStackedWidget()
self.file_button = qw.QPushButton('Add File')
self.file_button.clicked.connect(self.add_file)
self.layout.addWidget(self.file_display)
self.layout.addWidget(self.file_button)
self.setCentralWidget(qw.QWidget())
self.centralWidget().setLayout(self.layout)
# Open File Dialog and append file name to list
def add_file(self):
file_dialog = qw.QFileDialog()
self.files.append(file_dialog.getOpenFileName())
self.update_stack()
# Create new widget for StackedWidget remove the old one and display the new
def update_stack(self):
new_stack_item = qw.QWidget()
layout = qw.QVBoxLayout()
for file in self.files:
layout.addWidget(FileWidget(file[0]))
new_stack_item.setLayout(layout)
if len(self.file_display) > 0:
temp_widget = self.file_display.currentWidget()
self.file_display.removeWidget(temp_widget)
self.file_display.addWidget(new_stack_item)
class FileWidget(qw.QWidget):
def __init__(self, name):
# This widget is what is added when a new file is opened
# it has a file name and a close button
# my idea is that when the close button is pressed the widget is removed
# from the window and from the files[] list in the main class
super().__init__()
self.layout = qw.QHBoxLayout()
self.file_name = qw.QLabel(name)
self.close_button = qw.QPushButton()
self.close_button.clicked.connect(self.remove)
self.layout.addWidget(self.file_name)
self.layout.addWidget(self.close_button)
self.setLayout(self.layout)
def remove(self):
self.close()
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
You need to remove from the list in the remove method of theFileWidget class.
import sys
from PyQt5 import QtWidgets as qw
class FileWidget(qw.QWidget):
def __init__(self, name, files): # + files
super().__init__()
self.files = files # +
self.name = name # +
self.layout = qw.QHBoxLayout()
self.file_name = qw.QLabel(name)
self.close_button = qw.QPushButton("close {}".format(name))
self.close_button.clicked.connect(self.remove)
self.layout.addWidget(self.file_name)
self.layout.addWidget(self.close_button)
self.setLayout(self.layout)
def remove(self):
self.files.pop(self.files.index(self.name)) # <<<-----<
self.close()
class MainWindow(qw.QMainWindow):
def __init__(self):
super().__init__()
# List of opened files
self.files = []
# Main Window layout
self.layout = qw.QVBoxLayout()
self.file_display = qw.QStackedWidget()
self.file_button = qw.QPushButton('Add File')
self.file_button.clicked.connect(self.add_file)
self.layout.addWidget(self.file_display)
self.layout.addWidget(self.file_button)
self.setCentralWidget(qw.QWidget())
self.centralWidget().setLayout(self.layout)
# Open File Dialog and append file name to list
def add_file(self):
file_name, _ = qw.QFileDialog().getOpenFileName(self, 'Open File') # +
if file_name: # +
self.files.append(file_name) # +
self.update_stack()
# Create new widget for StackedWidget remove the old one and display the new
def update_stack(self):
new_stack_item = qw.QWidget()
layout = qw.QVBoxLayout()
for file in self.files:
layout.addWidget(FileWidget(file, self.files)) # + self.files
new_stack_item.setLayout(layout)
if len(self.file_display) > 0:
temp_widget = self.file_display.currentWidget()
self.file_display.removeWidget(temp_widget)
self.file_display.addWidget(new_stack_item)
if __name__ == '__main__':
app = qw.QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())

How to pass variables from one QWizardPage to main QWizard

I am trying to figure out how to pass variables from (e.g.: an openFile function) inside a QWizardPage class to the main QWizard class. I have also read about signals and slots but can't understand how and if this is the ideal way to do it when using PyQt5.
Here follows a simplified example of the code:
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.closeEvent)
def closeEvent(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QPushButton("Import Edge List")
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
def openFileNameDialog(self, parent):
options = QFileDialog.Options()
options |= QFileDialog.DontUseNativeDialog
fileName, _ = QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.parent.variable = fileName
if you want to access QWizard from QWizardPage you must use the wizard() method, on the other hand closeEvent() is an event that is triggered when the window is closed that should not be invoked by an additional signal that is not necessary, the correct thing is to create a slot that connects to the finished signal.
from PyQt5 import QtCore, QtWidgets
class ImportWizard(QtWidgets.QWizard):
def __init__(self, parent=None):
super(ImportWizard, self).__init__(parent)
self.addPage(Page1(self))
self.setWindowTitle("Import Wizard")
# Trigger close event when pressing Finish button to redirect variables to backend
self.finished.connect(self.onFinished)
#QtCore.pyqtSlot()
def onFinished(self):
print("Finish")
# Return variables to use in main
print(self.variable)
class Page1(QtWidgets.QWizardPage):
def __init__(self, parent=None):
super(Page1, self).__init__(parent)
self.openFileBtn = QtWidgets.QPushButton("Import Edge List")
self.comboBox = QtWidgets.QComboBox()
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.comboBox)
layout.addWidget(self.openFileBtn)
self.setLayout(layout)
self.openFileBtn.clicked.connect(self.openFileNameDialog)
#QtCore.pyqtSlot()
def openFileNameDialog(self):
options = QtWidgets.QFileDialog.Options()
options |= QtWidgets.QFileDialog.DontUseNativeDialog
fileName, _ = QtWidgets.QFileDialog.getOpenFileName(
self, "QFileDialog.getOpenFileName()", "",
"All Files (*);;Python Files (*.py)", options=options)
# if user selected a file store its path to a variable
if fileName:
self.wizard().variable = fileName
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = ImportWizard()
w.show()
sys.exit(app.exec_())

Resources