DWM是在Windows Vista后出现的操作系统窗口渲染程序,在中文化的系统中被描述为“桌面窗口管理器”,该进程随着系统一起启动,同时提供一些Api给其他程序进行调用。这里展示其中的一个功能——由窗口向DWM订阅其他窗口的缩略图,并由DWM绘制到请求的窗口上。
这一过程将使用以下四个API
///
/// 向DWM管理器订阅某窗口的缩略图
///
/// 显示缩略图的窗口句柄
/// 被显示缩略图的窗口句柄
/// 当获取成功时,缩略图的句柄
/// 当函数执行成功时,返回S_OK,否则,返回HRESULT错误代码
[DllImport("dwmapi.dll")]
public static extern int DwmRegisterThumbnail(IntPtr dest, IntPtr src, out IntPtr thumb);
///
/// 向DWM退订某个缩略图
///
/// 需要退订的缩略图的句柄,null或不存在的句柄将会返回E_INVALIDARG
/// 当函数执行成功时,返回S_OK,否则,返回HRESULT错误代码
[DllImport("dwmapi.dll")]
public static extern int DwmUnregisterThumbnail(IntPtr thumb);
///
/// 查询DWM提供的缩略图的大小
///
/// 需要查询的缩略图的句柄
/// 当函数执行成功时,返回带有缩略图Size信息的PSIZE结构体
/// 当函数执行成功时,返回S_OK,否则,返回HRESULT错误代码
[DllImport("dwmapi.dll")]
public static extern int DwmQueryThumbnailSourceSize(IntPtr thumb, out PSIZE size);
///
/// 更新DWM的相关属性
///
/// 要更新的缩略图句柄,当这个句柄被其他线程持有时,将会返回E_INVALIDARG。
/// 指向DWM_THUMBNAIL_PROPERTIES结构体的指针,带有更新后的缩略图属性
/// 当函数执行成功时,返回S_OK,否则,返回HRESULT错误代码。
[DllImport("dwmapi.dll")]
public static extern int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props);
过程中可能会用到的常量和结构体定义如下
#region 常量定义
public static readonly int GWL_STYLE = -16;
public static readonly int DWM_TNP_VISIBLE = 0x8;
public static readonly int DWM_TNP_OPACITY = 0x4;
public static readonly int DWM_TNP_RECTDESTINATION = 0x1;
public static readonly ulong WS_VISIBLE = 0x10000000L;
public static readonly ulong WS_BORDER = 0x00800000L;
public static readonly ulong WS_CAPTION = 0x00C00000L;
public static readonly ulong WS_CHILDWINDOW = 0x40000000L;
public static readonly ulong TARGETWINDOW = WS_BORDER | WS_VISIBLE;
#endregion
#region 结构体定义
///
/// 缩略图的尺寸
///
[StructLayout(LayoutKind.Sequential)]
public struct PSIZE
{
///
/// 缩略图的宽度
///
public int x;
///
/// 缩略图的高度
///
public int y;
}
///
/// 缩略图属性
///
[StructLayout(LayoutKind.Sequential)]
public struct DWM_THUMBNAIL_PROPERTIES
{
///
/// 标志位常量组,表示结构体中哪些成员被设置
///
public int dwFlags;
///
/// 目标窗口中将被用于显示缩略图的区域
///
public Rect rcDestination;
///
/// 要显示出缩略图的源窗口区域,默认情况下将会使用全部区域作为缩略图
///
public Rect rcSource;
///
/// 缩略图的不透明度,取值范围为0-255,默认值为255
///
public byte opacity;
///
/// 缩略图可见性,当该值为true时,缩略图可见。默认值为false
///
public bool fVisible;
///
/// 当该值为true时,仅使用缩略图来源的客户区,默认值为false
///
public bool fSourceClientAreaOnly;
}
///
/// 显示应用缩略图的位置
///
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public Rect(int left, int top, int right, int bottom)
{
Left = left;
Top = top;
Right = right;
Bottom = bottom;
}
public Rect(double left, double top, double right, double bottom)
{
Left = (int)left;
Top = (int)top;
Right = (int)right;
Bottom = (int)bottom;
}
public int Left;
public int Top;
public int Right;
public int Bottom;
}
需要的win32Api如下
///
/// 检索给定句柄的窗口的相关信息
///
/// 窗口的句柄,以及窗口所属的类的句柄。
///
/// 需要检索的值对0的偏移常量。有效值的范围是0到额外的窗口内存字节数减4。
/// 例如,如果指定了12个或更多字节的额外内存,则值8将是第三个32位整数的索引
///
/// 如果函数成功,则返回所请求的值。否则,返回0
///
[DllImport("user32.dll")]
public static extern ulong GetWindowLongA(IntPtr hWnd, int nIndex);
///
/// 通过将句柄传递给每个窗口,依次传递给应用程序定义的回调函数,枚举屏幕上的所有顶级窗口。枚举窗口一直持续到最后一个顶级窗口被枚举或回调函数返回FALSE
///
///
/// 指向应用程序定义的回调函数
/// 参见
///
/// 要传递给回调函数的应用程序定义的值
/// 如果函数执行成功,返回非0值,否则,返回0
///
[DllImport("user32.dll")]
public static extern int EnumWindows(EnumWindowsCallback lpEnumFunc, int lParam);
[DllImport("user32.dll")]
public static extern int EnumChildWindows(IntPtr hwnd, EnumWindowsCallback lpEnumFunc, int lParam);
///
/// 提供回调函数的委托
///
/// 窗口句柄
/// 传入的自定义值
/// 继续进行枚举时,返回true,否则返回false
public delegate bool EnumWindowsCallback(IntPtr hwnd, int lParam);
///
/// 将指定窗口标题栏的文本(如果有的话)复制到缓冲区中。如果指定的窗口是控件,则复制该控件的文本。但是,GetWindowText不能在另一个应用程序中检索控件的文本
///
/// 包含文本的窗口或控件的句柄
/// 将接收文本的缓冲区。如果字符串与缓冲区一样长或更长,则该字符串将被截断并以空字符结束
/// 要复制到缓冲区的最大字符数,包括空字符。如果文本超过这个限制,就会被截断。
///
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
public static extern void GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
///
/// 将创建指定窗口的线程带到前台并激活该窗口。键盘输入直接指向窗口,并为用户更改各种视觉提示。系统为创建前台窗口的线程分配的优先级略高于其他线程。
///
/// 需要激活的窗口的句柄
/// 如果函数执行成功,返回非0值,否则,返回0
///
[DllImport("user32.dll")]
public static extern bool SetForegroundWindow(int hWnd);
///
/// 获取桌面窗口的句柄
///
/// 桌面窗口的句柄
[DllImport("user32.dll")]
public static extern int GetDesktopWindow();
///
/// 设置指定窗口的显示状态
///
/// 指定的窗口句柄
/// 相应的状态值
/// 当指定的窗口可见时,返回true,不可见时则返回false
///
[DllImport("user32.dll")]
public static extern bool ShowWindow(int hWnd, int nCmdShow);
///
/// 检测给定的窗口是否图标化或最小化
///
/// 给定的窗口句柄
/// 当窗口被最小化时,返回非0值,否则,返回0
///
[DllImport("user32.dll")]
public static extern int IsIconic(int hWnd);
准备完毕之后,创建一个新的WPF工程,并在其中放置一个Rectangle作为缩略图位置标记。
随后通过int EnumWindows(EnumWindowsCallback lpEnumFunc, int lParam);枚举系统中的窗口,并通过传入委托进行窗口句柄的过滤工作。这里采用的过滤语句如下
if ((DWMApi.GetWindowLongA(hwnd, DWMApi.GWL_STYLE) & DWMApi.WS_VISIBLE) == DWMApi.WS_VISIBLE)
使用这条语句可以捕捉到较多的窗口,包括使用了无边框技术的窗口,例如WPS、Tim、微信等窗口。但是在Win10下会同时捕捉到未打开的Edge、设置,在Win7下会捕捉到开始菜单按钮图标,需要在后期进行进一步过滤。
如果需要显示桌面的缩略图,可以关注标题为 Program Manager 的程序句柄,通过此句柄可以获取桌面缩略图。
收集到桌面显示的窗口的句柄之后,下一步就是向DWM订阅窗口缩略图,由DWM进行缩略图的绘制。
订阅需要使用使用此函数
int DwmRegisterThumbnail(IntPtr dest, IntPtr src, out IntPtr thumb);
目标窗口的句柄已经在之前的操作中获取。此时需要获取绘制缩略图的窗口的句柄。在WPF程序中,可以采用这种方法获取
var windowHandle = new WindowInteropHelper(this).Handle;
其中this是需要绘制缩略图窗口的引用。如此即可向DWM订阅窗口缩略图,当返回值为0时,表示订阅成功。
订阅成功后,IntPtr thumb表示的是缩略图的句柄,这一句柄将在退订缩略图的时候使用,所以需要用变量或者容器将其暂存。
在订阅成功后即可指定缩略图的绘制位置,使用的函数如下
int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props);
这里的hThumb是刚刚获取的缩略图句柄,props是先前定义的结构体,携带绘制缩略图的位置信息。需要注意的是缩略图绘制使用的是拉伸模式,假如希望保持宽高比的话请自行通过代码调整。通过如下函数可以查询缩略图源窗口的大小信息
int DwmQueryThumbnailSourceSize(IntPtr thumb, out PSIZE size);
最后通过一定的算法,借助最初设置的Rectangle计算出缩略图相对于目标窗口四条边的像素距离,即可绘制出给定句柄的窗口的缩略图。这里不再详述。
DWMApi.DWM_THUMBNAIL_PROPERTIES props = new DWMApi.DWM_THUMBNAIL_PROPERTIES
{
fVisible = true,
dwFlags = DWMApi.DWM_TNP_VISIBLE | DWMApi.DWM_TNP_RECTDESTINATION | DWMApi.DWM_TNP_OPACITY,
opacity = 255
props.rcDestination = new DWMApi.Rect(rectangle.Left,
rectangle.Top,
rectangle.Right,
rectangle.Bottom);
}
做完这一步就能在窗口中看到其他窗口的缩略图了。
在关闭程序前,还要将已经订阅了的缩略图退订,通过Api将之前收集的缩略图句柄逐一关闭即可。
int DwmUnregisterThumbnail(IntPtr thumb);