一个用户小需求,要求在输入了账号和密码后按回车能够自动切换焦点,当焦点在button上时按回车和点击效果一样,一句话就是不想用鼠标。
功能很好实现,每个entry绑定回车再转移焦点即可,但如果输入的项目比较多,代码冗长就让人讨厌了,能不能从底层去解决?
百度几番找到一段代码,通过继承entry,绑定回车实现焦点的自动后移,效果不错,代码精炼!
import tkinter as tk
class MyEntry(tk.Entry):
"""Entry widget with Return bound to provide tabbing functionality"""
def __init__(self, master, **kw):
tk.Entry.__init__(self, master, **kw)
self.next_widget = None
self.bind("<Return>", self.on_ret)
def on_ret(self, ev):
if self.next_widget:
self.event_generate('<<TraverseOut>>')
self.next_widget.focus()
self.next_widget.event_generate('<<TraverseIn>>')
else:
self.event_generate('<<NextWindow>>')
return "break"
def set_next(self, widget):
"""Override the default next widget for this instance"""
self.next_widget = widget
def add_entry(parent, row, **kwargs):
widget = MyEntry(parent, **kwargs)
widget.grid(row=row, column=0, sticky=tk.NSEW)
return widget
def main(args=None):
root = tk.Tk()
frame = tk.LabelFrame(root, text="Entries", width=200, height=200)
entries = []
for row in range(4):
e = add_entry(frame, row)
entries.append(e)
e.set_next(entries[0])
frame.grid_columnconfigure(0, weight=1)
frame.grid_rowconfigure(0, weight=0)
button = tk.Button(root, text="Exit", command=root.destroy)
frame.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.SE)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
entries[0].focus_set()
root.mainloop()
if __name__ == '__main__':
main()
美中不足,这段代码写死了Entry!我还有几个Button也要获得焦点,还有其它类似的控制,最好能有个完美的“中间层”实现焦点控制。
借鉴上段代码,利用python OO多继承的特点构造了一个中间控制。
MYCtrl实现控件收到回车事件后需要判断的逻辑,这里面再判断是何种类型的控件,去实现相应动作,button执行创建时传入的command,entry则焦点转换到下一个控件
class MyCtrl:
def on_ret(self, ev):
if self.widgetName == "button":
self.invoke()
elif self.widgetName == "entry":
if self.next_widget:
self.event_generate('<<TraverseOut>>')
self.next_widget.focus()
self.next_widget.event_generate('<<TraverseIn>>')
else:
self.event_generate('<<NextWindow>>')
return "break"
def set_next(self, widget):
"""Override the default next widget for this instance"""
self.next_widget = widget
用Entry,Button分别和MyCtrl派生新控件,在类的init方法中绑定回车键的相应,即与MYCtrl.on_ret捆绑中一起,至此焦点自动转移需求完美实现!
class MyEntry(tk.Entry,MyCtrl):
def __init__(self, master, **kw):
tk.Entry.__init__(self, master, **kw)
self.next_widget = None
self.bind("<Return>", self.on_ret)
class MyButton(tk.Button,MyCtrl):
def __init__(self, master, **kw):
tk.Button.__init__(self, master, **kw)
self.next_widget = None
self.bind("<Return>", self.on_ret)
完整代码如下:
import tkinter as tk
class MyCtrl:
def on_ret(self, ev):
if self.widgetName == "button":
self.invoke()
elif self.widgetName == "entry":
if self.next_widget:
self.event_generate('<<TraverseOut>>')
self.next_widget.focus()
self.next_widget.event_generate('<<TraverseIn>>')
else:
self.event_generate('<<NextWindow>>')
return "break"
def set_next(self, widget):
"""Override the default next widget for this instance"""
self.next_widget = widget
class MyEntry(tk.Entry,MyCtrl):
def __init__(self, master, **kw):
tk.Entry.__init__(self, master, **kw)
self.next_widget = None
self.bind("<Return>", self.on_ret)
class MyButton(tk.Button,MyCtrl):
def __init__(self, master, **kw):
tk.Button.__init__(self, master, **kw)
self.next_widget = None
self.bind("<Return>", self.on_ret)
def add_entry(parent, row, **kwargs):
widget = MyEntry(parent, **kwargs)
widget.grid(row=row, column=0, sticky=tk.NSEW)
return widget
def main(args=None):
root = tk.Tk()
root.title("回车自动转移焦点、触发button")
frame = tk.LabelFrame(root, text="Entries", width=200, height=200)
entries = []
for row in range(5):
e = add_entry(frame, row)
entries.append(e)
# e.set_next(entries[0])
frame.grid_columnconfigure(0, weight=1)
frame.grid_rowconfigure(0, weight=0)
button = MyButton(root, text="Exit", command=root.destroy)
frame.grid(row=0, column=0, sticky=tk.NSEW)
button.grid(row=1, column=0, sticky=tk.SE)
root.grid_columnconfigure(0, weight=1)
root.grid_rowconfigure(0, weight=1)
entries[0].focus_set()
root.mainloop()
if __name__ == '__main__':
main()