Tkinter主窗口焦点

23

我有以下代码

window = Tk()
window.lift()
window.attributes("-topmost", True)

这段代码能够显示我的Tkinter窗口在其他所有窗口的上面,但它只解决了问题的一半。虽然窗口确实显示在其他所有窗口的上面,但是它并没有获得焦点。有没有办法让窗口不仅成为Tkinter中最前面的窗口,还能将焦点放在它上面?


你为什么认为它没有焦点?你尝试在窗口上调用 focus_force 吗? - Bryan Oakley
3
我认为这个窗口没有焦点,因为它是灰色的。我必须点击一下它才能识别按键。另外,“focus_force”无法解决这个问题。 - Python Spoils You
@marczellm,您能指明问题吗?我在Python 3.6和Windows 10上运行了代码,它可以正常工作。 - Tarun Lalwani
@TarunLalwani 具体来说,我想让我的Tkinter窗口从所有应用程序中夺取全局焦点,并将其设置为一个Entry字段。该窗口使用“-topmost”出现在所有窗口的顶部,但是没有任何方法可以将焦点放在Entry字段上 - 先前活动的应用程序仍然是活动/聚焦的窗口。 - marczellm
请提供您使用的示例测试应用程序。还需要提供Python版本以及如何启动脚本?@marczellm - Tarun Lalwani
@marczellm,请检查我发布的代码。对我来说非常有效。 - Tarun Lalwani
6个回答

18

如果focus_force()不起作用,你可以尝试执行以下操作:

window.after(1, lambda: window.focus_force())

本质上是同一件事,只是用不同的方式书写而已。 我刚在Python 2.7上测试过它。

root.focus_force()不能工作,但上述方法可以。


你用的是什么操作系统?(我在使用OSX) - Python Spoils You
@PythonSpoilsYou 抱歉回复晚了,但我使用的是Windows系统。我刚刚重新测试了一下,似乎只有一个代码可以运行,而另一个则不行。 - Brian Fuller
你有任何想法为什么吗? - Python Spoils You
1
@PythonSpoilsYou 不,我一点头绪也没有。这让我感到困惑。而且你使用的是OSX系统,这让我更加无从下手。你可以尝试进行一些艰苦的搜索,比如“如何使tkinter窗口在OSX上获得焦点”。祝你好运。 - Brian Fuller
@BrianFuller 我遇到了一个稍微不同的问题。我已经尝试了以下三种方法来将窗口置于焦点 - window.focus_force()window.wm_attributes("-topmost" , -1)以及你提供的答案window.after(1, lambda : window.focus_force())。在这三种方式中,当我运行程序时,窗口确实会立即获得焦点,但是它自己会在1秒后最小化(我既没有点击任何地方,也没有将其最小化)。为什么?我使用的是Windows 8。 - Nancy
太好了!谢谢。那个不关注TK窗口的问题折磨了我很久! - Apostolos

14

在Windows上的解决方案有些棘手 - 你不能仅仅从另一个窗口夺取焦点,你应该以某种方式将你的应用程序移至前台。但是首先,我建议您消化一下一点点的Windows理论,这样我们就能确认我们要实现的目标。

正如我在评论中提到的一样 - 这是使用SetForegroundWindow函数的好机会(也要检查限制!)。但是请考虑这样的东西是一种廉价的hack,因为用户“拥有”前台,而Windows会尽全力阻止你:

当用户正在使用另一个窗口时,应用程序无法强制将窗口置于前景。相反,Windows会闪烁窗口的任务栏按钮以通知用户。

此外,请查看页面上的注释:

如果用户按下ALT键或采取一些导致系统本身更改前景窗口的操作(例如,单击后台窗口),系统会自动启用对SetForegroundWindow的调用。

这里有一个最简单的解决方案,因为我们可以模拟Alt按键:

import tkinter as tk
import ctypes

#   store some stuff for win api interaction
set_to_foreground = ctypes.windll.user32.SetForegroundWindow
keybd_event = ctypes.windll.user32.keybd_event

alt_key = 0x12
extended_key = 0x0001
key_up = 0x0002


def steal_focus():
    keybd_event(alt_key, 0, extended_key | 0, 0)
    set_to_foreground(window.winfo_id())
    keybd_event(alt_key, 0, extended_key | key_up, 0)

    entry.focus_set()


window = tk.Tk()

entry = tk.Entry(window)
entry.pack()

#   after 2 seconds focus will be stolen
window.after(2000, steal_focus)

window.mainloop()

