【问题标题】:Enumerate windows like alt-tab does像 alt-tab 一样枚举窗口
【发布时间】:2010-09-17 15:39:38
【问题描述】:

我正在为 Vista 创建一个 alt-tab 替代品,但在列出所有活动程序时遇到了一些问题。

我正在使用 EnumWindows 来获取 Windows 列表,但是这个列表很大。当我只打开 10 个窗口时,它包含大约 400 个项目。它似乎是每个控件和许多其他东西的 hwnd。

所以我必须以某种方式过滤这个列表,但我无法像 alt-tab 那样做到这一点。

这是我现在用来过滤列表的代码。它工作得很好,但我得到了一些不需要的窗口,比如 Visual Studio 中分离的工具窗口,我也想念 iTunes 和 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;
}

【问题讨论】:

    标签: c# windows winapi


    【解决方案1】:

    干得好。我的 Pascal 有点生疏,但您的解决方案很有帮助。我是新手,所以请原谅我的代码和/或表达方式。答案比较简单。

    要制作 Alt-Tab 窗口列表,您似乎需要三个条件

    1) 窗口必须可见 - 使用 GetWindowVisible

    2) 窗口不能是工具栏窗口 - 使用 GetWindowInfo

    3) 不得隐藏窗口 - 使用 DwmGetWindowAttribute

    我认为您不需要查看类名。我认为 WS_EX_APPWINDOW 标志经常无法通过测试(例如 Chrome)——即使与 WS_EX_TOOLWINDOW 结合使用也是如此。另外...如果您在顶层枚举 Windows,我认为您不需要查看父 Window。

        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;
        }
    

    【讨论】:

      【解决方案2】:

      这是pascal/delphi中的一个函数,你可以很容易地把它翻译成C#。

      它包括对 Windows 10 应用程序的支持。

      
      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;
      

      【讨论】:

        【解决方案3】:

        Raymond Chen 不久前回答了这个问题
        (https://devblogs.microsoft.com/oldnewthing/20071008-00/?p=24863):

        其实很简单 几乎没有你能猜到的东西 靠自己。注:本次详情 算法是一种实现 细节。它可以随时改变,所以 不要依赖它。事实上,它已经 用 Flip 和 Flip3D 改变;我只是 谈论经典 Alt+Tab 窗口在这里。

        对于每个可见的窗口,向上走 所有者链,直到找到根 所有者。然后走回可见的地方 最后一个活动的弹出链,直到你找到 一个可见的窗口。如果你回到 你从哪里开始,然后把 Alt+Tab 列表中的窗口。在 伪代码:

        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;
        }
        

        点击 Chen 博客条目的链接了解更多详细信息和一些角落条件。

        【讨论】:

        • 请注意,此实现不支持 Raymond 博客文章中提到的 WS_EX_TOOLWINDOWWS_EX_APPWINDOW 扩展样式。
        • 这是一个基于此方法的更完整和更强大的示例github.com/christianrondeau/GoToWindow/blob/… 它来自另一个替代 alt-tab 实用程序,它似乎准确地显示了常规 alt-tab 菜单的作用。
        • 若要检测 UWP 应用程序,请检查“ApplicationFrameWindow”窗口类名,然后使用 DwmGetWindowAttribute(LHWindow, DWMWA_CLOAKED, @cloaked, sizeof(Cardinal)) 对其进行评估,如果 cloacked(整数)返回 0,则表示 UWP 应用程序未隐藏。
        【解决方案4】:

        感谢迈克 B。 Raymonds 博客中的示例为我指明了正确的方向。

        但也有一些例外,Windows Live messenger 有很多技巧可以在 Windows 等下创建阴影:@

        这是我的完整代码,已经使用了一天,并没有注意到与真正的 alt 选项卡有任何区别。有一些底层代码没有发布,但弄清楚它的作用是没有问题的。 :)

            private static bool KeepWindowHandleInAltTabList(IntPtr window)
            {
                if (window == Win32.GetShellWindow())   //Desktop
                    return false;
        
                //http://stackoverflow.com/questions/210504/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);
            }
        

        【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-05-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多