Windows 7:如何将窗口置于最前面,无论其他窗口是否具有焦点?

24

我正在实现一个任务栏替代方案,类似于应用程序切换器样式的程序。它在OpenGL和键盘快捷键方面做了一些独特的工作,因此它的设置方式使得窗口并不总是处于焦点状态。我希望实现这样一个功能,即可以将任意窗口置于前台,就像任务栏或ALT-TAB程序一样。

然而,我的代码只会导致应用程序图标在任务栏中闪烁。Windows API文档说这正是应该发生的事情,但我正在寻找一种解决方法。

我从以下示例中改编了我的代码。它们表示,连接到前台线程应该允许您设置前台窗口。以下是这些网站:

http://www.voidnish.com/Articles/ShowArticle.aspx?code=dlgboxtricks

http://invers2008.blogspot.com/2008/10/mfc-how-to-steal-focus-on-2kxp.html

我的代码看起来像这样。请注意,它使用Python的win32包装器(self.hwnd是我想要置于前台的窗口的句柄):

fgwin = win32gui.GetForegroundWindow()
fg = win32process.GetWindowThreadProcessId(fgwin)[0]
current = win32api.GetCurrentThreadId()
if current != fg:
    win32process.AttachThreadInput(fg, current, True)
    win32gui.SetForegroundWindow(self.hwnd)
    win32process.AttachThreadInput(fg, win32api.GetCurrentThreadId(), False)

但是,除非我的窗口是前台窗口(通常不是),否则这只会导致程序的图标闪烁。

我是不是线程附加错了?还有其他方法可以解决这个问题吗?我认为一定有,因为有很多应用程序切换器似乎可以很好地完成这个任务。

我用Python编写了这个程序,但如果在其他语言中有解决方案,我将使用包装器或采取任何必要措施来使其正常运行。

提前感谢您的帮助!

编辑:我可以接受一种只能在我的计算机上工作的方式,即启用一种使任何应用程序都可以获得焦点的方式。


2
好的,但是有很多应用程序可以做到这一点。显然是可能的,我也找到了告诉我如何做的教程。我的问题是,我做了什么导致教程中的代码无法工作? - jmite
3
好的,请提供需要翻译的文章。 - Chris Charabaruk
1
@jmite:这与Raymond所写的完全一致。用户控制自己电脑上的焦点。你是用户,你改变设置,你的电脑就按照你想要的方式运行。只是不要部署能够改变我电脑上该设置的代码。 - Ben Voigt
3
@David 这是有合法使用案例的。例如,如果您想在用户双击某些链接时将焦点从您的应用程序转移到另一个应用程序。如果您正在重新打开“其他”应用程序,则会获取焦点,但是如果它已经在运行且您不想启动新进程,则只需将旧进程调至前台即可。但是,我不会改变用户计算机上的设置。 nspire的答案完美地解决了问题。 - vikki
1
文档在这里 - https://msdn.microsoft.com/en-us/library/windows/desktop/ms724947(v=vs.85).aspx -- 另外,如何将其恢复为默认值?默认值是什么?不是0吗?注意:我之前使用的旧解决方案是像你一样使用AttachThreadInput,但如果用户右键单击Windows任务栏,它会失败,因为我们无法附加到系统线程或其他原因。 - Noitidart
显示剩余5条评论
10个回答

14

我不喜欢使用win32gui的建议,因为你无法通过pip轻松安装它。所以这是我的解决方案:

首先,通过pip安装pywinauto。如果您使用的是Python 2.7.9或2分支上的更新版本,或Python 3.4.0或3分支上的更新版本,则已安装了pip。对于其他人,请更新Python以获得它(如果必须运行旧版本的Python,则可以通过运行此脚本手动下载和安装它。)

只需从命令行(而不是在Python内部)运行此命令:

pip install pywinauto

接下来,从 pywinauto 中导入您所需的内容:

from pywinauto.findwindows    import find_window
from pywinauto.win32functions import SetForegroundWindow

最后,这只是一行实际的代码:

SetForegroundWindow(find_window(title='taskeng.exe'))

3
Python 3.6在Windows上会抛出以下错误:ImportError: cannot import name 'SetForegroundWindow,通过使用from win32gui import SetForegroundWindow进行修复。 - Pedro Lobito
1
@PedroLobito - 我明确表示不要使用 win32gui - 我的解决方案建议使用 pywinauto。你应该从 pywinauto.win32functions 导入 SetForegroundWindow - ArtOfWarfare
5
就目前而言,你的回答在Python 3.6上无法运行。pywinauto 基于 win32gui 进行了大量开发,如果没有它,它将无法正常加载,因此我不明白为什么不使用它。 - Pedro Lobito

10
根据我尝试过的nspire的解决方案,使用Python 2.7和W8,它非常完美地运行,即使窗口被最小化了。
win32gui.ShowWindow(HWND, win32con.SW_RESTORE)
win32gui.SetWindowPos(HWND,win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)  
win32gui.SetWindowPos(HWND,win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)  
win32gui.SetWindowPos(HWND,win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_SHOWWINDOW + win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)

最初是如果窗口没有最小化,但多亏了Whome的评论win32gui.ShowWindow(HWND, win32con.SW_RESTORE),现在在所有情况下都可以工作。

2
在调用SetWindowPos函数之前,您必须还原窗口。Delphi应用程序可以使用“Application.Restore;”函数来取消最小化。查找ShowWindow(handle, SW_RESTORE) Python调用。即使窗口已经显示,每次都调用它也不会有任何影响。 - Whome

