我无法使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()
答案 0 :(得分:0)
这是您当前在代码中所拥有的:
您设置了self.espconnecting = False
您致电_connect_esp()
会调用_show_conn_progress()
设置self.espconnecting = True
并启动进度条self.sp_pbar.start()
然后呼叫_update_conn_progress()
检查self.espconnecting
的值。
如果self.espconnecting
为True
(当前为self.espconnecting
),则连接继续,进度栏将按预期继续滚动。
如果False
是self.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的其他用户学习。