We have a Pyxll app (Excel app written in python) that makes a bunch of requests to get data when the workbook is opened. We would like to display a loading bar to the user while the requests are being made and update the loading bar after each request returns.
I'm trying to use Tkinter to do this, but have run into issues. I can get a progress bar to pop up, but it blocks Excel from running until you close the window for the progress bar. I can't put it in a different thread because I want to be able to update the progress based on when the HTTP requests return. Is there an easy way to do this?
Here is my code so far. I have made a basic loading bar class:
import Tkinter as tk
import ttk
class OrderingProgressBar(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
# progress goes from 0-100 (for percentage)
self.progress = 0
self.max_progress = 100
self.progress_bar = ttk.Progressbar(self, orient="horizontal", length=200, mode="determinate", maximum=self.max_progress)
self.progress_bar.pack()
And then I have a macro that gets called to launch the app and start making requests.
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
ordering_progress_bar.mainloop()
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
With this code, when I call the macro a loading bar pops up, but block Excel. If I close the window it continues. If I move the mainloop call to the end of launch_ordering_terminal, then the entire terminal loads, and then it pops up the loading bar. I can't get them to work at the same time.
Thanks in advance for the help!
Maintaining a GUI is a full time job that requires an endless loop, called the "mainloop". If you want to do something else in the meantime, you need to add it to the mainloop, or do it in a different thread. In your case I think a new thread would be easiest. Tkinter runs best in the main thread, so put your code in a child thread.
As a totally untested guess:
from threading import Thread
def do_stuff(ordering_progress_bar):
excel_utils.turn_off_excel_updates(xl_app())
launch_allocation_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_si_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_accounting_app(manager_id)
ordering_progress_bar.progress_bar.step(25)
launch_reports_builder(terminal_mode)
ordering_progress_bar.progress_bar.step(25)
excel_utils.turn_on_excel_updates(xl_app())
ordering_progress_bar.quit() # kill the GUI
def launch_ordering_terminal(ribbon):
"""Launch all of the apps required for the Ordering Terminal"""
ordering_progress_bar = OrderingProgressBar()
t = Thread(target=do_stuff, args=(ordering_progress_bar,))
t.start()
ordering_progress_bar.mainloop()
Related
I am developing an app in Kivy and have one function that seems to take a long time to finish. Therefore, when the button that calls this function is pressed, i first open a modalview with a progress bar. I now want to update the value in the progressbar every 500ms while the main function is executing
My first attempt was to use Clock.schedule_interval(). Pseudo code looks something like this:
Class MainClass(Screen):
def button_callback_function(self):
#open modalview with progress bar
view = ModalView(autodismiss=True)
view.open()
Clock.schedule_interval(self.update_progress_bar_in_modalview,0.5)
self.function_that_takes_too_long()
def function_that_takes_too_long(self):
/* code here*/
def update_progress_bar_in_modalview(self,dt):
/*updates value of progress bar*/
With this code, the progress bar does indeed get updated but only after function_that_takes_too_long() finishes, not parallel to it.
My second attempt was to use python threads:
Class MainClass(Screen):
def button_callback_function(self):
#open modalview with progress bar
view = ModalView(autodismiss=True)
view.open()
x=threading.Thread(target=self.update_progress_bar_in_modalview)
x.start()
self.function_that_takes_too_long()
def function_that_takes_too_long(self):
/* code here*/
def update_progress_bar_in_modalview(self):
/*updates value of progress bar*/
timer.sleep(0.5)
Here the second thread to update the progress bar is never even started. It seems the main thread has to pause to give the second thread a chance to start.
So is there any way to call update_progress_bar_in_modalview() while function_that_takes_too_long() is still executing? Something like periodic interrupts in micro controllers. Or maybe start the thread that updates the progress bar on a separate core?
I appreciate any hints.
Alex
I have built a Python tkinter GUI application which is an application for running different tasks. The application window is divided into 2 halves horizontally, first half shows the options the user can choose for the selected menu option and second half shows the progress of the task by showing the log messages. Each task has a separate menu option, the user selects the menu option and first half is refreshed with user option along with a Submit button.
The GUI is built using the object oriented method where each task in the menu option is an class method of the GUI object.
I now have about 5-6 menu options and working fine but the code size is becoming huge and it is becoming hard to debug any issue or add new features.
Is there any way to write the method of a class in separate file which can be called from within the main class. The logging of messages in the GUI is written in the main class so if the method is written in a separate file the how will the log messages written in the other file appear in the main window.
Please suggest alternatives.
This might not help you completely, but this is what I use. I divide my tkinter code into 2 files. First gui.py contains the GUI components (widgets) and the second methods.py contains the methods.
Both the files should be in same directory.
Here is an example of a simple app that changes the label on a button click. The method change() is stored in a different file.
gui.py
from tkinter import *
from tkinter import ttk
from methods import change #Using absolute import instead of wildcard imports
class ClassNameGoesHere:
def __init__(self,app):
self.testbtn = ttk.Button(app,text="Test",command = lambda: change(self))
#calling the change method.
self.testbtn.grid(row=0,column=0,padx=10,pady=10)
self.testlabel = ttk.Label(app,text="Before Button Click")
self.testlabel.grid(row=1,column=0,padx=10,pady=10)
def main():
root = Tk()
root.title("Title Goes Here")
obj = ClassNameGoesHere(root)
root.mainloop()
if __name__ == "__main__":
main()
methods.py
from tkinter import *
from tkinter import ttk
def change(self):
self.testlabel.config(text="After Button Click")
How can I architect code to run a pyqt GUI multiple times consecutively in a process?
(pyqtgraph specifically, if that is relevant)
The context
A python script that performs long running data capture on measurement equipment (a big for loop). During each capture iteration a new GUI appear and displays live data from the measurement equipment to the user, while the main capture code is running.
I'd like to do something like this:
for setting in settings:
measurement_equipment.start(setting)
gui = LiveDataStreamGUI(measurement_equipment)
gui.display()
measurement_equipment.capture_data(300) #may take hours
gui.close()
The main issue
I'd like the data capture code to be the main thread. However pyqt doesn't seems to allow this architecture, as its app.exec_() is a blocking call, allowing a GUI to be created only once per process (e.g., in gui.display() above).
An application is an executable process that runs on one or more foreground threads each of which can also start background threads to perform parallel operations or operations without blocking the calling thread. An application will terminate after all foreground threads have ended, therefore, you need at least one foreground thread which in your case is created when you call the app.exec_() statement. In a GUI application, this is the UI thread where you should create and display the main window and any other UI widget. Qt will automatically terminate your application process when all widgets are closed.
IMHO, you should try to follow the normal flow described above as much as possible, the workflow could be as follows:
Start Application > Create main window > Start a background thread for each calculation > Send progress to UI thread > Show results in a window after each calculation is finished > Close all windows > End application
Also, you should use ThreadPool to make sure you don't run out of resources.
Here is a complete example:
import sys
import time
import PyQt5
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtCore import QRunnable, pyqtSignal, QObject
from PyQt5.QtWidgets import QApplication, QMainWindow, QWidget, QDialog
class CaptureDataTaskStatus(QObject):
progress = pyqtSignal(int, int) # This signal is used to report progress to the UI thread.
captureDataFinished = pyqtSignal(dict) # Assuming your result is a dict, this can be a class, a number, etc..
class CaptureDataTask(QRunnable):
def __init__(self, num_measurements):
super().__init__()
self.num_measurements = num_measurements
self.status = CaptureDataTaskStatus()
def run(self):
for i in range(0, self.num_measurements):
# Report progress
self.status.progress.emit(i + 1, self.num_measurements)
# Make your equipment measurement here
time.sleep(0.1) # Wait for some time to mimic a long action
# At the end you will have a result, for example
result = {'a': 1, 'b': 2, 'c': 3}
# Send it to the UI thread
self.status.captureDataFinished.emit(result)
class ResultWindow(QWidget):
def __init__(self, result):
super().__init__()
# Display your result using widgets...
self.result = result
# For this example I will just print the dict values to the console
print('a: {}'.format(result['a']))
print('b: {}'.format(result['b']))
print('c: {}'.format(result['c']))
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.result_windows = []
self.thread_pool = QtCore.QThreadPool().globalInstance()
# Change the following to suit your needs (I just put 1 here so you can see each task opening a window while the others are still running)
self.thread_pool.setMaxThreadCount(1)
# You could also start by clicking a button, menu, etc..
self.start_capturing_data()
def start_capturing_data(self):
# Here you start data capture tasks as needed (I just start 3 as an example)
for setting in range(0, 3):
capture_data_task = CaptureDataTask(300)
capture_data_task.status.progress.connect(self.capture_data_progress)
capture_data_task.status.captureDataFinished.connect(self.capture_data_finished)
self.thread_pool.globalInstance().start(capture_data_task)
def capture_data_progress(self, current, total):
# Update progress bar, label etc... for this example I will just print them to the console
print('Current: {}'.format(current))
print('Total: {}'.format(total))
def capture_data_finished(self, result):
result_window = ResultWindow(result)
self.result_windows.append(result_window)
result_window.show()
class App(QApplication):
"""Main application wrapper, loads and shows the main window"""
def __init__(self, sys_argv):
super().__init__(sys_argv)
self.main_window = MainWindow()
self.main_window.show()
if __name__ == '__main__':
app = App(sys.argv)
sys.exit(app.exec_())
If you want your GUI to keep updating in realtime and to not be freezed, you have two main ways to do it:
Refresh the GUI from time to time calling QApplication.processEvents() inside your time consuming function.
Create a separate thread (I mean, QThread) where you run your time consuming function
My personal preference is to go for the latter way. Here is a good tutorial for getting started on how to do multi-threading in Qt.
Having a look at your code:
...
gui.display()
measurement_equipment.capture_data(300) #may take hours
gui.close()
...
it seems you are calling app.exec_ inside gui.display. Its very likely you will have to decouple both functions and call app.exec_ outside of gui.display and after calling capture_data. You will also have to connect the finished signal of the new thread to gui.close. It will be something like this:
...
gui.display() # dont call app.exec_ here
thread = QThread.create(measurement_equipment.capture_data, 300)
thread.finished.connect(gui.close)
app.exec_()
...
I hope this can help you and to not be late!!
You can have only One graphic GUI thread. This would imply to have some Threads to capture data and sync data with the graphic application when needed.
We need to know if the GUI data display is displaying realtime data or only oneshot.
I have a program I've been writing that began as a helper function for me to find a certain report on a shared drive based on some information in that report. I decided to give it a GUI so I can distribute it to other employees, and have ran into several errors on my first attempt to implement tkinter and threading.
I'm aware of the old adage "I had one problem, then I used threads, now I have two problems." The thread did, at least, solve the first problem -- so now on to the second....
My watered down code is:
class GetReport(threading.Thread):
def __init__(self,root):
threading.Thread.__init__(self)
# this is just a hack to get the StringVar in the new thread, HELP!
self.date = root.getvar('date')
self.store = root.getvar('store')
self.report = root.getvar('report')
# this is just a hack to get the StringVar in the new thread, HELP!
self.top = Toplevel(root)
ttk.Label(self.top,text="Fooing the Bars into Bazes").pack()
self.top.withdraw()
def run(self):
self.top.deiconify()
# a function call that takes a long time
self.top.destroy() #this crashes the program
def main():
root = Tk()
date,store,report = StringVar(),StringVar(),StringVar()
#####
## labels and Entries go here that define and modify those StringVar
#####
def launchThread(rpt):
report.set(rpt)
# this is just a hack to get the StringVar in the new thread, HELP!
root.setvar('date',date.get())
root.setvar('store',store.get())
root.setvar('report',report.get())
# this is just a hack to get the StringVar in the new thread, HELP!
reportgetter = GetReport(root)
reportgetter.start()
ttk.Button(root,text="Lottery Summary",
command=lambda: launchThread('L')).grid(row=1,column=3)
root.mainloop()
My expected output is for root to open and populate with Labels, Entries, and Buttons (some of which are hidden in this example). Each button will pull data from the Entries and send them to the launchThread function, which will create a new thread to perform the foos and the bars needed to grab the paperwork I need.
That thread will launch a Toplevel basically just informing the user that it's working on it. When it's done, the Toplevel will close and the paperwork I requested will open (I'm using ShellExecute to open a .pdf) while the Thread exits (since it exits its run function)
What's ACTUALLY happening is that the thread will launch its Toplevel, the paperwork will open, then Python will become non-responsive and need to be "end processed".
As far as I know you cannot use Threading to alter any GUI elements. Such as destroying a Toplevel window.
Any Tkinter code needs to be done in the main loop of your program.
Tkinter cannot accept any commands from threads other than the main thread, so launching a TopLevel in a thread will fail by design since it cannot access the Tk in the other thread. To get around this, use the .is_alive method of threads.
def GetReport(threading.Thread):
def __init__(self,text):
self.text = text
super().__init__()
def run(self):
# do some stuff that takes a long time
# to the text you're given as input
def main():
root = Tk()
text = StringVar()
def callbackFunc(text):
top = Toplevel(root)
ttk.Label(top,text="I'm working here!").pack()
thread = GetReport(text)
thread.start()
while thread.is_alive():
root.update() # this keeps the GUI updating
top.destroy() # only when thread dies.
e_text = ttk.Entry(root,textvariable=text).pack()
ttk.Button(root,text="Frobnicate!",
command = lambda: callbackFunc(text.get())).pack()
I have written a short module that can be passed an image and simply creates a Tkinter window and displays it. The problem that I am having is that even when I instantiate and call the method that displays the image in a separate thread, the main program will not continue until the Tkinter window is closed.
Here is my module:
import Image, ImageTk
import Tkinter
class Viewer(Tkinter.Tk):
def __init__(self,parent):
Tkinter.Tk.__init__(self,parent)
self.parent = parent
self.initialize()
def initialize(self):
self.grid()
def show(self,img):
self.to_display = ImageTk.PhotoImage(img)
self.label_image = Tkinter.Label(self,image=self.to_display)
self.label_image.grid(column = 0, row = 0, sticky = "NSEW")
self.mainloop()
It seems to work fine, except when I call it from my test program like the one below, it will not seem to allow my test program to continue, even when started in a different thread.
import Image
from viewer import Viewer
import threading
def showimage(im):
view = Viewer(None)
view.show(im)
if __name__ == "__main__":
im = Image.open("gaben.jpg")
t = threading.Thread(showimage(im))
t.start()
print "Program keeps going..."
I think that perhaps my problem is that I should be creating a new thread within the module itself, but I was wanting to just try and keep it simple, as I am new to Python.
Anyway, thanks in advance for any assistance.
edit: To clarity, I am just trying to make a module that will display an image in a Tkinter window, so that I can use this module any time I want to display an image. The problem that I am having is that any time a program uses this module, it cannot resume until the Tkinter window is closed.
Tkinter isn't thread safe, and the general consensus is that Tkinter doesn't work in a non-main thread. If you rewrite your code so that Tkinter runs in the main thread, you can have your workers run in other threads.
The main caveat is that the workers cannot interact with the Tkinter widgets. They will have to write data to a queue, and your main GUI thread will have to poll that queue.
If all you're doing is showing images, you probably don't need threading at all. Threading is only useful when you have a long running process that would otherwise block the GUI. Tkinter can easily handle hundreds of images and windows without breaking a sweat.
From your comments it sound's like you do not need a GUI at all. Just write the image to disk and call an external viewer.
On most systems it should be possible to launch the default viewer using something like this:
import subprocess
subprocess.Popen("yourimage.png")
From what I can tell, Tkinter doesn't like playing in other threads. See this post...I Need a little help with Python, Tkinter and threading
The work around is to create a (possibly hidden) toplevel in your main thread, spawn a separate thread to open images, etc - and use a shared queue to send messages back to the Tk thread.
Are you required to use Tkinter for your project? I like Tkinter. It's "quick and dirty." - but there are (many) cases where other GUI kits are the way to go.
I have tried to run tkinter from a separate thread, not a good idea, it freezes.
There is one solution that worked. Run the gui in the main thread, and send events to the main gui. This is similar example, it just shows a label.
import Tkinter as t
global root;
root = t.Tk()
root.title("Control center")
root.mainloop()
def new_window(*args):
global root
print "new window"
window = t.Toplevel(root)
label = t.Label(window, text="my new window")
label.pack(side="top", fill="both", padx=10, pady=10)
window.mainloop()
root.bind("<<newwin>>",new_window)
#this can be run in another thread
root.event_generate("<<newwin>>",when="tail")