C#如何在不隐藏WinForm的情况下截取其后方屏幕?
解决WinForm前置状态下截取后方屏幕内容的方案
方案一:利用分层窗口透明色(无闪烁,窗体全程可见)
通过Windows API将窗体设置为分层窗口,指定一个透明色键,让屏幕捕获操作忽略窗体区域,同时保持窗体视觉上正常显示。
步骤1:导入所需Windows API
在窗体类中添加以下API声明:
using System.Runtime.InteropServices; private const int WS_EX_LAYERED = 0x80000; private const int LWA_COLORKEY = 0x1; private const int WM_SETREDRAW = 0x000B; [DllImport("user32.dll")] private static extern int SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong); [DllImport("user32.dll")] private static extern bool SetLayeredWindowAttributes(IntPtr hWnd, uint crKey, byte bAlpha, int dwFlags); [DllImport("user32.dll")] private static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
步骤2:修改截图方法
private void GetScreen(string file_path) { // 保存原始状态 int originalExStyle = GetWindowLongPtr(this.Handle, -20); Color originalBackColor = this.BackColor; // 选择一个不常用的颜色作为透明键(比如品红色) Color transparentKey = Color.Magenta; try { // 禁止窗体重绘,避免闪烁 SendMessage(this.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero); // 启用分层窗口并设置透明色键 SetWindowLongPtr(this.Handle, -20, originalExStyle | WS_EX_LAYERED); this.BackColor = transparentKey; SetLayeredWindowAttributes(this.Handle, (uint)transparentKey.ToArgb(), 255, LWA_COLORKEY); // 强制刷新使透明设置生效 this.Update(); // 捕获目标区域的屏幕内容(此时窗体区域会被视为透明,捕获后方内容) var rect = new Rectangle(Location.X, Location.Y, Width, Height); using var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format64bppArgb); using var gfx = Graphics.FromImage(bmp); gfx.CopyFromScreen(rect.Left, rect.Top, 0, 0, this.Size); bmp.Save(file_path, ImageFormat.Tiff); } finally { // 恢复窗体原始状态 SetWindowLongPtr(this.Handle, -20, originalExStyle); this.BackColor = originalBackColor; // 恢复重绘并刷新 SendMessage(this.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero); this.Refresh(); } }
说明:如果窗体包含控件,需确保控件背景色不与透明键冲突,或给控件设置不透明的背景色,避免控件也被透明化。
方案二:临时隐藏窗体(实现简单,闪烁极轻微)
通过快速隐藏并恢复窗体,结合禁止重绘和顶层窗口设置,将闪烁降到最低,同时保持窗体前置状态。
步骤1:导入所需Windows API
using System.Runtime.InteropServices; private const int SW_HIDE = 0; private const int SW_SHOWNOACTIVATE = 4; private const int HWND_TOPMOST = -1; private const int SWP_NOSIZE = 0x0001; private const int SWP_NOMOVE = 0x0002; private const int SWP_NOACTIVATE = 0x0010; private const int WM_SETREDRAW = 0x000B; [DllImport("user32.dll")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags); [DllImport("user32.dll")] private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
步骤2:修改截图方法
private void GetScreen(string file_path) { try { // 禁止窗体重绘,避免闪烁 SendMessage(this.Handle, WM_SETREDRAW, IntPtr.Zero, IntPtr.Zero); // 隐藏窗体(不激活其他窗口) ShowWindow(this.Handle, SW_HIDE); // 强制处理消息队列,确保屏幕刷新 Application.DoEvents(); // 捕获目标区域的屏幕内容 var rect = new Rectangle(Location.X, Location.Y, Width, Height); using var bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format64bppArgb); using var gfx = Graphics.FromImage(bmp); gfx.CopyFromScreen(rect.Left, rect.Top, 0, 0, this.Size); bmp.Save(file_path, ImageFormat.Tiff); } finally { // 显示窗体(不激活)并恢复顶层状态 ShowWindow(this.Handle, SW_SHOWNOACTIVATE); SetWindowPos(this.Handle, (IntPtr)HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE); // 恢复重绘并刷新 SendMessage(this.Handle, WM_SETREDRAW, (IntPtr)1, IntPtr.Zero); this.Refresh(); } }
说明:该方案实现简单,闪烁几乎不可察觉,适合大多数场景。
内容的提问来源于stack exchange,提问作者Quince
相关产品推荐
相关产品推荐

