如何使用C#捕获并保存Windows窗口截图

在进行演示或监控任务时,可能需要对某些Windows窗口进行截图。虽然有一些文章介绍了如何做到这一点,例如Lim Bio Liong的文章,但它们要么使用了过时的非托管C++代码,要么在目标窗口超出桌面边界时无法正常工作。因此,创建了这个C#应用程序,它允许捕获指定的窗口,并将其保存在支持的格式文件中。

为了捕获一个窗口,需要获取它的句柄,并使用原生的win32 API调用来获取位图句柄,这将被托管代码使用。在FCL中没有太多帮助,所以不得不导入大量的原生调用。pinvoke.net网站对于这样的任务非常有帮助。

获取窗口句柄

如果知道要查找的窗口的标题和/或类名,那么使用FindWindow原生win32 API获取窗口句柄是微不足道的。但是,知道这些信息可能需要Spy++,即使这样,也可能有多具有相同参数的窗口。

FindWindow按钮总是根据输入的参数获取句柄。另一种更方便的方法是获取在本地机器上运行的UI应用程序的主框架窗口句柄。这在90%的情况下都能工作,但仍有一些应用程序,如Toad for Oracle,它们的主框架窗口是隐藏的,所以必须寻找要捕获的“真实”窗口。为此,将检查每个线程中的可见窗口,这些窗口在每个进程中具有最大的矩形。

internal UIApp(System.Diagnostics.Process proc) { _proc = proc; _RealHWnd = IntPtr.Zero; _windowHandles = new List(); GCHandle listHandle = default(GCHandle); try { if (proc.MainWindowHandle == IntPtr.Zero) throw new ApplicationException("Can't add a process with no MainFrame"); RECT MaxRect = default(RECT); if (IsValidUIWnd(proc.MainWindowHandle)) { _RealHWnd = proc.MainWindowHandle; return; } listHandle = GCHandle.Alloc(_windowHandles); foreach (ProcessThread pt in proc.Threads) { Win32API.EnumThreadWindows((uint)pt.Id, new Win32API.EnumThreadDelegate(EnumThreadCallback), GCHandle.ToIntPtr(listHandle)); } IntPtr MaxHWnd = IntPtr.Zero; foreach (IntPtr hWnd in _windowHandles) { RECT CrtWndRect; if (Win32API.IsWindowVisible(hWnd) && Win32API.GetWindowRect(hWnd, out CrtWndRect) && CrtWndRect.Height > MaxRect.Height && CrtWndRect.Width > MaxRect.Width) { RECT visibleRect; if (Win32API.IntersectRect(out visibleRect, ref _DesktopRect, ref CrtWndRect) && !Win32API.IsRectEmpty(ref visibleRect)) { MaxHWnd = hWnd; MaxRect = CrtWndRect; } } } if (MaxHWnd != IntPtr.Zero && MaxRect.Width > 0 && MaxRect.Height > 0) { _RealHWnd = MaxHWnd; } else _RealHWnd = proc.MainWindowHandle; } finally { if (listHandle != default(GCHandle) && listHandle.IsAllocated) listHandle.Free(); } }

捕获窗口内容

一旦有了有效的主框架句柄,可以尝试使用PInvoke进行捕获。在捕获之前,再次检查窗口的有效性,因为它可能已经被关闭了。IsClientWnd提供了一个选项,只捕获客户区,节省了一些空间。nCmdShow用于窗口被最小化时,告诉程序将其恢复到适当的大小。此外,如果窗口的一部分超出了桌面,还需要调整矩形。

private static Bitmap MakeSnapshot(IntPtr AppWndHandle, bool IsClientWnd, Win32API.WindowShowStyle nCmdShow) { if (AppWndHandle == IntPtr.Zero || !Win32API.IsWindow(AppWndHandle) || !Win32API.IsWindowVisible(AppWndHandle)) return null; if (Win32API.IsIconic(AppWndHandle)) Win32API.ShowWindow(AppWndHandle, nCmdShow); System.Threading.Thread.Sleep(1000); RECT appRect; bool res = IsClientWnd ? Win32API.GetClientRect(AppWndHandle, out appRect) : Win32API.GetWindowRect(AppWndHandle, out appRect); if (!res || appRect.Height == 0 || appRect.Width == 0) return null; Point lt = new Point(appRect.Left, appRect.Top); Point rb = new Point(appRect.Right, appRect.Bottom); Win32API.ClientToScreen(AppWndHandle, ref lt); Win32API.ClientToScreen(AppWndHandle, ref rb); appRect.Left = lt.X; appRect.Top = lt.Y; appRect.Right = rb.X; appRect.Bottom = rb.Y; IntPtr DesktopHandle = Win32API.GetDesktopWindow(); RECT desktopRect; Win32API.GetWindowRect(DesktopHandle, out desktopRect); RECT visibleRect; if (!Win32API.IntersectRect(out visibleRect, ref desktopRect, ref appRect)) visibleRect = appRect; if (Win32API.IsRectEmpty(ref visibleRect)) return null; int Width = visibleRect.Width; int Height = visibleRect.Height; IntPtr hdcTo = IntPtr.Zero; IntPtr hdcFrom = IntPtr.Zero; IntPtr hBitmap = IntPtr.Zero; try { Bitmap clsRet = null; hdcFrom = IsClientWnd ? Win32API.GetDC(AppWndHandle) : Win32API.GetWindowDC(AppWndHandle); hdcTo = Win32API.CreateCompatibleDC(hdcFrom); hBitmap = Win32API.CreateCompatibleBitmap(hdcFrom, Width, Height); if (hBitmap != IntPtr.Zero) { int x = appRect.Left < 0 ? -appRect.Left : 0; int y = appRect.Top < 0 ? -appRect.Top : 0; IntPtr hLocalBitmap = Win32API.SelectObject(hdcTo, hBitmap); Win32API.BitBlt(hdcTo, 0, 0, Width, Height, hdcFrom, x, y, Win32API.SRCCOPY); Win32API.SelectObject(hdcTo, hLocalBitmap); clsRet = System.Drawing.Image.FromHbitmap(hBitmap); } return clsRet; } finally { if (hdcFrom != IntPtr.Zero) Win32API.ReleaseDC(AppWndHandle, hdcFrom); if (hdcTo != IntPtr.Zero) Win32API.DeleteDC(hdcTo); if (hBitmap != IntPtr.Zero) Win32API.DeleteObject(hBitmap); } }

保存指定格式的图像

private void btnSaveImage_Click(object sender, EventArgs e) { if (saveFileDialog1.ShowDialog() != DialogResult.Cancel) { try { string ext = System.IO.Path.GetExtension(saveFileDialog1.FileName).Substring(1).ToLower(); switch (ext) { case "jpg": case "jpeg": _pictureBox.Image.Save(saveFileDialog1.FileName, ImageFormat.Jpeg); break; // ...代码省略... default: MessageBox.Show(this, "未知格式。请选择已知的格式。", "转换错误!", MessageBoxButtons.OK, MessageBoxIcon.Error); break; } } catch (Exception ex) { MessageBox.Show(this, ex.Message, "图像转换错误!", MessageBoxButtons.OK, MessageBoxIcon.Error); } } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485