Skip to content Skip to sidebar Skip to footer

Tkinter Cursor Not Changing Until After Action Despite Update_idletasks()

I am trying to change the cursor in my tkinter program to show the program is working but the cursor only changes to the working cursor until after the work is done, this is as com

Solution 1:

I suspect that all events need to be proceeded to change a cursor's look, because cursor depends on operating system and there're some events to handle (I assume that), since update_idletask has no effect - your cursor really change look only when code flow reaches a mainloop. Since you can treat an update as mainloop(1) (very crude comparison) - it's a good option if you know what you doing, because noone wants an endless loop in code.

Little snippet to represent idea:

try:
    import tkinter as tk
except ImportError:
    import Tkinter as tk
import time


classApp(tk.Tk):
    def__init__(self):
        tk.Tk.__init__(self)
        self.button = tk.Button(self, text='Toggle cursor', command=self.toggle_business)
        self.button.pack()

    deftoggle_business(self):
        if self['cursor']:
            self.config(cursor='')
        else:
            self.config(cursor='wait')


        # self.update_idletasks()   # have no effect at all# self.update()             # "local" mainloop(1)# simulate work with time.sleep# time.sleep(3)# also your work can be scheduled so code flow can reach a mainloop# self.after(500, lambda: time.sleep(3))


app = App()
app.mainloop()

To overcome this problem you can use:

  • update method (note warnings)

  • after method for scheduled work (opportunity to reach a mainloop for a code flow)

  • threading for "threaded" work (another opportunity, but GUI is responsive, you can handle other events and even simulate unresponsiveness, in other hand threading adds complexity, so use it if you really need it).

Note: There's no difference in behaviour between universal and native cursors on Windows platform.

Solution 2:

This code was tested in windows 10 and Python 3. I found the cursor would not change until control was returned to mainloop. The code here outlines how to consistently display the busy cursor during a long running task. Further, this code demonstrates how to retrieve the data from the long running task (like results from a database query).

#! python3'''
Everything you need to run I/O in a separate thread and make the cursor show busy
Summary:
    1. Set up to call the long running task, get data from windows etc.
        1a. Issue a callback to the routine that will process the data
    2. Do the long running task - absolutely no tkinter access, return the data
    3. Get the data from the queue and process away. tkinter as you will
