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
This is what you currently have in your code:
you set self.espconnecting = False
you call _connect_esp()
which calls _show_conn_progress()
which sets self.espconnecting = True
and starts the progressbar self.sp_pbar.start()
and then calls _update_conn_progress()
which checks the value of self.espconnecting
.
If self.espconnecting
is True
(which it currently is) connection continues and progress bar keeps rolling as expected.
If self.espconnecting
is False
progress bar is stopped self.sp_pbar.stop()
Before .after()
can make it's callback in 500ms, Control is passed back to _connect_esp
which sets self.espconnecting = False
.
Then .after()
calls _update_conn_progress()
which is meant to keep the bar rolling,
but(Here is your problem): what is the last value of self.espconnecting
? =False
hence, control branches to self.sp_pbar.stop()
, which stops the progrss bar. This is why when you comment that line out your code works as expected, because even if control branches there, there will be nothing to prevent the progress bar from working.
SOLUTION
Do not set self.espconnecting = False
in _connect_esp()
because before .after()
makes it's callback in 500ms, control would have been passed back to _connect_esp()
which sets self.espconnecting = False
which prevents your progress bar from working.
This means you have to find another means to 'end the connection', once it gets started.
N.B: I really don't see the need for time.sleep(5)
in the code.
Here is a possible way to go about the solving it:
...
def __init__( self, master=None, *args, **kw ):
super().__init__( master,style='App.TFrame')
self.master = master
self.espconnecting = False
self.count=0
self._set_style()
self._create_widgets()
def _set_style( self ):
print( '\ndef _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 ):
print( '\ndef _create_widgets( self ):' )
self.sp_frame = ttk.Frame( self, style='sp.TFrame' )
self.sp_frame.grid(row=0, column=0)
#self.sp_frame widgets
self.sp_label1 = ttk.Label( self.sp_frame, text='SP(s):')
self.sp_label2 = ttk.Label( self.sp_frame, text='ESP(s):')
self.sp_label3 = ttk.Label( self.sp_frame, )
self.sp_combox = ttk.Combobox( self.sp_frame, state="readonly",
values=['a','b','c'] )
self.sp_combox.bind('<<ComboboxSelected>>', 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' )
self.sp_label2.grid( row=2, column=0)
self.sp_label3.grid( row=2, column=1)
def _connect_esp( self, event=None):
print( '\ndef connect_esp( self, event=None ):' )
self._show_conn_progress()
print("START Connection")
time.sleep(5)
def end_connection(self):
print("END Connection")
self.espconnecting = False
def _show_conn_progress( self ):
print( '\ndef _show_conn_progress( self ):' )
self.espconnecting = True
self.sp_label3['text']='Connecting.....'
self.sp_label3.update_idletasks()
self.sp_pbar.start()
self._update_conn_progress()
def _update_conn_progress( self ):
print( '\ndef _update_conn_progress( self ):' )
if not self.espconnecting:
print('connected')
self.sp_pbar.stop()
self.sp_label3['text']='Connected'
else:
print('connecting')
#self.sp_pbar.update_idletasks()
self.after(500, self._update_conn_progress) # Call this method after 500 ms.
self.count=self.count + 1
if self.count==10:
self.end_connection()
def main():
root = tk.Tk()
root.geometry('300x100+0+24')
root.rowconfigure(0, weight=1)
root.columnconfigure(0, weight=1)
app = App( root )
app.grid(row=0, column=0, sticky='nsew')
root.mainloop()
if __name__ == '__main__':
main()
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('<<ComboboxSelected>>', 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.