Tkinter when double-clicking the left mouse button to reach the maximum height of the window, one of the buttons is activated incorrectly

92 views Asked by At

The only desired action in my Tkinter window is when clicking on one of the buttons, it prints the market ID and the player ID.

But when I double-click the mouse on the edge of the window in the blue circled region in order to vertically extend the window to maximum fit with the Windows screen, a false click is generated on the button written Roger Federer. I couldn't understand how this is possible and how I can prevent it so that only when I click on the button does the button_clicked() function be executed.

enter image description here

from functools import partial
import tkinter as tk
import pandas as pd

def button_clicked(market_id, player_id):
    print("Market ID:", market_id)
    print("Player ID:", player_id)

def create_window(dataframe):
    window = tk.Tk()
    window.title("Tennis Matches")
    window_width = 400
    window_height = min(len(dataframe) * 50 + 100, 400)
    window.geometry(f"{window_width}x{window_height}")
    window.configure(bg="white")

    canvas = tk.Canvas(window, bg="white")
    canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    scrollbar = tk.Scrollbar(window, orient=tk.VERTICAL, command=canvas.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    canvas.configure(yscrollcommand=scrollbar.set)

    frame = tk.Frame(canvas, bg="white")
    canvas.create_window((0, 0), window=frame, anchor='nw')

    max_button_text_length = max(
        max(dataframe['player_one_name'].apply(len)),
        max(dataframe['player_two_name'].apply(len))
    )

    for index, row in dataframe.iterrows():
        game_label = tk.Label(frame, text=row['match_name'], bg="white", fg="black")
        game_label.pack()

        button_frame = tk.Frame(frame, bg="white")
        button_frame.pack()

        button1 = tk.Button(button_frame, text=row['player_one_name'], command=partial(button_clicked, row['market_id'], row['player_one_id']), bg="white", fg="black")
        button1.pack(side=tk.LEFT, padx=5)
        button1.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button2 = tk.Button(button_frame, text=row['player_two_name'], command=partial(button_clicked, row['market_id'], row['player_two_id']), bg="white", fg="black")
        button2.pack(side=tk.LEFT, padx=5)
        button2.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button1.config(anchor="center")
        button2.config(anchor="center")

        separator = tk.Frame(frame, height=2, bd=1, relief=tk.SUNKEN, bg="white")
        separator.pack(fill=tk.X, padx=5, pady=5)

    def on_configure(event):
        canvas.configure(scrollregion=canvas.bbox('all'))

    frame.bind('<Configure>', on_configure)

    def _on_mousewheel(event):
        canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    canvas.bind_all("<MouseWheel>", _on_mousewheel)

    window.mainloop()


data = {
    'match_name': ['Aberto da Austrália - Rafael Nadal x Roger Federer',
                   'Wimbledon - Novak Djokovic x Andy Murray',
                   'Aberto da França - Serena Williams x Simona Halep',
                   'US Open - Naomi Osaka x Ashleigh Barty',
                   'Aberto da Austrália - Dominic Thiem x Alexander Zverev',
                   'Wimbledon - Stefanos Tsitsipas x Matteo Berrettini',
                   'Aberto da França - Iga Swiatek x Elina Svitolina',
                   'US Open - Bianca Andreescu x Karolina Pliskova'],
    'market_id': [1, 2, 3, 4, 5, 6, 7, 8],
    'player_one_name': ['Rafael Nadal', 'Novak Djokovic', 'Serena Williams', 'Naomi Osaka', 'Dominic Thiem', 'Stefanos Tsitsipas', 'Iga Swiatek', 'Bianca Andreescu'],
    'player_one_id': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
    'player_two_name': ['Roger Federer', 'Andy Murray', 'Simona Halep', 'Ashleigh Barty', 'Alexander Zverev', 'Matteo Berrettini', 'Elina Svitolina', 'Karolina Pliskova'],
    'player_two_id': [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008]
}

df = pd.DataFrame(data)
create_window(df)

Request made by user @thingamabobs
Version without Pandas, using a basic dict instead:

import tkinter as tk

def button_clicked(market_id, player_id):
    print("Market ID:", market_id)
    print("Player ID:", player_id)

def create_window(data):
    window = tk.Tk()
    window.title("Tennis Matches")
    window_width = 400
    window_height = min(len(data['match_name']) * 50 + 100, 400)
    window.geometry(f"{window_width}x{window_height}")
    window.configure(bg="white")

    canvas = tk.Canvas(window, bg="white")
    canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    scrollbar = tk.Scrollbar(window, orient=tk.VERTICAL, command=canvas.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    canvas.configure(yscrollcommand=scrollbar.set)

    frame = tk.Frame(canvas, bg="white")
    canvas.create_window((0, 0), window=frame, anchor='nw')

    max_button_text_length = max(
        max(len(name) for name in data['player_one_name']),
        max(len(name) for name in data['player_two_name'])
    )

    for i in range(len(data['match_name'])):
        game_label = tk.Label(frame, text=data['match_name'][i], bg="white", fg="black")
        game_label.pack()

        button_frame = tk.Frame(frame, bg="white")
        button_frame.pack()

        button1 = tk.Button(button_frame, text=data['player_one_name'][i], command=lambda i=i: button_clicked(data['market_id'][i], data['player_one_id'][i]), bg="white", fg="black")
        button1.pack(side=tk.LEFT, padx=5)
        button1.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button2 = tk.Button(button_frame, text=data['player_two_name'][i], command=lambda i=i: button_clicked(data['market_id'][i], data['player_two_id'][i]), bg="white", fg="black")
        button2.pack(side=tk.LEFT, padx=5)
        button2.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button1.config(anchor="center")
        button2.config(anchor="center")

        separator = tk.Frame(frame, height=2, bd=1, relief=tk.SUNKEN, bg="white")
        separator.pack(fill=tk.X, padx=5, pady=5)

    def on_configure(event):
        canvas.configure(scrollregion=canvas.bbox('all'))

    frame.bind('<Configure>', on_configure)

    def _on_mousewheel(event):
        canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    canvas.bind_all("<MouseWheel>", _on_mousewheel)

    window.mainloop()


data = {
    'match_name': ['Aberto da Austrália - Rafael Nadal x Roger Federer',
                   'Wimbledon - Novak Djokovic x Andy Murray',
                   'Aberto da França - Serena Williams x Simona Halep',
                   'US Open - Naomi Osaka x Ashleigh Barty',
                   'Aberto da Austrália - Dominic Thiem x Alexander Zverev',
                   'Wimbledon - Stefanos Tsitsipas x Matteo Berrettini',
                   'Aberto da França - Iga Swiatek x Elina Svitolina',
                   'US Open - Bianca Andreescu x Karolina Pliskova'],
    'market_id': [1, 2, 3, 4, 5, 6, 7, 8],
    'player_one_name': ['Rafael Nadal', 'Novak Djokovic', 'Serena Williams', 'Naomi Osaka', 'Dominic Thiem', 'Stefanos Tsitsipas', 'Iga Swiatek', 'Bianca Andreescu'],
    'player_one_id': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
    'player_two_name': ['Roger Federer', 'Andy Murray', 'Simona Halep', 'Ashleigh Barty', 'Alexander Zverev', 'Matteo Berrettini', 'Elina Svitolina', 'Karolina Pliskova'],
    'player_two_id': [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008]
}

create_window(data)

2

There are 2 answers

0
Сергей Кох On

EDIT. This is a tcl tk bug in Windows. This can be seen by double clicking on the window title.

from tkinter import *

def window_clicked(event):
    print("window_clicked", event.x, event.y)


root = Tk()
root.geometry("600x300")
root.bind("<Button-1>", window_clicked)
root.mainloop()

-------------------

window_clicked 418 99

An event is logged that should not occur.

P.S. The simplest thing that comes to mind is to intercept the sequence of events:

  1. The pointer leaves the window <Leave>.
  2. Resizing the window (redrawing it) <Expose>.
  3. Returning the pointer to the window <Enter>.
  4. False click <Button-1>.

Since a more specific binding will be selected, the false click is easily intercepted.

from tkinter import *


def window_clicked(event):
    print("window_clicked", event.x, event.y)


def leave_expose_enter_b1(event):
    print("leave_expose_enter_b1")


root = Tk()
root.geometry("600x300")
root.bind("<Button-1>", window_clicked)
root.bind('<Leave> <Expose> <Enter> <Button-1>', leave_expose_enter_b1)

root.mainloop()

But a problem arises: when a person manually resizes the window and tries to click in the window, he will also be intercepted. Here you need to either track the time of the sequence of events, or the length of the mouse movement (double click is quite fast for small movements).

0
Thingamabobs On

As mentioned already in the comment section, this is not a bug in tkinter.
You have to place the window on a spot where your mouse cursor ends up over a button, to reproduce the issue.
So it's fair to say that the event is send by the underlying operating system.

To prevent that you either have to hook into the event queue for events such as WM_ENTERSIZEMOVE or think about something different to find the right time to block those events while resizing.

In the below code, I listed all of your buttons, bound ALL windows for <Enter> and <Leave> events and disabled the buttons when the mouse leaves the window.
However the disadvantages should be obvious and the code is just for demonstration purpose.

import tkinter as tk

def button_clicked(market_id, player_id):
    print("Market ID:", market_id)
    print("Player ID:", player_id)

btn_lst = []

def disable(event):
    for btn in btn_lst:
        btn.configure(state='disabled')

def enable():
    for btn in btn_lst:
        btn.configure(state='normal')

def delayed(event):
    w = event.widget
    w.after(50, enable)

def create_window(data):
    window = tk.Tk()
    window.bind_class(window.winfo_class(),'<Leave>', disable)
    window.bind_class(window.winfo_class(),'<Enter>', delayed)
    window.title("Tennis Matches")
    window_width = 400
    window_height = min(len(data['match_name']) * 50 + 100, 400)
    window.geometry(f"{window_width}x{window_height}")
    window.configure(bg="white")

    global canvas
    canvas = tk.Canvas(window, bg="white")
    canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    scrollbar = tk.Scrollbar(window, orient=tk.VERTICAL, command=canvas.yview)
    scrollbar.pack(side=tk.RIGHT, fill=tk.Y)

    canvas.configure(yscrollcommand=scrollbar.set)

    frame = tk.Frame(canvas, bg="white")
    canvas.create_window((0, 0), window=frame, anchor='nw')

    max_button_text_length = max(
        max(len(name) for name in data['player_one_name']),
        max(len(name) for name in data['player_two_name'])
    )

    for i in range(len(data['match_name'])):
        game_label = tk.Label(frame, text=data['match_name'][i], bg="white", fg="black")
        game_label.pack()

        button_frame = tk.Frame(frame, bg="white")
        button_frame.pack()

        button1 = tk.Button(button_frame, text=data['player_one_name'][i], command=lambda i=i: button_clicked(data['market_id'][i], data['player_one_id'][i]), bg="white", fg="black")
        button1.pack(side=tk.LEFT, padx=5)
        button1.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button2 = tk.Button(button_frame, text=data['player_two_name'][i], command=lambda i=i: button_clicked(data['market_id'][i], data['player_two_id'][i]), bg="white", fg="black")
        button2.pack(side=tk.LEFT, padx=5)
        button2.config(width=max_button_text_length, borderwidth=2, font=('Arial', 10, 'bold'))

        button1.config(anchor="center")
        button2.config(anchor="center")
        btn_lst.extend([button1,button2])

        separator = tk.Frame(frame, height=2, bd=1, relief=tk.SUNKEN, bg="white")
        separator.pack(fill=tk.X, padx=5, pady=5)

    def on_configure(event):
        canvas.configure(scrollregion=canvas.bbox('all'))

    frame.bind('<Configure>', on_configure)

    def _on_mousewheel(event):
        canvas.yview_scroll(int(-1 * (event.delta / 120)), "units")

    canvas.bind_all("<MouseWheel>", _on_mousewheel)

    window.mainloop()


data = {
    'match_name': ['Aberto da Austrália - Rafael Nadal x Roger Federer',
                   'Wimbledon - Novak Djokovic x Andy Murray',
                   'Aberto da França - Serena Williams x Simona Halep',
                   'US Open - Naomi Osaka x Ashleigh Barty',
                   'Aberto da Austrália - Dominic Thiem x Alexander Zverev',
                   'Wimbledon - Stefanos Tsitsipas x Matteo Berrettini',
                   'Aberto da França - Iga Swiatek x Elina Svitolina',
                   'US Open - Bianca Andreescu x Karolina Pliskova'],
    'market_id': [1, 2, 3, 4, 5, 6, 7, 8],
    'player_one_name': ['Rafael Nadal', 'Novak Djokovic', 'Serena Williams', 'Naomi Osaka', 'Dominic Thiem', 'Stefanos Tsitsipas', 'Iga Swiatek', 'Bianca Andreescu'],
    'player_one_id': [1001, 1002, 1003, 1004, 1005, 1006, 1007, 1008],
    'player_two_name': ['Roger Federer', 'Andy Murray', 'Simona Halep', 'Ashleigh Barty', 'Alexander Zverev', 'Matteo Berrettini', 'Elina Svitolina', 'Karolina Pliskova'],
    'player_two_id': [2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008]
}
create_window(data)