Can't get ttk.Progressbar to start correctly

后端 未结 2 1961
隐瞒了意图╮
隐瞒了意图╮ 2021-01-28 04:57

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

相关标签:
2条回答
  • 2021-01-28 05:20

    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()
    
    0 讨论(0)
  • 2021-01-28 05:43

    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.

    0 讨论(0)
提交回复
热议问题