I am not able to get the ttk.Progressbar
widget to work. May I know what is the issue and how I can fix it?
I know the Progressbar widget is functional; whe
The tkinter .after()
method cannot be used to implement an indeterminate ttk.Progressbar()
widget concurrently with another ongoing process. This is because the on-going process, simulated by the time.sleep(5) method, is stalling the tkinter application from issuing another process. During the stall, not even the .after()
method can run despite it having a very much shorter wait interval.
As mentioned by @Lukas comments and the references he had shared, an approach to implement an indeterminate ttk.Progressbar()
running concurrently with another application process is to use a thread.daemon
from python's threading
module to manage the concurrency.
Alternatively, python's asyncio infrastructure can be used to implement an indeterminate ttk.Progressbar()
running concurrently with another application process. I recently explored this possibility. A caveat to this approach is that the "stalling process", and the activation and termination of the ttk.Progressbar
must be written up in separate coroutines.
Below is my script showing how to implement asyncio with tkinter 8.6 and its ttk.Progressbar()
widget in Python 3.6.
import tkinter as tk
import tkinter.ttk as ttk
import tkinter.messagebox as tkMessageBox
import asyncio
INTERVAL = 0.05 #seconds
class App(ttk.Frame):
def __init__( self, master, loop, interval=0.05, *args, **kw ):
super().__init__( master,style='App.TFrame')
self.master = master
self.loop = loop
self._set_style()
self._create_widgets()
def _set_style( self ):
self.style = ttk.Style()
self.style.configure( 'App.TFrame', background='pink')
self.style.configure( 'sp.TFrame', background='light green')
def _create_widgets( self ):
self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
self.sp_frame.grid(row=0, column=0)
#sp_frame widgets
self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
self.sp_combox = ttk.Combobox(
self.sp_frame, state="readonly", values=['a','b','c'] )
self.sp_combox.bind('<>', self._connect_esp)
self.sp_pbar = ttk.Progressbar( self.sp_frame, length=200,
mode='indeterminate',
orient=tk.HORIZONTAL, )
self.sp_label1.grid( row=0, column=0 )
self.sp_combox.grid( row=0, column=1, padx=[10,0] )
self.sp_pbar.grid( row=1, column=0, columnspan=2, sticky='ew' )
def _connect_esp( self, event):
async def dojob( loop, start_time, duration=1 ):
print( '\nasync def dojob( loop, end_time):' )
while True:
duration = 3 #seconds
t = loop.time()
delta = t - start_time
print( 'wait time = {}'.format( delta ) )
if delta >= duration:
break
await asyncio.sleep( 1 )
async def trackjob( loop ):
print( '\nasync def trackjob( loop ):' )
start_time = loop.time()
self.sp_pbar.start( 50 )
self.sp_pbar.update_idletasks()
print( 'Job: STARTED' )
result = await dojob( loop, start_time )
print( 'result = ', result, type(result) )
print( 'Job: ENDED' )
self.sp_pbar.stop()
self.sp_pbar.update_idletasks()
try:
task = self.loop.create_task( trackjob( self.loop ) )
print( 'task = ', task, type(task))
except Exception:
raise
async def tk_update( root, interval=INTERVAL ):
print( '\nasync def tk_update( interval ):' )
try:
while True:
root.update() #tk update
await asyncio.sleep( interval )
except tk.TclError as err:
if "application has been destroyed" not in err.args[0]:
raise
def ask_quit( root, loop, interval=INTERVAL ):
'''Confirmation to quit application.'''
if tkMessageBox.askokcancel( "Quit","Quit?" ):
root.update_task.cancel() #Cancel asyncio task to update Tk()
root.destroy() #Destroy the Tk Window instance.
loop.stop() # Stop asyncio loop. This is needed before a run_forever type loop can be closed.
def main():
loop = asyncio.get_event_loop()
root = tk.Tk()
root.geometry('300x100+0+24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
root.update_task = loop.create_task( tk_update( root ) )
app = App( root, loop )
app.grid(row=0, column=0, sticky='nsew')
#root.mainloop() #DO NOT IMPLEMENT; this is replaced by running
# tk's update() method in a asyncio loop called loop.
# See tk_update() method and root.update_task.
#Tell Tk window instance what to do before it is destroyed.
root.protocol("WM_DELETE_WINDOW",
lambda :ask_quit( root, loop ) )
try:
print('start loop.run_forever()')
loop.run_forever()
finally:
loop.run_until_complete( loop.shutdown_asyncgens() )
loop.close()
if __name__ == '__main__':
main()
Taking a macro view, it does seem that implementing tkinter within Python's asyncio event loop can facilitate the development of better concurrent GUI applications. I am discovering this myself and hope this attached script can help fellow tkinter users in learning so.