Tkinter中双击左键最大化窗口时,按钮激活不正确

4 投票
2 回答
89 浏览
提问于 2025-04-12 18:54

我在我的 Tkinter 窗口里,唯一想要的操作就是点击按钮时,打印出市场 ID 和玩家 ID。

但是,当我在窗口的边缘,也就是蓝色圈住的区域,双击鼠标想把窗口最大化以适应 Windows 屏幕时,竟然会错误地点击到一个写着 Roger Federer 的按钮。我搞不懂这是怎么回事,也不知道怎么才能防止这种情况发生,让 button_clicked() 函数只在我真正点击按钮时执行。

在这里输入图片描述

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)

这是用户 @thingamabobs 提出的请求
这是一个不使用 Pandas 的版本,改用基本的 dict

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 个回答

0

正如评论区提到的,这并不是tkinter的错误。
你需要把窗口放在一个位置,让你的鼠标光标正好在一个按钮上,才能重现这个问题。
所以可以说,这个事件是由底层操作系统发送的。

为了防止这种情况发生,你要么需要处理事件队列中的一些事件,比如WM_ENTERSIZEMOVE,要么想其他办法找到合适的时机来阻止这些事件在调整大小时发生。

在下面的代码中,我列出了你所有的按钮,绑定了所有窗口的<Enter><Leave>事件,并在鼠标离开窗口时禁用了按钮。
不过,这样做的缺点应该很明显,这段代码只是为了演示。

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

编辑。

这是Windows系统下tcl tk的一个bug。你可以通过双击窗口标题来观察到这个问题。

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

会记录一个不应该发生的事件。

顺便说一下,想到的最简单的办法是拦截事件的顺序:

  1. 鼠标指针离开窗口 <Leave>
  2. 调整窗口大小(重新绘制窗口) <Expose>
  3. 鼠标指针返回到窗口 <Enter>
  4. 错误的点击 <Button-1>

由于会选择更具体的绑定,所以这个错误的点击很容易被拦截。

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

但是会出现一个问题:当用户手动调整窗口大小并尝试在窗口内点击时,这个点击也会被拦截。在这种情况下,你需要追踪事件的时间顺序,或者鼠标移动的距离(因为双击的速度对于小幅度的移动来说是相当快的)。

撰写回答