You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

关键实现步骤

  1. 从进程句柄获取窗口句柄
    进程句柄无法直接用于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;
}
  1. 注册并显示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;
    }
}
  1. 将缩略图转为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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.13 08:19:59