Tcl/Tk:在法语键盘上触发的菜单栏加速器

Tcl/Tk: menu bar accelerators triggered on French keyboards

这让我们困惑了一段时间: 在我们的 Tcl/Tk 应用程序中,我们使用菜单项的键盘快捷键来触发某些操作。

基本上我们在菜单里设置了一个-accelerator。根据 documentation,这是为了“仅显示”,因此我们还为与匹配菜单相同的命令添加了一些键盘绑定。

因为我们有很多这样的快捷键,简单的修改键绑定(例如 Control+n )已经不够了,我们开始使用多个修饰符(例如 Control+Shift+n)。

代码是这样的:


# case insensitive version 'bind' - in case the user pressed CapsLock
proc bind_capslock {tag seq_prefix seq_nocase script} {
    bind $tag <${seq_prefix}-[string tolower ${seq_nocase}]> $script
    bind $tag <${seq_prefix}-[string toupper ${seq_nocase}]> $script
}

if { [tk windowingsystem] == "aqua" } {
    set modifier "Mod1"
    set accelerator "Command"
} else {
    set modifier "Control"
    set accelerator $modifier
}

menu .menubar
. configure -menu .menubar
menu .menubar.edit
.menubar add cascade -menu .menubar.edit -label Edit

.menubar.edit add command -label "Test Me" \
    -command {puts "you menued $accelerator-Shift-N"} \
    -accelerator "${accelerator}-Shift-N"

bind_capslock all $modifier-Shift-Key N {puts "you pressed $modifier-Shift-N"}

通常,菜单 -command 与绑定 script 相同(这里有所不同以证明我的观点)。

现在,上面的代码在 Linux、Windows 和 macOS 上运行良好:

万岁!

不幸的是,这似乎不适用于法语键盘布局的 macOS:

因此,无论出于何种原因,指定为菜单 -accelerator 触发器 的快捷方式,即使文档明确提到 -accelerator 是 [=44] =]仅供展示.

由于两个动作调用的 commands/scripts 完全相同,我们最终会重复调用该命令!

我们做错了什么?为什么此行为仅在法语键盘布局(而不是美国或德语布局)上触发,并且仅在 macOS 上触发(而不是 Linux 或 Windows)? 最重要的是:我们如何解决这个问题? (我们怎样才能快速修复?正确的修复方法是什么?)

问题出现在不久前 (Tcl/Tk 8.5),但在 Tcl/Tk-8.6.12 中仍然存在。 macOS 是 10.15 (Catalina),但其他版本也受到影响。

问题是菜单在 macOS 上的工作方式与 Tk 真正期望的不同。特别是,我们使用 NSMenuItem 并立即设置一个选择器(即调用回调处理程序)。但是如果设置了加速器,那么菜单也会在事件真正到达Tk之前捕获加速器描述的键序列,然后also将事件传递给我们;这是本机 GUI 行为,无法真正修复。 (macOS 上的 Tk 已经 方式 比理智更深入地了解本机 GUI 的内容。)

最简单的事情就是不要在 macOS 上放置这些绑定。

否则……将事件处理推迟到空闲事件处理程序。像那样,您可以用少量代码实现简单的第一个事件处理程序获胜规则。您指定的事件处理程序稍晚运行,但这对于与键盘绑定或菜单回调相关的任何事情都无关紧要!

# Most of your code doesn't need to change

set ::currentMenuAction {}
proc scheduleMenuAction script {
    global currentMenuAction
    if {$currentMenuAction eq ""} {
        # Prepend a command to clear the variable
        set act "set ::currentMenuAction {};$script"
        set currentMenuAction [after idle $act]
    }
}

# I don't like using lots of backslashes; your mileage may vary
.menubar.edit add command -label "Test Me" -command {
    scheduleMenuAction {puts "you menued $accelerator-Shift-N"}
} -accelerator "${accelerator}-Shift-N"

bind_capslock all $modifier-Shift-Key N {
    scheduleMenuAction {puts "you pressed $modifier-Shift-N"}
}