C# 像 alt-tab 一样枚举窗口
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/210504/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Enumerate windows like alt-tab does
提问by
I'm creating an alt-tab replacement for Vista but I have some problems listing all active programs.
我正在为 Vista 创建一个 alt-tab 替代品,但在列出所有活动程序时遇到了一些问题。
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.
我正在使用 EnumWindows 来获取 Windows 列表,但这个列表很大。当我只打开 10 个窗口时,它包含大约 400 个项目。它似乎是每个控件和许多其他东西的首选。
So I have to filter this list somehow, but I can't manage to do it exactly as alt-tab does.
所以我必须以某种方式过滤这个列表,但我无法像 alt-tab 那样完全做到这一点。
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.
这是我现在用来过滤列表的代码。它工作得很好,但我得到了一些不需要的窗口,比如 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;
}
回答by Michael Burr
Raymond Chen answered this a while back
(https://devblogs.microsoft.com/oldnewthing/20071008-00/?p=24863):
Raymond Chen 不久前回答了这个问题
(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:
它实际上非常简单,尽管您几乎无法自己猜出任何东西。注意:这个算法的细节是一个实现细节。它可以随时改变,所以不要依赖它。事实上,它已经随着 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;
}
Follow the link to Chen's blog entry for more details and some corner conditions.
有关更多详细信息和一些角落条件,请访问 Chen 的博客条目的链接。
回答by Michael Burr
Thanks Mike B. The example from Raymonds blog pointed me in the correct direction.
感谢 Mike B。 Raymonds 博客中的示例为我指明了正确的方向。
There are however some exceptions that has to be made, Windows Live messenger got alot of hacks for creating shadows under windows etc :@
然而,必须做出一些例外,Windows Live Messenger 有很多技巧可以在 Windows 等下创建阴影:@
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. :)
这是我的完整代码,现在已经使用了一天,并没有注意到与真正的 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);
}
回答by vhanla
This is a function in pascal/delphi, you can easily translate it to C#.
这是 pascal/delphi 中的一个函数,您可以轻松将其转换为 C#。
It includes support for Windows 10 applications.
它包括对 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;