Tkinter的overrideredirect在Mac和Linux中阻止了某些事件。

13

我正在使用Python和Tkinter UI编写程序。我想要一个没有标题栏的小窗口。该窗口必须接收键盘输入。无论是以Entry部件的形式还是绑定到KeyPress事件。通常使用overrideredirect(True)来禁用标题栏。但是,不幸的是,(除了Windows),这似乎会阻止许多事件被接收。我编写了以下代码来说明问题:

#!/usr/bin/env python
from __future__ import print_function
import Tkinter

class AppWindow(Tkinter.Tk):
    def __init__(self, *args, **kwargs):
        Tkinter.Tk.__init__(self, *args, **kwargs)
        self.overrideredirect(True)
        self.geometry("400x25+100+300")

        titleBar = Tkinter.Frame(self)
        titleBar.pack(expand = 1, fill = Tkinter.BOTH)

        closeButton = Tkinter.Label(titleBar, text = "x")
        closeButton.pack(side = Tkinter.RIGHT)
        closeButton.bind("<Button-1>", lambda event: self.destroy())

        self.bind("<KeyPress>", lambda event: print("<KeyPress %s>" % event.char))
        self.bind("<Button-1>", lambda event: print("<Button-1>"))
        self.bind("<Enter>", lambda event: print("<Enter>"))
        self.bind("<Leave>", lambda event: print("<Leave>"))
        self.bind("<FocusIn>", lambda event: print("<FocusIn>"))
        self.bind("<FocusOut>", lambda event: print("<FocusOut>"))

if __name__ == "__main__":
    app = AppWindow()
    app.mainloop()

这会创建一个小窗口(没有标题栏),当它接收到常见事件时会打印出它们的名称。我已经在Windows 7、Mac OSX(El Capitan)和Ubuntu 14.04.1上运行了此脚本。我只在虚拟机(VMWare)中运行了Ubuntu。
  • 在Windows中,这似乎按预期工作。所有我的代码测试的事件都可以被接收。

  • 在Ubuntu中,Tkinter窗口如预期地接收<Enter><Leave><Button-1>事件,但<KeyPress><FocusIn><FocusOut>从未被接收。事实上,即使窗口已经被单击,仍然会继续将焦点保留在最后一个具有焦点的窗口上。

  • 在OSX中,Tkinter窗口如预期地接收<Button-1>事件,但<KeyPress><FocusIn><FocusOut>从未被接收。最后一个具有焦点的窗口不会像在Ubuntu中那样继续接收键盘输入。<Enter><Leave>事件的行为有些奇怪。只有在单击窗口后,才会接收到<Enter>事件。然后,一旦发生了<Leave>事件,需要再次单击窗口才能接收到另一个<Enter>事件。

我还尝试了在__init__函数结束前加入self.focus_force()。这会导致程序启动时窗口接收到一个<FocusIn>事件,但从未接收到进一步的<KeyPress><FocusIn><FocusOut>事件。

最终,我的问题是这样的:是否有任何方法可以在OSX和Linux中隐藏标题栏但继续接收键盘输入?


我知道还有其他几个与此相同问题有关的问题。在这三个问题中:

接受的答案是使用self.attributes('-fullscreen', True),但这对我来说不起作用,因为我想要一个小窗口,而不是全屏应用程序。
还有一个问题:Tkinter overrideredirect no longer receiving event bindings。这似乎非常接近我的问题,但提供的细节较少,没有答案。
更新:我一直在尝试调查我的问题的基本机制。我知道Tkinter是Tcl/Tk的包装器,所以我想尝试用Tcl重写我的代码。我不太了解Tcl,但我认为我已经成功(或多或少)地将我的Python代码翻译成了Tcl。
#!/usr/bin/env wish
wm overrideredirect . True
wm geometry . "400x25+100+300"
bind . <KeyPress> {puts "<KeyPress %K>"}
bind . <Button-1> {puts "<Button-1>"}
bind . <Enter> {puts "<Enter>"}
bind . <Leave> {puts "<Leave>"}
bind . <FocusIn> {puts "<FocusIn>"}
bind . <FocusOut> {puts "<FocusOut>"}

我在Windows和Mac OSX中尝试了生成的程序。在Windows中,我收到了<KeyPress>事件,但在OSX中没有收到。没有wm overrideredirect . True这行命令,OSX就可以接收<KeyPress>事件。因此,看起来问题不在于Python,而是在于Tcl/Tk。


禁用 mainloop如何再次访问它? 绑定(键监听)仅适用于子元素。如果 listener 被冻结,您将获得一个 ghost 应用程序。 - dsgdfg
一个技巧:将您的[X]按钮作为主循环(因此deiconify())! - dsgdfg
1个回答

4
我已向Tk提交了一个关于这种情况的错误报告。
您可以使用devilspie程序从窗口中移除装饰。使用wm title . myname命令为您的窗口指定一个特定的名称,并在下面的devilspie配置片段中使用该名称。从您的程序中删除overrideredirect命令。
我已经测试过(作为Tk程序),未装饰的窗口仍将接收按键等绑定。
请注意,devilspie被编写为守护进程并保持活动状态。启动后,可以杀死守护进程,所做的窗口更改仍将生效。或者它可以保持运行状态,任何时候激活您的窗口,都将应用devilspie配置。
(if (is (application_name) "t.tcl")
   (begin (undecorate)))

错误...那是针对Linux的。我不知道Mac是否有相应的程序。 - Brad Lanam
谢谢,对我来说完美无缺(使用LXDE的Archlinux)。 - j_4321
一个带有overrideredirect的窗口被故意阻止获取焦点,因此无法获得键盘事件。 http://core.tcl.tk/tk/artifact/7892c68f49012d2d71222ae0e312a1e7dc69a801?txt=1&ln=51-64那行代码和注释很古老了,但可能可以删除。不太确定。 - PeterS
我为终端仿真器设置了overrideredirect=1(在Archlinux中使用xdotool),我注意到即使在这种情况下,窗口也无法获得焦点。因此,这似乎不是Tcl/Tk的特定情况。 - j_4321

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接