Enumerate windows like alt-tab does
Asked Answered
A

4

36

I'm creating an alt-tab replacement for Vista but I have some problems listing all active programs.

I'm using EnumWindows to get a list of Windows, but this list is huge. It contains about 400 items when I only have 10 windows open. It seems to be a hwnd for every single control and a lot of other stuff.

So I have to filter this list somehow, but I can't manage to do it exactly as alt-tab does.

This is the code I use to filter the list right now. It works pretty well, but I get some unwanted windows like detached tool-windows in Visual Studio and I also miss windows like iTunes and Warcraft3.

private bool ShouldWindowBeDisplayed(IntPtr window)
{
    uint windowStyles = Win32.GetWindowLong(window, GWL.GWL_STYLE);

    if (((uint)WindowStyles.WS_VISIBLE & windowStyles) != (uint)WindowStyles.WS_VISIBLE ||
        ((uint)WindowExStyles.WS_EX_APPWINDOW & windowStyles) != (uint)WindowExStyles.WS_EX_APPWINDOW)
    {
        return true;
    }
    return false;
}
Ashantiashbaugh answered 16/10, 2008 at 22:21 Comment(0)
C
27

Raymond Chen answered this a while back
(https://devblogs.microsoft.com/oldnewthing/20071008-00/?p=24863):

It's actually pretty simple although hardly anything you'd be able to guess on your own. Note: The details of this algorithm are an implementation detail. It can change at any time, so don't rely on it. In fact, it already changed with Flip and Flip3D; I'm just talking about the Classic Alt+Tab window here.

For each visible window, walk up its owner chain until you find the root owner. Then walk back down the visible last active popup chain until you find a visible window. If you're back to where you're started, then put the window in the Alt+Tab list. In pseudo-code:

BOOL IsAltTabWindow(HWND hwnd)
{
 // Start at the root owner
 HWND hwndWalk = GetAncestor(hwnd, GA_ROOTOWNER);

 // See if we are the last active visible popup
 HWND hwndTry;
 while ((hwndTry = GetLastActivePopup(hwndWalk)) != hwndTry) {
  if (IsWindowVisible(hwndTry)) break;
  hwndWalk = hwndTry;
 }
 return hwndWalk == hwnd;
}

Follow the link to Chen's blog entry for more details and some corner conditions.

Catechize answered 16/10, 2008 at 22:26 Comment(3)
Note that this implementation doesn't honor the WS_EX_TOOLWINDOW and WS_EX_APPWINDOW extended styles mentioned in Raymond's blog post.Rieger
Here's a more complete and robust example based on this method github.com/christianrondeau/GoToWindow/blob/… It's from an alternative alt-tab utility, which seems to show exactly what regular alt-tab menu does.Clipboard
To detect UWP apps check 'ApplicationFrameWindow' window classname then evaluate it with DwmGetWindowAttribute(LHWindow, DWMWA_CLOAKED, @cloaked, sizeof(Cardinal)) if cloacked (integer) returns 0 it means the UWP application is not hidden.Grady
A
13

Thanks Mike B. The example from Raymonds blog pointed me in the correct direction.

There are however some exceptions that has to be made, Windows Live messenger got alot of hacks for creating shadows under windows etc :@

Here is my complete code, have been using it for one day now and havn't noticed any differences from the real alt tab. There's some underlying code not posted but it's no problem figuring out what it does. :)

    private static bool KeepWindowHandleInAltTabList(IntPtr window)
    {
        if (window == Win32.GetShellWindow())   //Desktop
            return false;

        //https://mcmap.net/q/421952/-enumerate-windows-like-alt-tab-does
        //http://blogs.msdn.com/oldnewthing/archive/2007/10/08/5351207.aspx
        //1. For each visible window, walk up its owner chain until you find the root owner. 
        //2. Then walk back down the visible last active popup chain until you find a visible window.
        //3. If you're back to where you're started, (look for exceptions) then put the window in the Alt+Tab list.
        IntPtr root = Win32.GetAncestor(window, Win32.GaFlags.GA_ROOTOWNER);

        if (GetLastVisibleActivePopUpOfWindow(root) == window)
        {
            WindowInformation wi = new WindowInformation(window);

            if (wi.className == "Shell_TrayWnd" ||                          //Windows taskbar
                wi.className == "DV2ControlHost" ||                         //Windows startmenu, if open
                (wi.className == "Button" && wi.windowText == "Start") ||   //Windows startmenu-button.
                wi.className == "MsgrIMEWindowClass" ||                     //Live messenger's notifybox i think
                wi.className == "SysShadow" ||                              //Live messenger's shadow-hack
                wi.className.StartsWith("WMP9MediaBarFlyout"))              //WMP's "now playing" taskbar-toolbar
                return false;

            return true;
        }
        return false;
    }

    private static IntPtr GetLastVisibleActivePopUpOfWindow(IntPtr window)
    {
        IntPtr lastPopUp = Win32.GetLastActivePopup(window);
        if (Win32.IsWindowVisible(lastPopUp))
            return lastPopUp;
        else if (lastPopUp == window)
            return IntPtr.Zero;
        else
            return GetLastVisibleActivePopUpOfWindow(lastPopUp);
    }
Ashantiashbaugh answered 17/10, 2008 at 23:52 Comment(2)
Where does 'WindowInformation' come from? I've Googled it a number of ways and haven't recognized anything useful. Is it a custom Type?Ashkhabad
You can use this: github.com/akfish/MwLib/blob/master/Native/WindowInfo.csDulcimer
A
3

Good work vhanla. My Pascal is a bit rusty, but your solution was a great help. I'm new to this, so please excuse my code and/or way of expressing things. The answer is relatively simple.

To make the list of Alt-Tab Windows, it appears you need three criteria

1) The Window must be visible - using GetWindowVisible

