.NET 6.0 WPF C#中如何通过进程句柄获取程序DWM缩略图
实现Windows Vista Flip3D功能:通过进程句柄获取DWM缩略图并转为BitmapImage
我正在尝试重现Windows Vista的Flip3D功能(每个窗口的预览图排列在三维空间中),目前已经获取到每个程序的进程句柄,但需要抓取它们的DWM缩略图并转换为BitmapImage,作为WPF Image控件的源。
之前调研过相关实现,但其中用到的“WindowInfo”对象不存在,因此需要基于进程句柄完成该功能,以下是已整理的DWM API P/Invoke声明:
[DllImport("dwmapi.dll", PreserveSig = false)] private static extern bool DwmIsCompositionEnabled(); [DllImport("dwmapi.dll")] private static extern int DwmExtendFrameIntoClientArea(IntPtr hWnd, ref MARGINS pMargins); [StructLayout(LayoutKind.Sequential)] struct MARGINS { public int Left; public int Right; public int Top; public int Bottom; public void ExtendToWholeClientArea() { this.Left = -1; this.Right = -1; this.Top = -1; this.Bottom = -1; } } [DllImport("dwmapi.dll")] private static extern int DwmRegisterThumbnail(IntPtr dest, IntPtr src, out IntPtr thumb); [DllImport("dwmapi.dll")] private static extern int DwmUnregisterThumbnail(IntPtr thumb); [DllImport("dwmapi.dll")] private static extern int DwmQueryThumbnailSourceSize(IntPtr thumb, out PSIZE size); [StructLayout(LayoutKind.Sequential)] private struct PSIZE { public int x; public int y; } [DllImport("dwmapi.dll")] private static extern int DwmUpdateThumbnailProperties(IntPtr hThumb, ref DWM_THUMBNAIL_PROPERTIES props); [StructLayout(LayoutKind.Sequential)] private struct DWM_THUMBNAIL_PROPERTIES { public int dwFlags; public RECT rcDestination; public RECT rcSource; public byte opacity; public bool fVisible; public bool fSourceClientAreaOnly; } [StructLayout(LayoutKind.Sequential)] private struct RECT { public RECT(int left, int top, int right, int bottom) { Left = left; Top = top; Right = right; Bottom = bottom; } public int Left; public int Top; public int Right; public int Bottom; } private static readonly int DWM_TNP_VISIBLE = 0x8; private static readonly int DWM_TNP_RECTDESTINATION = 0x1; private static readonly int DWM_TNP_SOURCECLIENTAREAONLY = 0x00000010;
关键实现步骤
- 从进程句柄获取窗口句柄
进程句柄无法直接用于DWM缩略图API,需要先获取进程对应的主窗口句柄。可以通过枚举系统所有窗口,匹配进程ID来实现:
[DllImport("user32.dll")] private static extern bool EnumWindows(EnumWindowsProc enumProc, IntPtr lParam); [DllImport("user32.dll")] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId); [DllImport("user32.dll")] private static extern bool IsWindowVisible(IntPtr hWnd); private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam); public static IntPtr GetMainWindowHandle(Process process) { IntPtr mainWindowHandle = IntPtr.Zero; uint processId = (uint)process.Id; EnumWindows((hWnd, lParam) => { GetWindowThreadProcessId(hWnd, out uint windowProcessId); if (windowProcessId == processId && IsWindowVisible(hWnd)) { mainWindowHandle = hWnd; return false; // 找到主窗口后停止枚举 } return true; }, IntPtr.Zero); return mainWindowHandle; }
- 注册并显示DWM缩略图
拿到窗口句柄后,注册缩略图并设置显示属性,可将缩略图显示在WPF的HwndHost控件中(因为DWM缩略图需要绑定到Win32窗口句柄):
private IntPtr _thumbnailHandle; public void RegisterThumbnail(IntPtr targetHwnd, IntPtr sourceWindowHwnd) { if (!DwmIsCompositionEnabled()) throw new InvalidOperationException("DWM组合未启用"); // 注册缩略图 int result = DwmRegisterThumbnail(targetHwnd, sourceWindowHwnd, out _thumbnailHandle); if (result != 0) throw new Win32Exception(result); // 获取源窗口尺寸 DwmQueryThumbnailSourceSize(_thumbnailHandle, out PSIZE sourceSize); // 设置缩略图属性:可见、指定显示区域 DWM_THUMBNAIL_PROPERTIES props = new DWM_THUMBNAIL_PROPERTIES(); props.dwFlags = DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_SOURCECLIENTAREAONLY; props.fVisible = true; props.fSourceClientAreaOnly = true; // 仅显示客户区 props.rcDestination = new RECT(0, 0, sourceSize.x, sourceSize.y); // 铺满目标控件 result = DwmUpdateThumbnailProperties(_thumbnailHandle, ref props); if (result != 0) throw new Win32Exception(result); } // 记得在窗口关闭时注销缩略图 public void UnregisterThumbnail() { if (_thumbnailHandle != IntPtr.Zero) { DwmUnregisterThumbnail(_thumbnailHandle); _thumbnailHandle = IntPtr.Zero; } }
- 将缩略图转为BitmapImage
如果需要将缩略图转为BitmapImage用于WPF Image控件,可以通过抓取显示缩略图的HwndHost控件内容实现:
public BitmapImage ConvertThumbnailToBitmapImage(HwndHost hostControl) { using (Bitmap bitmap = new Bitmap((int)hostControl.ActualWidth, (int)hostControl.ActualHeight)) { using (Graphics g = Graphics.FromImage(bitmap)) { IntPtr hdc = g.GetHdc(); // 抓取控件内容到Bitmap BitBlt(hdc, 0, 0, bitmap.Width, bitmap.Height, GetDC(hostControl.Handle), 0, 0, 0x00CC0020); g.ReleaseHdc(hdc); } // 将Bitmap转为BitmapImage BitmapImage bitmapImage = new BitmapImage(); using (MemoryStream ms = new MemoryStream()) { bitmap.Save(ms, ImageFormat.Png); ms.Position = 0; bitmapImage.BeginInit(); bitmapImage.CacheOption = BitmapCacheOption.OnLoad; bitmapImage.StreamSource = ms; bitmapImage.EndInit(); } return bitmapImage; } } // 需要添加的P/Invoke声明 [DllImport("gdi32.dll")] private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop); [DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hWnd);
注意事项
- 确保DWM组合已启用(通过
DwmIsCompositionEnabled检查),否则无法使用DWM缩略图API。 - 每个缩略图句柄在不再使用时必须调用
DwmUnregisterThumbnail释放资源,避免内存泄漏。 - 转换BitmapImage时,注意控件的实际尺寸,避免拉伸或变形。
内容的提问来源于stack exchange,提问作者Eric William
相关产品推荐
相关产品推荐