一些链接和例子:


警告:运行此代码后,我的电脑开始表现异常(例如键盘无法工作),即使退出程序也是如此。不得不重新启动才能使一切恢复正常。 - Sebastian Nielsen
@Sebastian Nielsen,如果唯一的问题是键盘,则这并不奇怪,但如果您在中途中断steal_focus,则有可能。无论如何,keybd_event早已被取代,因此也许您应该围绕最新的SendInput构建解决方案,但我无法重现您所描述的行为。 - CommonSense
这个解决方案是唯一可行的,但是如何使用 SendInput 代替 keybd_event? - Elijah
@Eli,试试这个:https://dev59.com/UE0GtIcB2Jgan1znti1N#62205935 - CommonSense
太棒了!我通过发送"alt"键按下成功让它工作了。我使用了"keyboard"库,然后在窗口执行"focus_force()"之前使用了Keyboard.press_and_release('alt')。 - Sandeep S D

5

不确定您面临的问题是什么。但是以下是我使用过的示例代码,效果非常好。

from tkinter import *

window = Tk()

def handle_focus(event):
    if event.widget == window:
        window.focus_set()
        input1.focus_set()


label1 = Label(window,text = "Enter Text 2")
input1 = Entry(window, bd=5)

label2 = Label(window,text = "Enter Text 2")
input2 = Entry(window, bd=5)

submit = Button(window, text="Submit")

label1.pack()
input1.pack()
label2.pack()
input2.pack()
submit.pack(side=BOTTOM)

window.lift()
window.attributes("-topmost", True)

window.bind("<FocusIn>", handle_focus)

hwnd = window.winfo_id()

window.mainloop()

这是在Windows 10上使用最新的Python 3.6进行测试的。

测试结果

运行程序后,结果是我只需开始键入,输入就会进入第一个文本框。


我想要做的是,我的应用程序(响应全局键盘事件或定时事件)从其他应用程序(如Firefox)那里窃取焦点。 - marczellm
@marczellm,不幸的是,你的需求并不是关于从另一个应用程序中窃取焦点(至少在win上),而是要替换一个[foreground](https://msdn.microsoft.com/en-gb/library/windows/desktop/ms633539(v=vs.85).aspx)窗口(例如firefox)为另一个(你的tk应用程序)。这并不像看起来那么简单,所以这里有一个[类似的问题](https://dev59.com/RoTba4cB1Zd3GeqP2jK9)。 - CommonSense
@Tarun Lawani,谢谢你。这种方法帮助我解决了一个在MacOS上运行脚本时无法正常捕获焦点的问题,其他方法都不起作用。 - Cam U

5
注意:这是特定于Windows的。这将聚焦于整个主窗口,实际上是强制进行alt-tab以到达该窗口。
对我来说,这些答案都没有用,使用-topmost标志时,tk窗口会出现在所有东西的顶部,我的输入框会获得焦点,但窗口本身不会,这意味着在tk窗口中无法输入。它看起来像这样:

enter image description here

添加一个调用Windows API来窃取焦点有所帮助:
import win32gui    
root = tk.Tk()

# Application specific setup ...

win32gui.SetForegroundWindow(root.winfo_id())

免责声明:窃取焦点是不良行为,所以只有在必要时并且有意义时才使用它,例如对于用户将立即输入的输入框。


也许这是不好的行为,但这是我解决问题所需要的。谢谢! - undefined

1

对我而言有效。我正在使用Macos Mojave,Python 3.6.4。在root.mainloop()之前放置此代码即可。

import os
os.system("open -a Python")

当您打开已经打开的应用程序时,该应用程序将获得焦点。

0

对于Linux(Ubuntu 16.04.6 LTS),我尝试了许多建议,包括使用.grab,但这会导致问题。最终,我使用了以下方法:

if self.top2_is_active is True: # Are we already playing songs?
    self.top2.focus_force()     # Get focus
    self.top2.lift()            # Raise in stacking order
    root.update()
    return                      # Don't want to start playing again

这并不是真正的“窃取”焦点,因为我在主窗口上定义了一个按钮:

''' ▶  Play Button '''
self.play_text="▶  Play"                # play songs window is opened.
self.listbox_btn2 = tk.Button(frame3, text=self.play_text, \
                    width=BTN_WID, command=self.play_items)
self.listbox_btn2.grid(row=0, column=1, padx=2)

点击此按钮后,文本将更改为:

self.listbox_btn2 ["text"] = "  Show playing"     # Play button

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