Why Does Enumwindows Return More Windows Than I Expected

Why does EnumWindows return more windows than I expected?

The way to list out only windows in taskbar (or similarly in Alt-Tab box) is described by Raymond in this article on MSDN blog:

Which windows appear in the Alt+Tab list?

And this is the super function to check whether a window is shown in alt-tab:

BOOL IsAltTabWindow(HWND hwnd)
{
TITLEBARINFO ti;
HWND hwndTry, hwndWalk = NULL;

if(!IsWindowVisible(hwnd))
return FALSE;

hwndTry = GetAncestor(hwnd, GA_ROOTOWNER);
while(hwndTry != hwndWalk)
{
hwndWalk = hwndTry;
hwndTry = GetLastActivePopup(hwndWalk);
if(IsWindowVisible(hwndTry))
break;
}
if(hwndWalk != hwnd)
return FALSE;

// the following removes some task tray programs and "Program Manager"
ti.cbSize = sizeof(ti);
GetTitleBarInfo(hwnd, &ti);
if(ti.rgstate[0] & STATE_SYSTEM_INVISIBLE)
return FALSE;

// Tool windows should not be displayed either, these do not appear in the
// task bar.
if(GetWindowLong(hwnd, GWL_EXSTYLE) & WS_EX_TOOLWINDOW)
return FALSE;

return TRUE;
}

Credited to the source code here:

http://www.dfcd.net/projects/switcher/switcher.c

Is the order in which handles are returned by EnumWindows meaningful?

It returns them in Z order. First the top-most window with WS_EX_TOPMOST set, until the bottom-most window with WS_EX_TOPMOST set, then the top-most window without WS_EX_TOPMOST, though to the bottom-most window without WS_EX_TOPMOST. Note that visibility is not a determining factor, so an invisible window that's higher in the Z-order than a visible window will still appear before it.

EDIT:

It's highly unlikely that you could use this as you want, just taking the first return from EnumWindows. Not only is your new window unlikely to be the first return, but you'd have a race condition where other windows could be opened in the meantime. You could, however, keep a list of all known windows for the application, and when you need to find a newly opened window, call EnumWindows and compare the window handles to those in your list. When you find one that has the correct class and caption (you might even check that it belongs to the right process with GetWindowThreadProcessID) that is not in your list, then you've found the new window.

For your purposes, though, you may be even better served by installing a CBT hook and watching for the HCBT_CREATEWND notification. See MSDN help on SetWindowsHookEx() and the CBTProc callback for more information.

Level of certainty about enumeration order:

A number of comments and other answers to this question have mentioned a lack of precise documentation in MSDN about the order in which EnumWindows returns window handles. And indeed, the pages on EnumWindows and the EnumWindowsProc callback are both quite silent on the issue. I offer as evidence the following:

  1. A C++ Q&A article in MSDN magazine does state specifically:

    EnumWindows enumerates the windows in top-down Z-order

  2. The page on EnumChildWindows alludes to the order in the remarks section:

    A child window that is moved or repositioned in the Z order during the enumeration process will be properly enumerated.

    This implies that the order is Z-order dependent. And since, in the description of the hWndParent parameter, it says this:

    If this parameter is NULL, this function is equivalent to EnumWindows.

    one can assume that the same logic and ordering applies to EnumWindows.

  3. This is the observable behavior of this function, which makes it a breaking change to alter it. In general, Microsoft has been very good about not making breaking changes to observable behavior. That's not a guarantee, but it's a pretty safe bet. You're more likely to find that in the next version the function you're using has been deprecated—and replaced with yet another "Ex" version—than to find that its observable behavior has changed.

Of course, this is all very academic at this point, since EnumWindows is probably not the best solution for the OP's problem—at the very least EnumThreadWindows would probably be a better fit—but I thought it was worth mentioning for other people who might come across this post.

EnumWindows does not detect windows

Your callback returns FALSE so EnumWindows() stops enumerating windows. Have it return TRUE instead.

Delphi XE2 EnumWindows not working properly

This declaration is incorrect:

function FindMyWindow(hWnd: HWND; lParam: LPARAM): boolean; stdcall;

It should be:

function FindMyWindow(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

You have to be careful not to mix up Boolean and BOOL since they are not the same thing. The former is a single byte, the latter is 4 bytes. This mismatch between what EnumWindows expects and what your callback function delivers is enough to cause the behaviour you observe.


In addition, Rob Kennedy contributed this excellent comment:

The compiler can help find this error if you get out of the habit of using the @ operator before the function name when you call EnumWindows. If the function signature is compatible, the compiler will let you use it without @. Using @ turns it into a generic pointer, and that's compatible with everything, so the error is masked by unnecessary syntax. In short, using @ to create function pointers should be considered a code smell.


Discussion

Unfortunately the Windows.pas header translation defines EnumWindows in a most unhelpful manner, like this:

function EnumWindows(lpEnumFunc: TFNWndEnumProc; lParam: LPARAM): BOOL; stdcall;

Now, the problem is in the definition of TFNWndEnumProc. It is defined as:

TFarProc = Pointer;
TFNWndEnumProc = TFarProc;

This means that you have to use the @ operator to make a generic pointer, because the function needs a generic pointer. If TFNWndEnumProc were declared like this:

TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

then the compiler would have been able to find the error.

type
TFNWndEnumProc = function(hWnd: HWND; lParam: LPARAM): BOOL; stdcall;

function EnumWindows(lpEnumFunc: TFNWndEnumProc;
lParam: LPARAM): BOOL; stdcall; external 'user32';

function FindMyWindow(hWnd: HWND; lParam: LPARAM): Boolean; stdcall;
begin
Result := False;
end;

....
EnumWindows(FindMyWindow, 0);

The compiler rejects the call to EnumWindows with the following error:

[DCC Error] Unit1.pas(38): E2010 Incompatible types: 'LongBool' and 'Boolean'

I think I will QC this issue and try my luck at persuading Embarcadero to stop using TFarProc.

How to use EnumWindows to get only actual application windows?

Perhaps checking the window's style for a title bar will do what you want:

[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);

static bool IsAppWindow(IntPtr hWnd)
{
int style = GetWindowLong(hWnd, -16); // GWL_STYLE

// check for WS_VISIBLE and WS_CAPTION flags
// (that the window is visible and has a title bar)
return (style & 0x10C00000) == 0x10C00000;
}

But this won't work for some of the fancier apps which are fully custom drawn.

How can I get EnumWindows to list all windows?

It's (as I assumed) not a problem with EnumWindows at all. The problem is with the output stream.

While debugging, I noticed that enumWindowsProc is called just fine for every window, but that some iterations are simply not generating output.

For the time being, I switched to using _tprintf, but I don't understand what the problem with the original code is. Calling wcout.flush() had no desirable effect either.



Related Topics



Leave a reply



Submit