如何在Python的Tkinter中点击窗口时保持其不获取焦点?
问题
我正在开发一款带希腊字母面板的应用,点击面板按钮就能把字符输入到当前使用的文本框里。但点击按钮时,Tk窗口会抢走焦点,导致字符没法粘贴到之前的窗口。试过给Button加takefocus=0,但这只对键盘导航有用,想知道怎么让窗口不获取焦点。
当前代码:
from tkinter import * import math import pyautogui class App(Tk): def __init__(self): super().__init__() self.rows = 4 self.alphabet = ["Αα", "Ββ", "Γγ", "Δδ", "Εε", "Ζζ", "Ηη", "Θθ", "Ιι", "Κκ", "Λλ", "Μμ", "Νν", "Ξξ", "Οο", "Ππ", "Ρρ", "Σσ", "Ττ", "Υυ", "Φφ", "Χχ", "Ψψ", "Ωω"] self.buttons = [Button(text=i[1], command=(lambda: self.paste(i)), font=('Times New Roman', 16, 'bold'), takefocus=0) for i in self.alphabet] blen = math.ceil(len(self.buttons) / self.rows) for i in range(0, self.rows): self.grid_rowconfigure(i, weight=1) for j in range(0, blen): self.grid_columnconfigure(j, weight=1) if i * blen + j < len(self.buttons): self.buttons[i * blen + j].grid(row=i, column=j, ipadx=4, ipady=4, sticky="nesw") def paste(self, string): # probably need to do this differently self.clipboard_clear() self.clipboard_append(string[1]) pyautogui.hotkey("ctrl", "v") if __name__ == "__main__": app = App() app.attributes('-topmost', True) app.mainloop()
解决方案
Tk本身没有直接提供让窗口不获取焦点的属性,需要借助Win32 API修改窗口样式,同时优化字符输入逻辑:
修改步骤
- 引入ctypes库调用Win32 API,给窗口添加
WS_EX_NOACTIVATE扩展样式,这个样式能让窗口不会因鼠标点击获取焦点。 - 修正lambda变量捕获问题,避免所有按钮触发最后一个字符的输入。
- 替换剪贴板粘贴为直接输入,用
pyautogui.typewrite直接输出字符,省去剪贴板操作,避免覆盖原有剪贴板内容。
修改后的完整代码
from tkinter import * import math import pyautogui import ctypes from ctypes import wintypes # Win32 API常量与函数定义 GWL_EXSTYLE = -20 WS_EX_NOACTIVATE = 0x08000000 user32 = ctypes.WinDLL('user32', use_last_error=True) user32.GetWindowLongPtrW.restype = wintypes.LONG_PTR user32.GetWindowLongPtrW.argtypes = (wintypes.HWND, wintypes.INT) user32.SetWindowLongPtrW.restype = wintypes.LONG_PTR user32.SetWindowLongPtrW.argtypes = (wintypes.HWND, wintypes.INT, wintypes.LONG_PTR) class App(Tk): def __init__(self): super().__init__() self.rows = 4 self.alphabet = ["Αα", "Ββ", "Γγ", "Δδ", "Εε", "Ζζ", "Ηη", "Θθ", "Ιι", "Κκ", "Λλ", "Μμ", "Νν", "Ξξ", "Οο", "Ππ", "Ρρ", "Σσ", "Ττ", "Υυ", "Φφ", "Χχ", "Ψψ", "Ωω"] # 修正lambda变量捕获问题,确保每个按钮绑定对应字符 self.buttons = [Button(text=i[1], command=(lambda char=i: self.paste(char)), font=('Times New Roman', 16, 'bold'), takefocus=0) for i in self.alphabet] blen = math.ceil(len(self.buttons) / self.rows) for i in range(0, self.rows): self.grid_rowconfigure(i, weight=1) for j in range(0, blen): self.grid_columnconfigure(j, weight=1) if i * blen + j < len(self.buttons): self.buttons[i * blen + j].grid(row=i, column=j, ipadx=4, ipady=4, sticky="nesw") # 保持窗口始终置顶 self.attributes('-topmost', True) # 窗口初始化完成后设置无焦点样式 self.after(100, self.set_no_activate) def set_no_activate(self): # 获取Tk窗口的系统句柄 hwnd = wintypes.HWND(self.winfo_id()) # 获取当前窗口扩展样式 ex_style = user32.GetWindowLongPtrW(hwnd, GWL_EXSTYLE) # 添加WS_EX_NOACTIVATE样式,阻止窗口获取焦点 user32.SetWindowLongPtrW(hwnd, GWL_EXSTYLE, ex_style | WS_EX_NOACTIVATE) # 更新窗口样式 user32.SetWindowPos(hwnd, None, 0, 0, 0, 0, 0x0001 | 0x0002 | 0x0040) def paste(self, char): # 直接输入目标字符,无需依赖剪贴板 pyautogui.typewrite(char[1]) if __name__ == "__main__": app = App() app.mainloop()
关键说明
WS_EX_NOACTIVATE样式是Windows系统特有的,能让窗口成为"非激活"窗口,鼠标点击不会让它获取焦点,完全不影响之前操作的窗口。after(100, ...)是为了等窗口完全初始化后再修改样式,避免因句柄未就绪导致操作失败。- 修正lambda变量捕获是因为原代码中lambda会引用循环变量的最终值,导致所有按钮都触发最后一个字符的输入,改用
char=i可以捕获当前循环的变量值。
内容的提问来源于stack exchange,提问作者Dustin Thomas
相关产品推荐
相关产品推荐

