简体   繁体   中英

How to change the cursor during a long-running task in tkinter

My app contains some long-running tasks that are triggered by button clicks. I want to set the cursor to 'wait' when the button is clicked and reset it when the task is finished. This seems to be fairly straightforward but I can't get it working.

A sample (not the actual app) to illustrate my problem:

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

From what I have learned from searching the internet and reading numerous posts, I understand that the cursor won't be updated until execution returns to the mainloop. But by that time the task has finished and the cursor has already been reset to normal, so no cursor change is visible. The options I have seen are, to use:

  1. update_idletasks()
  2. update()
  3. after()
  4. threading
  5. your own mainloop

Apart from 5, I have tried to implement all options, but I can't get any of them working.

What is the simplest way to get my code working?

EDIT : The solution mentioned in this post is actually option 2 mentioned above, but adding self.update() or self.master.update() after the self.config(.) lines does not resolve my issue. The same holds for adding self.master.update_idletasks() (option 1).

Additional information about my setup: Python 3.7.3 on Windows 10 Enterprise

EDIT 2 : So for absolute clarity, none of the following works:

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

EDIT 3 : Neither does:

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

EDIT 4 : Delaying the long-running task using after() (as suggested in this post ) does not help either:

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

EDIT 5 : Althoug it does not resolve the problem, a status bar could at least serve as an alternative in many cases, or may even be preferred because of the possibility to add a tailored status message. In contrast to the cursor (in TKInter on Windows), a status bar can be updated during a long-running task. Here's an example that works on 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()

Question : How to change the cursor= during a long-running task

Reference


Note : It makes no differnce, using .update(... , update_idletasks(... or .after(500, ... to force a timeslice to the .mainloop() .

  1. Working , using root.config(... or self.master.config(...
     root.config(cursor='watch') root.update_idletasks() ...
  2. Working , using self.button.config(...
     self.button.config(cursor='watch') self.update_idletasks()
  3. NOT Working : using self.config(...
    It seems, man can't set a cursor= to a Frame object, no error shown.

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

The delayed variant, using .after(... , with the same results as above:

    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)

Tested with Linux - Python: 3.5 - 'TclVersion': 8.6 'TkVersion': 8.6

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM