【问题标题】:Failure of tkinter .focus_set()tkinter .focus_set() 失败
【发布时间】:2014-11-30 13:25:37
【问题描述】:

下面的代码是一个函数的一部分,它产生一个简单的对话框,调用者选择的文本和按钮。

问题在于对“s”、“a”、“d”和“c”的键输入的处理。该代码对于鼠标单击和 Tab 键后跟空格键或 Enter 键操作正确。该代码是用 Python 3.4 编写的,并且正在测试是否符合 Windows 7。

我的理解是 Tk 为最终用户的鼠标点击处理重新聚焦。在用户通过 Tab 键改变焦点后使用空格键和回车键。对于所有这些交互,键都通过代码绑定到每个按钮:

for action in ('<1>', '<space>', '<Return>'): b.bind(action, handler)

对于“下划线”键输入,我相信我的代码需要在调用处理程序之前处理重新聚焦。这是重新聚焦例程的目的。每当按下 's'、'a'、's' 或 'c' 时,打印语句(“重新聚焦到...”)都会打印正确的 button.winfo_name() 值。这就是我认为button.focus_set() 失败的原因。

如果它有效,这将使处理程序能够通过查看event.widget.winfo_name() 来简单地检查按下了哪个按钮。事实上,重新聚焦失败意味着在event.widget.winfo_name()中使用错误的按钮调用了处理程序

如果我通过制表键手动移动焦点,那么无论按下“s”、“a”、“d”还是“c”中的哪一个,焦点都会给出event.widget.winfo_name() 返回的名称。

在阅读了 Stack Overflow 上的其他帖子后,我尝试在 button.focus_set() 之后添加 button.focus_force()。这对问题没有影响。

我尝试通过将处理程序的签名更改为 def button_handler(event, *args) 然后将 refocus 的最后一行更改为 handler(event, button.winfo_name()) 来传递按钮名称,但 *args 在调用时为空.

def refocus_wrapper(button):
    def refocus(event):
        print("refocusing to '{}'".format(button.winfo_name()))
        button.focus_set()
        handler(event)
    return refocus

for button_text, underline, button_name in buttons:
    b = ttk.Button(button_inner_frame, text=button_text, name=button_name,
                   underline=underline)
    b.pack(padx=2, side='left')
    for action in ('<1>', '<space>', '<Return>'):
        b.bind(action, handler)
    action = '{}'.format(button_text[underline:underline + 1].lower())
    dialog.bind(action, refocus_wrapper(b))
    if not default or default == button_name:
        default = button_name
        b.focus_set()

【问题讨论】:

  • acds 键上设置绑定的代码在哪里?
  • @BryanOakley。 dialog.bind(action, refocus_wrapper)。最终用户可以看到按钮文本,因此它来自可以轻松国际化的外部配置文件。带下划线的加速键取决于配置文件的语言。例如,由于我不知道梵语中的“取消”是什么样的,也不知道哪个字母将用作加速键,所以下划线也在外部配置文件中。

标签: python python-3.x tkinter tk


【解决方案1】:

您最初关于需要将焦点设置在按钮上的假设是不正确的。在 Tk 中处理这个问题的常用方法是在对话框顶层表单上绑定加速键事件,并为事件绑定调用按钮调用方法。

在 Tcl/Tk 中:

toplevel .dlg
pack [button .dlg.b -text "Save" -underline 0 -command {puts "Save pressed"}]
bind .dlg <Alt-s> {.dlg.b invoke}

因此,将按键事件绑定到按钮的父级顶层。如果那是一个对话框,那么它的父级应该是应用程序顶级小部件。

python 等价物是:

from tkinter import *
main = Tk()
dialog = Toplevel(main)
button = Button(dialog, text="Send", underline="0", command=lambda: print("hello"))
button.pack()
dialog.bind('<Alt-s>', lambda event: button.invoke())
main.mainloop()

键绑定将一个事件对象附加到回调函数中,我们可以使用 lambda 将其丢弃,以包装按钮的调用方法。

【讨论】:

  • 这对我不起作用。我在dialog.bind('&lt;Alt-s&gt;', b.invoke) 中尝试了我认为是Python 的等价物。这会产生“TypeError:invoke() 接受 1 个位置参数但给出了 2 个”。我还尝试了产生相同错误的完整路径名dialog.children['button_outer_frame'].children[ 'button_inner_frame'].children[str(button_name)].invoke。代码dialog.button_outer_frame.button_inner_frame.b.invoke 不会运行,因为button_outer_frame 不是对话框的属性。
  • invoke 方法不接受任何参数,但键绑定会自动附加一些内容。您可以使用 lambda 来解决此问题。请参阅上面的编辑。
  • 类型错误已消失,但键 's'、'a'、'd' 和 'c' 无任何作用。我看不出他们将如何考虑回调函数handler 必须绑定到对话框。在我的“工作”回答中,这通过调用 refocus 间接发生。
  • @lemi57ssss:它之所以有效,是因为每个小部件都有其顶级父级作为其绑定标签的一部分。粗略地说,它“继承”了顶层的所有绑定(“继承”并不是字面上会发生的事情,但对于这个讨论来说已经足够接近了)。如果将&lt;Alt-s&gt; 绑定到顶级小部件,则无论对话框中的哪个小部件具有键盘焦点,绑定都会触发。您根本不需要重新调整焦点。
  • @lemi57ssss:十个按钮都可以使用lambda,只需要正确使用lambda即可。这个网站上有几个与此相关的答案。你确实不需要需要在这些绑定中设置焦点,你确实需要为每个特殊键正确定义一个绑定,除了空格键和鼠标点击。绑定都可以在顶层窗口中,因为按钮将“看到”这些绑定。
【解决方案2】:

focus_set() 当然可以完美运行。我期望重新聚焦会重写事件对象是错误的。以下代码(经过广泛修改以纳入 Brian Oakley 的建议)产生了预期的结果。

def _make_buttons(dialog, buttons, buttonbox, default, handler):
    def bcommand_wrapper(button):
        def bcommand():
            name = button.winfo_name()
            dialog.destroy()
            handler(name)
        return bcommand

    for button_text, underline, button_name in buttons:
        b = ttk.Button(buttonbox, text=button_text, underline=underline,
                       name=button_name)
        b.configure(command=bcommand_wrapper(b))
        b.pack(padx=2, side='left')
        action = button_text[underline:underline + 1].lower()
        try:
            dialog.bind(action, lambda event, b=b: b.invoke())
        except tk.TclError:
            raise ValueError(
                "Invalid underline of '{}' in position {}. Character '{}'.".
                format(button_text, underline, action))
        b.bind('<Return>', lambda event, b=b: b.invoke())
        if not default or default == button_name:
            default = button_name
            b.focus_set()

我放弃了将 Tk 事件返回给调用者的最初想法。这是一个对话框,因此调用者只需要单击按钮的名称即可。

请注意,我没有使用“Alt”修饰符来捕获加速键。至少在 MS Windows 上,“Alt”键在与加速键一起使用时是一个功能键:它会导致菜单上显示下划线。这里的下划线是静态的,所以使用“Alt”键是不合适的。

【讨论】:

  • 这太复杂了。您不需要绑定到&lt;1&gt; 等。tkinter 为您处理焦点。
  • 我可以删除 和 在没有显式绑定的情况下不起作用。
  • 是的,你需要绑定到&lt;Return&gt;。您不必使绑定设置焦点,只需使其执行默认行为即可。
【解决方案3】:

我是否可以建议使用 root.bind('c', ) 让快捷方式执行您想要的操作?

确保只在弹出窗口时绑定它们,完成后解除绑定。

【讨论】:

  • 感谢您的想法。快捷方式正确绑定。 print 语句证明了这一点。我不清楚如何将它们重新绑定到不同的小部件来解决焦点问题。
  • @lemi57ssss 因为根绑定是全局绑定的,这就是为什么确保仅在必要时绑定它并在完成时解除绑定很重要的原因。我不会撒谎,这可能不是最好的方法,但它既易于实施,又可以在有人提出更好的解决方案之前一直有效,但遗憾的是我无法提供。
猜你喜欢
  • 2016-07-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-19
  • 2013-11-02
相关资源
最近更新 更多