CustomTkinter icon indicating copy to clipboard operation
CustomTkinter copied to clipboard

Changing cursor on click for unresponsive gap

Open EN20M opened this issue 3 years ago • 5 comments
trafficstars

My aplication has a button that triggers a timeconsuming interaction with some hardware. During that time the ui is unresponsive whitch is fine but I want to give the user a clue by showing the watch cursor. When I tryed this at app level in the the function given to the button as command nothing changes no matter how often i call self.update() after changing the cursor to 'watch'. So I changed the CTkButton classes clicked method and added the respective instance attributes like so:

def clicked(self, event=None):
    if self.command is not None:
        if self.state != tkinter.DISABLED:

            # click animation: change color with .on_leave() and back to normal after 100ms with click_animation()
            if self.clickedCursor is not None:
                self.config(cursor=self.clickedCursor)
                self.update()
                self.update() # for some reason it only works with a second self.update()

            self.on_leave()
            self.click_animation_running = True
            self.after(100, self.click_animation)

            self.command()

            if self.clickedCursor is not None:
                self.config(cursor='')

this works halfway decent (somtimes not at first click) and only when I click and stay on the button long enough. If I move the mouse away from the button fast enough after clicking it, it changes to arrow (probably from the surrounding frame).

Is there a more general way to overwrite the cursor, maybe at toplevel, for as long as I need and than give the controll back to the framework?

EN20M avatar Oct 25 '22 14:10 EN20M

@EN20M This is happening because the whole program becomes unresponsive. What I recommend is to use a simple threading in the 'clicked' function.

Here is a basic example:

import threading

def thread_this():
    threading.Thread(target=clicked).start() # target will be your 'clicked' function.

def clicked(self, event=None):
     # your main function here

self.button = customtkinter.CTkButton(self, command=thread_this) # command that button to 'thread_this' first.

Everything will work normally in the main window, and the program will never freeze.

Akascape avatar Oct 25 '22 14:10 Akascape

@Akascape I understand where you are comming from. I know it feel counterintuitive but in my specific situation unless the hardware interaction is done I don't even wand the UI to be responsive since without the hardware beeing the same the UI is just an empty shell.

If I would use threading it would introduce two new problems:

  1. The UI would look like everything is working while in reality the hardware is still busy.
  2. I would have to overwrite the defaultcursor for every singel widget in my whole UI and deactivate avery single button.

So in my case I kind of use the unresponsiveness as a feature but still I would like to be able to generally/reliably overwrite the cursor.

EN20M avatar Oct 26 '22 06:10 EN20M

@EN20M Ok, In that case you have to update the cursor immediately after clicking the button. Try this:

self.button.configure(cursor='watch') # change the cursor to watch
self.update() # immediately update it

Then you can add this in the end:

self.button.configure(cursor='hand2') # back to normal

Instead of changing the cursor in self, we are changing it after pressing the button.

Akascape avatar Oct 26 '22 06:10 Akascape

Thats exactly what the code in my initial issue description does. As I tried to explain the shown code snippet is what I overwrote the CTkButton class with. But as I said this comes with the drawback of only working if I keep the mouse on the button long enouth to take effect after clicking.

EN20M avatar Oct 26 '22 09:10 EN20M

But as I said this comes with the drawback of only working if I keep the mouse on the button long enouth to take effect after clicking.

In that case you can change the surrounding frame/window's cursor too.

self.button.configure(cursor='watch')
self.frame.configure(cursor='watch')
self.update()

Or you can also make the button a little bit bigger.

Obviously it will be very rare that the mouse is moved immediately other than the button and surrounding frame.

Akascape avatar Oct 28 '22 16:10 Akascape