如何在 tkinter 中长时间运行的任务期间更改光标



我的应用包含一些由按钮单击触发的长时间运行的任务。我想在单击按钮时将光标设置为'wait',并在任务完成后重置它。这似乎相当简单,但我无法让它工作。

一个示例(不是实际的应用程序(来说明我的问题:

import tkinter as tk
import tkinter.ttk as ttk
import time
class App(ttk.Frame):
def __init__(self, master):
super().__init__(master)
self.button = ttk.Button(master=self.master, text='Run task', command=self.onclick_button)
self.button.grid(padx=25, pady=25)
def set_cursor_busy(self):
self.config(cursor='wait')
def reset_cursor(self):
self.config(cursor='')
def onclick_button(self):
self.set_cursor_busy()
time.sleep(5)           # Simulate a long running task.
self.reset_cursor()

root = tk.Tk()
app = App(master=root)
app.mainloop()

根据我从搜索互联网和阅读大量帖子中学到的知识,我知道在执行返回到主循环之前,光标不会更新。但到那时,任务已经完成,光标已经重置为正常,因此看不到光标更改。我看到的选项是,使用:

  1. update_idletasks()
  2. update()
  3. after()
  4. 线程
  5. 您自己的主环

除了 5 个,我尝试实现所有选项,但我无法让其中任何一个工作。

让我的代码工作的最简单方法是什么?

编辑:这篇文章中提到的解决方案实际上是上面提到的选项2,但是在self.config(.)行之后添加self.update()self.master.update()并不能解决我的问题。添加self.master.update_idletasks()也是如此(选项 1(。

有关我的设置的其他信息:Windows 10 企业版上的 Python 3.7.3

编辑2:因此,为了绝对清晰起见,以下方法均无效:

def set_cursor_busy(self):
self.master.config(cursor='wait')
self.master.update_idletasks()
# or: self.update_idletasks()
# or: self.update()
# or: self.master.update()
def reset_cursor(self):
self.master.config(cursor='')
self.master.update_idletasks()
# or: self.update_idletasks()
# or: self.update()
# or: self.master.update()

编辑3:也没有:

def set_cursor_busy(self):
self.master.config(cursor='watch')  # 'watch' instead of 'wait'
self.master.update_idletasks()

编辑4:使用after()延迟长时间运行的任务(如本文所示(也无济于事:

def set_cursor_busy(self):
self.master.config(cursor='watch')
def reset_cursor(self):
self.master.config(cursor='')
def onclick_button(self):
self.set_cursor_busy()
self.after(500, lambda: time.sleep(5))
# Simulate a long running task with time.sleep(5)
self.reset_cursor()

编辑5:虽然它不能解决问题,但在许多情况下,状态栏至少可以作为替代方案,或者甚至可以首选,因为可以添加定制的状态消息。与光标(在 Windows 上的 TKInter 中(相反,状态栏可以在长时间运行的任务期间更新。下面是一个适用于 Windows 的示例:

class App(ttk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.button = ttk.Button(master=self.master, text='Run task', command=self.onclick_button)
self.button.grid(padx=25, pady=25)
self.statusbar = ttk.Label(master=self.master, text='', background='white')
self.statusbar.grid(row=1000, padx=1, sticky=tk.EW)
# Use a high row number to force the statusbar to be the last item on the grid.
def onclick_button(self):
self.update_statusbar()
time.sleep(3)                   # A long-running task.
self.update_statusbar()
def update_statusbar(self):
if not self.statusbar['text']:
self.statusbar['text'] = 'Busy...'
else:
self.statusbar['text'] = ''
self.master.update()

问题:如何在长时间运行的任务期间更改cursor=

参考

  • TkCmd - 游标
  • 尽管有 update_idletasks(( 操作后,光标才会更改
  • 如何获取光标以显示忙碌状态

注意:它没有区别,使用.update(...update_idletasks(....after(500, ...强制将时间片强制到.mainloop()

  1. 工作、使用root.config(...self.master.config(...
    root.config(cursor='watch')
    root.update_idletasks()
    ...
    
  2. 工作,使用self.button.config(...
    self.button.config(cursor='watch')
    self.update_idletasks()
    
  3. 不工作:使用self.config(...
    似乎,人类无法将cursor=设置为Frame对象,没有显示错误。

    self.config(cursor='watch')
    self.update_idletasks()
    

延迟变体,使用.after(...,结果与上面相同:

def long_running_task(self):
time.sleep(5)  # Simulate a long running task.
self.button.config(cursor='no')
def onclick_button(self):
self.button.config(cursor='watch')
self.after(500, self.long_running_task)

用Linux测试 - Python:3.5 - "Tcl版本":8.6 "Tk版本":8.6

最新更新