2) The Window must not be a Tool bar Window - using GetWindowInfo

3) The Window must not be cloaked - using DwmGetWindowAttribute

I don't believe that you need to look at class names. I think the WS_EX_APPWINDOW flag often fails the test (e.g. Chrome) - even when used in conjunction with the WS_EX_TOOLWINDOW. Also ... I don't think you need to look at the parent Window if you are enumerating Windows at the top level.

    public static bool IsAltTabWindow(IntPtr hWnd)
    {
        const uint WS_EX_TOOLWINDOW = 0x00000080;
        const uint DWMWA_CLOAKED = 14;

        //  It must be a visible Window
        if (!IsWindowVisible(hWnd)) return false;

        //  It must not be a Tool bar window
        WINDOWINFO winInfo = new WINDOWINFO(true);
        GetWindowInfo(hWnd, ref winInfo);            
        if ((winInfo.dwExStyle & WS_EX_TOOLWINDOW) != 0) return false;

        //  It must not be a cloaked window
        uint CloakedVal;
        DwmGetWindowAttribute(hWnd, DWMWA_CLOAKED, out CloakedVal, sizeof(uint));
        return CloakedVal == 0;
    }
Afore answered 1/6, 2020 at 6:38 Comment(0)
G
0

This is a function in pascal/delphi, you can easily translate it to C#.

It includes support for Windows 10 applications.


EnumWindows(@ListApps, 0);

function ListApps(LHWindow: HWND; lParam: Pointer): Boolean; stdcall;
var
   LHDesktop: HWND;
   LHParent: HWND;
   LExStyle: DWORD;

   AppClassName: array[0..255] of char;

   Cloaked: Cardinal;

   titlelen: Integer;
   title: String;
begin

  LHDesktop:=GetDesktopWindow;

    GetClassName(LHWindow, AppClassName, 255);
    LHParent:=GetWindowLong(LHWindow,GWL_HWNDPARENT);
    LExStyle:=GetWindowLong(LHWindow,GWL_EXSTYLE);

    if AppClassName = 'ApplicationFrameWindow' then
      DwmGetWindowAttribute(LHWindow, DWMWA_CLOAKED, @cloaked, sizeof(Cardinal))
    else
      cloaked := DWM_NORMAL_APP_NOT_CLOAKED;

    if IsWindowVisible(LHWindow)
    and (AppClassName <> 'Windows.UI.Core.CoreWindow')
    and ( (cloaked = DWM_NOT_CLOAKED) or (cloaked = DWM_NORMAL_APP_NOT_CLOAKED) )
    and ( (LHParent=0) or (LHParent=LHDesktop) )
    and (Application.Handle<>LHWindow)
    and ((LExStyle and WS_EX_TOOLWINDOW = 0) or (LExStyle and WS_EX_APPWINDOW <> 0))
    then
    begin
      titlelen := GetWindowTextLength(LHWindow);
      SetLength(title, titlelen);
      GetWindowText(LHWindow, PChar(title), titlelen + 1);
      { add each to a list }
      But.ListBox1.Items.Add(title);
      { also add each HWND to the list too, later switch using SwitchToThisWindow }
      { ... }
    end;


  Result := True;
end;
Grady answered 14/8, 2019 at 23:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.