There are two parts to this question-how to find the main window of an application launched with ShellExecute
, and how to force that window to the foreground. We'll look at both parts and some code that shows how to ensure the activation will work under all 32-bit operating systems from Microsoft.
Using ShellExecute
to launch an application is convenient, particularly if you want to launch it via one of the documents that it manages. For example, if you need to open a Microsoft Word document, you either do this by specifying the path (either full or just the filename) to the Microsoft Word executable and the path to the document, or you can just specify the document. If the latter is the approach taken, ShellExecute
will look up the application in the registry that is mapped to the extension of the document (in this case a ".doc file) and execute it according to the values in the registry. This is very powerful (let alone convenient) since the user only has to be concerned with the documents and not about the location of the application.
Although the application is launched, ShellExecute
doesn't return any information about the main window handle of the process (it also doesn't return any reliable information about the process ID). For this example, we'll assume that using the SDK function FindWindow
will suffice to find the window. A more advanced technique would require using CreateProcess
to create the application and receive accurate information about the process ID, mapping top-level windows to processes and comparing these processes to the ID of the one that was launched.
The second part of the questionnamely, forcing this window to the foreground-is a bit trickier. Anyone who's been developing Windows applications for a number of years has likely used SDK functions that did one thing under Win16 and changed under Win32; or worse, changed under particular operating system's implementation of Win32. The latter is the situation we find with forcing windows owned by other applications to the foreground.
Normally, you might think to just use the SDK function SetForegroundWindow
to do this. Here is what the July 2004 edition of MSDN has to say about what SetForegroundWindow
does:
"The SetForegroundWindow function puts the thread that created the specified window into the foreground and activates the window. Keyboard input is directed to the window, and various visual cues are changed for the user. The system assigns a slightly higher priority to the thread that created the foreground window than it does to other threads."
Reading through this description carefully, we see that it's the thread that
created the window that is put into the foreground. When launching an application
with ShellExecute
(or CreateProcess
for that matter), it's the
newly created process that owns the window, not your process. So calling
SetForegroundWindow
on the window handle received earlier from the call
to FindWindow
would not work as expected under some operating systems
as we can see below:
"Starting with Microsoft Windows 98 and Windows 2000, the system restricts which processes can set the foreground window. A process can set the foreground window only if one of the following conditions is true:
- The process is the foreground process.
- The process was started by the foreground process.
- The process received the last input event.
- There is no foreground process.
- The foreground process is being debugged.
- The foreground is not locked (see LockSetForegroundWindow).
- The foreground lock time-out has expired (see SPI_GETFOREGROUNDLOCKTIMEOUT in SystemParametersInfo)."
Curious about why this change was made, I did some poking around. It seems
the scuttlebutt is that some of the Microsoft developers in the Systems group
decided that the Microsoft developers in the Office group had overused the SetForegroundWindow
API and put in code to restrict how it could be used. I have no idea whether
this theory is correct or just speculation, but it wouldn't surprise me if it
is true. If any Microsoft developers reading this have any insight, drop me
an e-mail.
However, there is still a way to use SetForegroundWindow
in the "old manner-namely to briefly align the input queues of the threads that own the windows. This is one of those niche corners of the Win32 SDK that many developers haven't explored. When I first discussed this technique with some of the senior developers in my group, they were surprised that input queue alignment was required to support some operating systems when using SetForegroundWindow
.
Let's look at some code to see how to do this:
HWND hOtherWnd = FindWindow( ... ); if (hOtherWnd) { DWORD hMyThread = GetWindowThreadProcessId( m_hWnd,NULL ); DWORD hOtherThread = GetWindowThreadProcessId( hOtherWnd,NULL); AttachThreadInput( hMyThread,hOtherThread,TRUE ); SetForegroundWindow(hOtherWnd); AttachThreadInput( hMyThread,hOtherThread,FALSE); if IsIconic(hOtherWnd) ShowWindow( hOtherWnd,SW_RESTORE ); elses ShowWindow( hOtherWnd,SW_SHOW ); }
Looking through this code, the interesting part is the use of AttachThreadInput
to join two threads together temporarily in order for the messages generated by SetForegroundWindow
to be processed properly by the target window. The first call does the join (by passing in TRUE as the third parameter) and the second call reverses the join. This is one of those seldom used, but very useful functions in the SDK that makes it a snap to do something difficult or impossible to do otherwise. Here is what MSDN has to say about this function:
"Windows created in different threads typically process input independently of each other. That is, they have their own input states (focus, active, capture windows, key state, queue status, and so on), and they are not synchronized with the input processing of other threads. By using the AttachThreadInput function, a thread can attach its input processing to another thread. This also allows threads to share their input states, so they can call the SetFocus function to set the keyboard focus to a window of a different thread. This also allows threads to get key-state information. These capabilities are not generally possible."
So, next time you need to send messages from one application to another and want to ensure that they get processed properly under all versions of Windows, remember to use AttachThreadInput
.
Mark M. Baker is the Chief of Research & Development at BNA Software located
in Washington, D.C. Do you have a Windows development question? Send it to [email protected].