8

我有一些代码已经运行了多年,可以追溯到Windows 95。在双击应用程序系统托盘图标时,我总是使用Win32 API函数,如BringWindowToTop和SetForegroundWindow将我的应用程序窗口置于前台。但在Windows 7上,这一切都停止了正常工作,我的输入窗口会被其他窗口遮挡,并且窗口图标会在状态栏上闪烁。我想出的“解决方法”是这样的;它似乎适用于所有版本的Windows。

//-- show the window as you normally would, and bring window to foreground.
//   for example;
::ShowWindow(hWnd,SW_SHOW); 
::BringWindowToTop(hWnd);
::SetForegroundWindow(hWnd);

//-- on Windows 7, this workaround brings window to top
::SetWindowPos(hWnd,HWND_NOTOPMOST,0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);
::SetWindowPos(hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE | SWP_NOSIZE);
::SetWindowPos(hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_SHOWWINDOW | SWP_NOMOVE | SWP_NOSIZE);

1
它将其置于顶部,但键盘焦点不在我们的顶层窗口。 - Noitidart

3
SetForegroundWindow函数的文档解释了这实际上是预期的行为;进程不应该能够“窃取”焦点。然而,您可以调整代码使其仍然工作。
查看LockSetForegroundWindow的备注部分:它解释了:

如果用户按下ALT键,系统会自动启用对SetForegroundWindow的调用[..]

您可以利用此行为,通过使用SendInput函数模拟按下Alt键,然后调用SetForegroundWindow来实现。

3

这很酷,但它并不完美。比如,如果用户在Win10中右键单击Windows任务栏。@jmite的SystemParametersInfo解决方案起作用了。 - Noitidart

3
晚回复,但你可以使用以下方法:
import win32gui
hwnd = win32gui.FindWindowEx(0,0,0, "Window Title")
win32gui.SetForegroundWindow(hwnd)

2
还不算太晚,只有9年:P - jmite
1
现在11 @jmite :P - GoekhanDev

2

这篇回答基于@nspire和@nergeia的内容,并将查找窗口句柄的方法(https://www.blog.pythonlibrary.org/2014/10/20/pywin32-how-to-bring-a-window-to-front/)包装成一个方便的函数:

def raise_window(my_window):

    import win32con
    import win32gui

    def get_window_handle(partial_window_name):

        # https://www.blog.pythonlibrary.org/2014/10/20/pywin32-how-to-bring-a-window-to-front/

        def window_enumeration_handler(hwnd, windows):
            windows.append((hwnd, win32gui.GetWindowText(hwnd)))

        windows = []
        win32gui.EnumWindows(window_enumeration_handler, windows)

        for i in windows:
            if partial_window_name.lower() in i[1].lower():
                return i
                break

        print('window not found!')
        return None

    # https://dev59.com/Qm015IYBdhLWcg3w7wMW

    def bring_window_to_foreground(HWND):
        win32gui.ShowWindow(HWND, win32con.SW_RESTORE)
        win32gui.SetWindowPos(HWND, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)
        win32gui.SetWindowPos(HWND, win32con.HWND_TOPMOST, 0, 0, 0, 0, win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)
        win32gui.SetWindowPos(HWND, win32con.HWND_NOTOPMOST, 0, 0, 0, 0, win32con.SWP_SHOWWINDOW + win32con.SWP_NOMOVE + win32con.SWP_NOSIZE)

    hwnd = get_window_handle(my_window)

    if hwnd is not None:
        bring_window_to_foreground(hwnd[0])


raise_window('Untitled - notepad')

1
这是我让我的工作方式:
import win32gui
from win32con import (SW_SHOW, SW_RESTORE)

def get_windows_placement(window_id):
    return win32gui.GetWindowPlacement(window_id)[1]

def set_active_window(window_id):
    if get_windows_placement(window_id) == 2:
        win32gui.ShowWindow(window_id, SW_RESTORE)
    else:
        win32gui.ShowWindow(window_id, SW_SHOW)
    win32gui.SetForegroundWindow(window_id)
    win32gui.SetActiveWindow(window_id)

运行得非常好。 (2019/08),键盘是活动的。 - Aubergine

0

我看到上面有一些很好的答案,但需要额外的功能,其中窗口名称将是更灵活的参数。如果失败,则返回false:

from win32gui import IsWindowVisible, GetWindowText, EnumWindows,\
ShowWindow, SetForegroundWindow, SystemParametersInfo

#Sub-Functions
def window_enum_handler(hwnd, resultList):
    if IsWindowVisible(hwnd) and GetWindowText(hwnd) != '':
        resultList.append((hwnd, GetWindowText(hwnd)))

#Prime-Functions
def winFocus(partial_window_name):
    SystemParametersInfo(8193, 0, 2 | 1)
    handles=[]
    EnumWindows(window_enum_handler, handles)
    for i in handles:
        if str(partial_window_name).upper() in str(i[1]).upper():
            ShowWindow(i[0], 3)
            SetForegroundWindow(i[0])
            return True
    print(partial_window_name + " was not found")
    return False

winFocus("not existing window")
winFocus("ChroME")

0

使用Python中的pywinauto解决方案

from pywinauto import Application
main_app = Application(backend="uia").connect(title_re=".*Office", control_type="Window")
main_win = main_app.top_window()
main_win.set_focus()

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