WPF应用在第二显示器截图异常:黑屏与尺寸错误问题求助
问题
- WPF透明窗口截图需求:需同时截取窗口及后方背景
- 主显示器上截图正常,但窗口完全移至第二显示器时,截图尺寸错误且黑屏;窗口跨屏时仍可正常工作
- 当前使用的截图函数如下:
public void SaveSnapshot2(int count) { string imageName = "image" + count.ToString() + ".jpeg"; string basePath = Path.GetFullPath(Environment.CurrentDirectory); string folderPath = Path.Combine(basePath, "Snapshots"); string fullPath = Path.Combine(folderPath, imageName); if (!Directory.Exists(folderPath)) { Directory.CreateDirectory(folderPath); } System.Windows.Point relativeWindowPosition = App.Current.MainWindow.PointToScreen(new System.Windows.Point(0, 0)); System.Windows.Point relativeBottomRightWindowPosition = App.Current.MainWindow.PointToScreen(new System.Windows.Point(App.Current.MainWindow.Width, App.Current.MainWindow.Height)); System.Windows.Point actualWidthHeight = new System.Windows.Point((relativeBottomRightWindowPosition.X - relativeWindowPosition.X), (relativeBottomRightWindowPosition.Y - relativeWindowPosition.Y)); System.Drawing.Size convertedSize = new System.Drawing.Size((int)actualWidthHeight.X, (int)actualWidthHeight.Y); Bitmap Screenshot = new Bitmap((int)actualWidthHeight.X, (int)actualWidthHeight.Y, PixelFormat.Format32bppArgb); using (Graphics g = Graphics.FromImage(Screenshot)) { // Crops the screenshot // The relativePoint is where the top left corner of the image is. This is correct. g.CopyFromScreen((int)relativeWindowPosition.X, (int)relativeWindowPosition.Y, 0, 0, convertedSize); // or (0, 0, 0, 0, Screenshot.Size) to get the whole bitmap image } try { Screenshot.Save(fullPath, ImageFormat.Jpeg); } catch (Exception ex) { MessageBox.Show(ex.Message + ", the image did not save."); } Screenshot.Dispose(); }
回答
多显示器坐标系说明
Windows多显示器采用虚拟桌面坐标系:主显示器左上角是原点(0,0),副显示器的坐标可能为负数(比如主显示器在左,副显示器在其左侧时),具体取决于显示器的排列方式。你的问题核心是CopyFromScreen不支持读取负坐标区域的画面,导致副屏截图黑屏。
当前实现的问题
- 通过
PointToScreen计算右下角再反推宽高的方式冗余,且未考虑DPI缩放:WPF的Width/Height是逻辑像素,转成屏幕物理像素会有偏差 CopyFromScreen不支持负坐标,副显示器坐标为负时无法正常读取画面- 资源释放逻辑不严谨,未用
finally确保Bitmap被释放
修复后的代码
改用Win32的BitBlt API实现,支持跨任意显示器的截图,同时处理DPI缩放问题:
public void SaveSnapshot(int count) { string imageName = $"image{count}.jpeg"; string folderPath = Path.Combine(Environment.CurrentDirectory, "Snapshots"); string fullPath = Path.Combine(folderPath, imageName); Directory.CreateDirectory(folderPath); // 无需提前判断目录是否存在,该方法会自动处理 var mainWindow = App.Current.MainWindow; // 获取窗口在屏幕上的真实物理位置和尺寸(适配DPI) var windowTopLeft = mainWindow.PointToScreen(new System.Windows.Point(0, 0)); var windowBottomRight = mainWindow.PointToScreen(new System.Windows.Point(mainWindow.ActualWidth, mainWindow.ActualHeight)); int actualWidth = (int)Math.Abs(windowBottomRight.X - windowTopLeft.X); int actualHeight = (int)Math.Abs(windowBottomRight.Y - windowTopLeft.Y); Bitmap screenshot = new Bitmap(actualWidth, actualHeight, PixelFormat.Format32bppArgb); using (Graphics g = Graphics.FromImage(screenshot)) { IntPtr screenDC = GetDC(IntPtr.Zero); IntPtr bitmapDC = g.GetHdc(); // 使用BitBlt复制屏幕区域,支持负坐标 BitBlt(bitmapDC, 0, 0, actualWidth, actualHeight, screenDC, (int)windowTopLeft.X, (int)windowTopLeft.Y, 0xCC0020); g.ReleaseHdc(bitmapDC); ReleaseDC(IntPtr.Zero, screenDC); } try { screenshot.Save(fullPath, ImageFormat.Jpeg); } catch (Exception ex) { MessageBox.Show($"{ex.Message},截图保存失败"); } finally { screenshot.Dispose(); } } // 需添加的Win32 API声明 [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern IntPtr GetDC(IntPtr hwnd); [System.Runtime.InteropServices.DllImport("user32.dll")] private static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); [System.Runtime.InteropServices.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);
关键优化点
- 用
BitBlt替代CopyFromScreen,支持读取虚拟桌面任意坐标(包括负数)的画面 - 使用
ActualWidth/ActualHeight获取窗口真实渲染尺寸,适配DPI缩放 - 简化目录创建逻辑,
Directory.CreateDirectory无需提前判断 - 用
finally块确保Bitmap资源被释放,避免内存泄漏
内容的提问来源于stack exchange,提问作者emdashii
相关产品推荐
相关产品推荐

