20
votes

I'm implementing a task-bar replacement, dock-like application-switcher style program. It's doing some unique stuff with OpenGL, and with keyboard shortcuts, so the way it's set up, the window doesn't always have focus. I'd like to implement it such that I can bring an arbitrary window to the foreground, much like a taskbar or an ALT-TAB program would.

However, my code simply causes the application icon to flash in the taskbar. The Windows API documentation says that this is what is supposed to happen, but I'm looking for a way to work around this.

I've adapted my code from the following examples, which say that attaching to the foreground thread should allow you to set the foreground window. Here are the sites:

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

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

My code looks like this. Note that it's using the win32 wrappers for python (self.hwnd is the handle of the window I want to bring to the front):

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)

However, unless my window is the foreground window (which it isn't usually), this just causes the program's icon to flash.

Am I doing the thread attaching wrong? Is there another way to work around this? I figure there must be, as there are lots of application switchers out there that seem to be able to do this just fine.

I'm writing this in python, but if there is a solution in another language I will use wrappers or do whatever is necessarry to get this up and running.

Thanks in advance!

EDIT: I'd be open to a way to make it work only on my particular computer, i.e. a way to enable, on my machine, a way for any application to take focus.

9
Okay, but there are plenty of applications which are able to do it. It's clearly possible, and I've found tutorials telling me how. My question is, what am I doing which is causing the code from the tutorial not to work?jmite
That's what I've done. The code I posted is almost the exact code from the tutorial, in a python wrapper (see for yourself at the two links I posted). I'm asking if anybody can tell why my code isn't doing what the pages say it will do, or if there's a better way to do it.jmite
I'm literally rewriting the taskbar in a way that makes that guideline completely insufficient. In any case, the solution was posted above, it's one line of code, so it's clearly not something the Windows API is going too far to prevent you from doing.jmite
@David There are legitimate use cases for this. For example if you want to pass focus from your application to another when a user double clicks some link. If your opening the "other" application afresh then it will take focus, but if it's already running and you don't want to start a new process, you just bring the old one to the front. However I wouldn't go as far as changing a setting on the user's computer. nspire's answer works perfectlyvikki

9 Answers

7
votes

I've had some code that's been running for years, going all the way back to Windows 95. When double clicking the applications system tray icon I always used Win32 API functions such as BringWindowToTop and SetForegroundWindow to bring my application windows to the foreground. This all stopped working as intended on Windows 7, where my input window would end up behind other windows and the window icon would flash on the status bar. The 'work around' that I came up with was this; and it seems to work on all versions of 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);
11
votes

I don't like these suggestions of using win32gui because you can't easily install that via pip. So here's my solution:

First, install pywinauto via pip. If you're on Python 2.7.9 or a newer version on the 2 branch, or Python 3.4.0 or a newer version from the 3 branch, pip is already installed. For everyone else, update Python to get it (or you can manually download and install it by running this script, if you must run an older version of Python.)

Just run this from the command line (not from within Python):

pip install pywinauto

Next, import what you need from pywinauto:

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

Finally, it's just one actual line:

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

According to nspire, I've tried his solution with python 2.7 and W8, and it works like a charm, even if the window is minimized *.

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)
  • Originally it was if the window it's not minimized, but thanks to Whome's comment win32gui.ShowWindow(HWND, win32con.SW_RESTORE), it now works in all situations .
3
votes

If you're implementing hotkeys, use RegisterHotKey. As Raymond Chen puts it (companion blog article to the one already linked by Chris), "Pressing a registered hotkey gives you the foreground activation love".

1
votes

The documentation for the SetForegroundWindow function explains, that this is actually the intended behaviour; processes shouldn't be able to "steal" the focus. However, it's possible to adjust your code so that it works anyway.

Have a look at the remark section of LockSetForegroundWindow: it explains

The system automatically enables calls to SetForegroundWindow if the user presses the ALT key[..]

You can exploit this behaviour by making your program simulate pressing the Alt key using the SendInput function before calling SetForegroundWindow.

1
votes

This is how I got mine working:

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)
1
votes

Late answer, but you can use:

import win32gui
hwnd = win32gui.FindWindowEx(0,0,0, "Window Title")
win32gui.SetForegroundWindow(hwnd)
0
votes

I saw some great answers above, but needed extra functionality where window name would be a more flexible parameter. On failure it returns 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
votes

This answer builds on @nspire and @nergeia above, and wraps in a method to find the window handle (https://www.blog.pythonlibrary.org/2014/10/20/pywin32-how-to-bring-a-window-to-front/) into one convenience function:

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://stackguides.com/questions/6312627/windows-7-how-to-bring-a-window-to-the-front-no-matter-what-other-window-has-fo

    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')