无法获取ttk.Progressbar以正确启动

时间:2019-01-30 10:34:45

标签: python tkinter

我无法使ttk.Progressbar小部件正常工作。我可以知道问题是什么以及如何解决吗?

我知道Progressbar小部件可以正常工作;当我注释掉self.sp_pbar.stop()时,进度条会运行,但这会在time.sleep(5)完成后发生,这不是您期望的行为。

import tkinter as tk
import tkinter.ttk as ttk
import time

class App(ttk.Frame):


    def __init__( self, master=None, *args, **kw ):

        super().__init__( master,style='App.TFrame')

        self.master = master
        self.espconnecting = False

        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) # The code is running a function here which can take some time.  
        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.


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

2 个答案:

答案 0 :(得分:0)

这是您当前在代码中所拥有的:

您设置了self.espconnecting = False

您致电_connect_esp()

会调用_show_conn_progress()

设置self.espconnecting = True并启动进度条self.sp_pbar.start()

然后呼叫_update_conn_progress()

检查self.espconnecting的值。 如果self.espconnectingTrue(当前为self.espconnecting),则连接继续,进度栏将按预期继续滚动。 如果Falseself.sp_pbar.stop(),进度条将停止.after()

_connect_esp可以在500毫秒内进行回调之前,控制权将传递回self.espconnecting = False并设置.after()。 然后_update_conn_progress()调用self.espconnecting,这是为了使标准不断滚动,

但是(这里是您的问题):=False的最后一个值是什么? self.sp_pbar.stop()因此,控制分支到self.espconnecting = False,这将停止进度条。这就是为什么在注释该行代码时可以按预期工作的原因,因为即使控件在那里分支,也不会阻止进度条正常工作。

解决方案

请勿在{{1​​}}中设置_connect_esp(),因为在.after()进行500ms回调之前,控制权将被传递回_connect_esp(),后者将self.espconnecting = False设置为阻止进度栏正常工作。

这意味着一旦开始就必须找到另一种“终止连接”的方式。

NB:我真的看不到代码中需要time.sleep(5)

这是解决问题的一种可能方法:

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

答案 1 :(得分:0)

tkinter .after()方法不能用于与另一个正在进行的进程同时实现不确定的ttk.Progressbar()小部件。这是因为通过time.sleep(5)方法模拟的正在进行的进程正在阻止tkinter应用程序发布另一个进程。在停顿期间,即使.after()方法的等待间隔非常短,也无法运行。

正如@Lukas的评论和他分享的参考文献所提到的,一种实现不确定的ttk.Progressbar()与另一个应用程序进程同时运行的方法是使用python的thread.daemon模块中的threading管理并发。

或者,python的 asyncio 基础结构可用于实现与另一个应用程序进程同时运行的不确定ttk.Progressbar()。我最近浏览了this possibility。对于这种方法的一个警告是,必须在单独的coroutines中编写“跟踪过程”以及ttk.Progressbar的激活和终止。

下面是我的脚本,该脚本显示了如何在Python 3.6中使用 tkinter 8.6 及其ttk.Progressbar()小部件实现 asyncio

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

从宏观的角度来看,似乎在Python的asyncio事件循环中实现tkinter可以促进更好的并发GUI应用程序的开发。我自己发现了这个问题,并希望这个附件脚本可以帮助tkinter的其他用户学习。