pythontkinter

Event button release doesn't work if another event happens


The problem occurs when a mouse button is pressed and not released, another click event happens which interrupts the current event. I will try to show this with the following code example:

import tkinter as tk

root = tk.Tk()
l = tk.Label(bg='red', width=30, height=30)
l.pack(fill='both', padx=100, pady=100)

l.bind('<Button-1>', lambda e: print('pressed'))
l.bind('<ButtonRelease-1>', lambda e: print('release'))
root.mainloop()

To reproduce this error:

  1. Click and hold the left mouse button on the red label.
  2. Move the cursor to the white space that is on the root. The mouse button must remain pressed.
  3. Now press and release the other button while still holding the left mouse button.
  4. Release the left mouse button on the white space.

Note: In the 4'th step it is important to stay outside the red label otherwise everything will work.

So for some reason the ButtonRelease-1 event will not happen, most likely because another event happened during the hold process, but it is still not clear for me why.

I want to avoid this. Maybe it is possible to block other events during this process or force other events to work together with this event. I tried to use break, also tried to use add='+' for the bind functions, but none of that works.


Solution

  • it doesn't work on windows because windows can only have 1 "window" (AKA widget) capturing the mouse with SetCapture, when you click outside of there the focus shifts from the label (static window) to the main window (TKWindow) and the OS is going to send the WM_LBUTTONUP message to the window instead of the label.

    to get around that you could have the parent window handle the mouse event instead of the label, and just check that the mouse is within the bounds of the label, or just live with this problem.

    Edit: another way is to block events from all of the system until this action is done as follows.

    import tkinter as tk
    
    root = tk.Tk()
    l = tk.Label(bg='red', width=30, height=30)
    l.pack(fill='both', padx=100, pady=100)
    
    def down(e):
        print('pressed')
        l.grab_set_global()
    
    def up(e):
        print('release')
        l.grab_release()
    
    l.bind('<Button-1>', down)
    l.bind('<ButtonRelease-1>', up)
    root.mainloop()