'''import tkinter as tk
import tkinter.ttk as ttk
from threading import Thread
from threading import Event
import queue


classSimpleWindow(object):
    def__init__(self):

        self._build_widgets()

    def_build_widgets(self):
        # *************************************************************************************************# * Build buttons and some entry boxes# *************************************************************************************************
        g_col = 0
        g_row = 0
        WaiterFrame = ttk.Frame()
        WaiterFrame.pack( padx=50)

        i = 0
        g_row += 1
        longWaitButton = ttk.Button(WaiterFrame, text='Long Wait',command=self.setup_for_long_running_task)
        longWaitButton.grid(row = g_row, column = i, pady=4, padx=25)
        i += 1
        QuitButton = ttk.Button(WaiterFrame, text='Quit', command=self.quit)
        QuitButton.grid(row = g_row, column = i,pady=4, padx=25)
        i += 1
        self.Parm1Label = ttk.Label(WaiterFrame, text="Parm 1 Data")
        self.Parm1Label.grid(row = g_row-1, column = i, pady=4, padx=2)
        self.Parm1 = ttk.Entry(WaiterFrame)
        self.Parm1.grid(row = g_row, column = i, pady=4, padx=2)

        i += 1
        self.Parm2Label = ttk.Label(WaiterFrame, text="Parm 2 Data")
        self.Parm2Label.grid(row = g_row-1, column = i, pady=4, padx=2)
        self.Parm2 = ttk.Entry(WaiterFrame)
        self.Parm2.grid(row = g_row, column = i, pady=4, padx=2)

        i += 1
        self.Parm3Label = ttk.Label(WaiterFrame, text="Parm 3 Data")
        self.Parm3Label.grid(row = g_row-1, column = i, pady=4, padx=2)
        self.Parm3 = ttk.Entry(WaiterFrame)
        self.Parm3.grid(row = g_row, column = i, pady=4, padx=2)

        i += 1
        self.Parm4Label = ttk.Label(WaiterFrame, text="Parm 4 Data")
        self.Parm4Label.grid(row = g_row-1, column = i, pady=4, padx=2)
        self.Parm4 = ttk.Entry(WaiterFrame)
        self.Parm4.grid(row = g_row, column = i, pady=4, padx=2)

    defquit(self):
        root.destroy()
        root.quit()

    defsetup_for_long_running_task(self):
        # ********************************************************************************************************# * Do what needs to be done before starting the long running task in a thread# ********************************************************************************************************
        Parm1, Parm2, Parm3, Parm4 = self.Get_Parms()

        root.config(cursor="wait")  # Set the cursor to busy# ********************************************************************************************************# * Set up a queue for thread communication# * Invoke the long running task (ie. database calls, etc.) in a separate thread# ********************************************************************************************************
        return_que = queue.Queue(1)
        workThread = Thread(target=lambda q, w_self, p_1, p_2, p_3, p_4: \
                            q.put(self.long_running_task(Parm1, Parm2, Parm3, Parm4)),
                            args=(return_que, self, Parm1, Parm2, Parm3, Parm4))
        workThread.start()
        # ********************************************************************************************************# * Busy cursor won't appear until this function returns, so schedule a callback to accept the data# * from the long running task. Adjust the wait time according to your situation# ********************************************************************************************************
        root.after(500,self.use_results_of_long_running_task,workThread,return_que)  # 500ms is half a second# ********************************************************************************************************# * This is run in a thread so the cursor can be changed to busy. NO tkinter ALLOWED IN THIS FUNCTION# ********************************************************************************************************deflong_running_task(self, p1,p2,p3,p4):

        Event().wait(3.0)  # Simulate long running task

        p1_out = f'New {p1}'
        p2_out = f'New {p2}'
        p3_out = f'New {p3}'
        p4_out = f'New {p4}'return [p1_out, p2_out, p3_out, p4_out]

    # ********************************************************************************************************# * Waits for the thread to complete, then gets the data out of the queue for the listbox# ********************************************************************************************************defuse_results_of_long_running_task(self, workThread,return_que):
        ThreadRunning = 1while ThreadRunning:
            Event().wait(0.1)  # this is set to .1 seconds. Adjust for your process
            ThreadRunning = workThread.is_alive()

        whilenot return_que.empty():
            return_list = return_que.get()

        self.LoadWindow(return_list)
        root.config(cursor="")  # reset the cursor to normaldefLoadWindow(self, data_list):

        self.Parm1.delete(0, tk.END)
        self.Parm2.delete(0, tk.END)
        self.Parm3.delete(0, tk.END)
        self.Parm4.delete(0, tk.END)

        i=0; self.Parm1.insert(0,data_list[i])
        i+=1; self.Parm2.insert(0,data_list[i])
        i+=1; self.Parm3.insert(0,data_list[i])
        i+=1; self.Parm4.insert(0,data_list[i])

    # ********************************************************************************************************# * The long running task thread can't get to the tkinter self object, so pull these parms# * out of the window and into variables in the main process# ********************************************************************************************************defGet_Parms(self):
        p1 = self.Parm1Label.cget("text")
        p2 = self.Parm2Label.cget("text")
        p3 = self.Parm3Label.cget("text")
        p4 = self.Parm4Label.cget("text")
        return p1,p2,p3,p4

defWaitForBigData():
    global root

    root = tk.Tk()
    root.title("Wait with busy cursor")
    waitWindow = SimpleWindow()
    root.mainloop()

if __name__ == '__main__':

    WaitForBigData()

Post a Comment for "Tkinter Cursor Not Changing Until After Action Despite Update_idletasks()"