Tkinter中双击左键最大化窗口时,按钮激活不正确
我在我的 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
会记录一个不应该发生的事件。
顺便说一下,想到的最简单的办法是拦截事件的顺序:
- 鼠标指针离开窗口
<Leave>
。 - 调整窗口大小(重新绘制窗口)
<Expose>
。 - 鼠标指针返回到窗口
<Enter>
。 - 错误的点击
<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()
但是会出现一个问题:当用户手动调整窗口大小并尝试在窗口内点击时,这个点击也会被拦截。在这种情况下,你需要追踪事件的时间顺序,或者鼠标移动的距离(因为双击的速度对于小幅度的移动来说是相当快的)。