在自動化測試中需要截取 WPF 的 Popup 視窗,卻發現傳統 GDI 方式(BitBlt)與 PrintWindow API 皆無法正確捕捉 WPF 繪製的內容(呈現黑畫面或只抓到背景)。本文記錄三種截圖方案的嘗試與比較。
BitBlt(dcImage, 0, 0, (int)rect.Width, (int)rect.Height,
dcScreen, (int)rect.Left, (int)rect.Top, TernaryRasterOperations.SRCCOPY);
IntPtr hdc = Native.GetWindowDC(hwnd);
IntPtr hdcMem = Native.CreateCompatibleDC(hdc);
IntPtr hbitmap = Native.CreateCompatibleBitmap(hdc, width, height);
Native.SelectObject(hdcMem, hbitmap);
Native.PrintWindow(hwnd, hdcMem, 0); // 關鍵呼叫
Bitmap bmp = Bitmap.FromHbitmap(hbitmap);
// 釋放資源...
const uint KEYEVENTF_EXTENDEDKEY = 0x1;
const uint KEYEVENTF_KEYUP = 0x2;
const byte VK_SNAPSHOT = 0x2C;
Native.keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY, UIntPtr.Zero);
Native.keybd_event(VK_SNAPSHOT, 0x45, KEYEVENTF_EXTENDEDKEY | KEYEVENTF_KEYUP, UIntPtr.Zero);
IDataObject iObj = Clipboard.GetDataObject();
if (iObj.GetDataPresent(DataFormats.Bitmap))
{
Bitmap bmpScreen = (Bitmap)iObj.GetData(DataFormats.Bitmap);
Bitmap bmpOutput = new Bitmap(rect.Width, rect.Height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bmpOutput))
{
g.DrawImage(bmpScreen, new Rectangle(0,0,rect.Width,rect.Height),
rect.X, rect.Y, rect.Width, rect.Height, GraphicsUnit.Pixel);
}
bmpOutput.Save(path, ImageFormat.Bmp);
}
WPF 採用 DirectX (硬體加速) 繪製繞過 GDI,故傳統 BitBlt/PrintWindow(基於 GDI DC)無法取得像素。PrintScreen 由視窗管理器 (DWM) 合成最終畫面,故能捕捉所有視覺內容。
DwmGetWindowAttribute、DirectX Surface Copy),避免模擬按鍵。RenderTargetBitmap (WPF 內建) 對視覺樹渲染,但需在 UI 執行緒且無法跨進程。Windows.Graphics.Capture API (Win10 1903+) 實現高效、無剪貼板依賴的跨進程截圖。若需截取 WPF 應用程式的完整視覺內容(含 Popup、ToolTip、硬體加速區域),目前最穩健的無外部相依方案仍是模擬 PrintScreen;長遠來看,應遷移至 Windows.Graphics.Capture 或同層級的現代截圖 API。