使用DWM(Desktop Window Manage)在WPF程序中绘制缩略图

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

 

 

你可能感兴趣的:(WPF,程序开发)