无法让 ttk.Progressbar 正确启动 [英] Can't get ttk.Progressbar to start correctly

查看:51
本文介绍了无法让 ttk.Progressbar 正确启动的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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?

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

I know the Progressbar widget is functional; when I comment out self.sp_pbar.stop() the progressbar runs but this happens after time.sleep(5) completes which is not the desired behavior.

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

推荐答案

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

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.

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

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.

或者,python 的asyncio 基础结构可用于实现与另一个应用程序进程同时运行的不确定的ttk.Progressbar().我最近探索了 这个可能性.这种方法的一个警告是,停滞过程"以及 ttk.Progressbar 的激活和终止必须写在单独的 协同程序.

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.

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

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

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

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.

这篇关于无法让 ttk.Progressbar 正确启动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