Tkinter grid布局下Listbox显示时抢占Entry输入焦点如何解决
搜索控件焦点异常修复
问题根因
焦点自动跳转是两处代码逻辑错误导致的:
- 列表选中回调
selector最后一行错误写为listbox.focus_set(),选中内容填充完成后,焦点被强制分配给了已经隐藏的Listbox - 输入触发列表刷新时,调用
grid()重新显示Listbox会触发Tkinter默认的焦点分配规则,自动把焦点切到Listbox上,直接打断Entry的连续输入
之前调用focus_set()不生效,本质是调用位置错误,没有在列表重绘、选中操作完成后把焦点拉回Entry。
修复方法
只需要修改两处代码即可解决问题:
- 把
selector函数里的listbox.focus_set()删除,替换为entry.focus_set(),选中建议项填充到输入框后,焦点直接返回Entry - 在
updator函数末尾,也就是显示完Listbox和滚动条的代码后面,添加entry.focus_set(),每次刷新建议列表都把焦点锁定在Entry上,避免Listbox抢占焦点。
修复后可运行代码
from tkinter import * from tkinter import ttk class SearchEntryBase(ttk.Frame): def __init__(self, parent, toStr = str, rows = [], *args, **kwargs): super().__init__(parent, *args, **kwargs) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) self._selected = None self.toStr = toStr self._rows = rows visible_rows = [] entry_content = StringVar() entry = ttk.Entry(self,textvariable=entry_content) listbox = Listbox(self,selectmode=SINGLE) hscroll = ttk.Scrollbar(self, orient='horizontal', command=listbox.xview) vscroll = ttk.Scrollbar(self, orient='vertical', command=listbox.yview) listbox.configure(xscroll = hscroll.set) listbox.configure(yscroll = vscroll.set) # 网格布局摆放控件 entry.grid(row=0, column=0, columnspan=2, sticky= (W, E)) listbox.grid(row=1,column=0,sticky=(N, S, E, W)) hscroll.grid(row=2,column=0,sticky=(E, W)) vscroll.grid(row=1,column=1,rowspan=2,sticky=[N, S]) # 默认隐藏列表和配套滚动条 listbox.grid_remove() vscroll.grid_remove() hscroll.grid_remove() # 列表项选中处理逻辑 def selector(event): listbox.grid_remove() vscroll.grid_remove() hscroll.grid_remove() s = listbox.curselection() if s: i=s[0] entry_content.set(listbox.get(i)) self._selected = visible_rows[i] listbox.select_clear(0,END) # 修复:选中后焦点返回输入框 entry.focus_set() listbox.bind('<<ListboxSelect>>',selector) # 输入变化时更新建议列表 def updator(event): value=entry_content.get() visible_rows.clear() listbox.delete(0,END) for row in self._rows: str_val = self.toStr(row) if value == str_val: self._selected = row else: self._selected = None if value in str_val: visible_rows.append(row) listbox.insert(END,str_val) if not(visible_rows): for row in self._rows: visible_rows.append(row) listbox.insert(END,self.toStr(row)) listbox.grid() vscroll.grid() hscroll.grid() # 修复:列表刷新后保持输入框焦点 entry.focus_set() return True entry.bind('<KeyRelease>',updator) root = Tk() root.geometry('300x400') root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) rows=['Jan','Piet','Joris','Corneel','AAA','BBB','ABC'] se=SearchEntryBase(root,rows=rows) se.grid(row=0,column=0,sticky='new') root.mainloop()
顺手修复了一个隐性bug:原代码循环里用
str作为变量名,会覆盖Python内置的str()方法,修改为str_val避免后续出现异常。
内容的提问来源于stack exchange,提问作者Marc Mertens
相关产品推荐
相关产品推荐

