如何在C# .NET中捕获非活动应用窗口的特定区域?
截取非前台窗口的特定区域为Bitmap
我需要将非前台应用程序窗口的特定区域保存为Bitmap,而非整个窗口。目前我用以下代码查找窗口并截图,但不知道如何修改RECT的起点和大小,只截取目标窗口(比如记事本)的指定部分。
public static IntPtr WinGetHandle(string wName) { foreach (Process pList in Process.GetProcesses()) if (pList.MainWindowTitle.Contains(wName)) return pList.MainWindowHandle; return IntPtr.Zero; } [DllImport("user32.dll")] public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect); [DllImport("user32.dll")] public static extern bool PrintWindow(IntPtr hWnd, IntPtr hdcBlt, int nFlags); public static void PrintThisWindow(IntPtr hwnd) { // Create the Rectangle RECT rc = new RECT(); GetWindowRect(hwnd, out rc); // Capture bmp from hwnd Bitmap bmp = new Bitmap(rc.Width, rc.Height, PixelFormat.Format32bppArgb); Graphics gfxBmp = Graphics.FromImage(bmp); IntPtr hdcBitmap = gfxBmp.GetHdc(); PrintWindow(hwnd, hdcBitmap, 0); // Dispose gfxBmp.ReleaseHdc(hdcBitmap); gfxBmp.Dispose(); //bitmap.Save bmp.Save("Example.png"); } //Whatever window you want to test. IntPtr hwnd = WinGetHandle("New Text Document.txt - Notepad"); PrintThisWindow(hwnd);
我试过手动设置RECT:RECT rc = new RECT(100, 100, 200, 200),也尝试过设置位置和大小:rc.Location = new Point(100, 100) 和 rc.Size = new Size(100, 100),但都没成功。我找了很久适配的函数,只找到CopyFromScreen:captureGraphics.CopyFromScreen(200, 200, 0, 0, new Size(100, 100)),它能按指定起点和大小截取,但这是从整个屏幕复制,不是针对特定窗口的。
以下是RECT类定义:
[StructLayout(LayoutKind.Sequential)] public struct RECT { private int _Left; private int _Top; private int _Right; private int _Bottom; public RECT(RECT Rectangle) : this(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom) { } public RECT(int Left, int Top, int Right, int Bottom) { _Left = Left; _Top = Top; _Right = Right; _Bottom = Bottom; } public int X { get { return _Left; } set { _Left = value; } } public int Y { get { return _Top; } set { _Top = value; } } public int Left { get { return _Left; } set { _Left = value; } } public int Top { get { return _Top; } set { _Top = value; } } public int Right { get { return _Right; } set { _Right = value; } } public int Bottom { get { return _Bottom; } set { _Bottom = value; } } public int Height { get { return _Bottom - _Top; } set { _Bottom = value + _Top; } } public int Width { get { return _Right - _Left; } set { _Right = value + _Left; } } public Point Location { get { return new Point(Left, Top); } set { _Left = value.X; _Top = value.Y; } } public Size Size { get { return new Size(Width, Height); } set { _Right = value.Width + _Left; _Bottom = value.Height + _Top; } } public static implicit operator Rectangle(RECT Rectangle) { return new Rectangle(Rectangle.Left, Rectangle.Top, Rectangle.Width, Rectangle.Height); } public static implicit operator RECT(Rectangle Rectangle) { return new RECT(Rectangle.Left, Rectangle.Top, Rectangle.Right, Rectangle.Bottom); } public static bool operator ==(RECT Rectangle1, RECT Rectangle2) { return Rectangle1.Equals(Rectangle2); } public static bool operator !=(RECT Rectangle1, RECT Rectangle2) { return !Rectangle1.Equals(Rectangle2); } public override string ToString() { return "{Left: " + _Left + "; " + "Top: " + _Top + "; Right: " + _Right + "; Bottom: " + _Bottom + "}"; } public override int GetHashCode() { return ToString().GetHashCode(); } public bool Equals(RECT Rectangle) { return Rectangle.Left == _Left && Rectangle.Top == _Top && Rectangle.Right == _Right && Rectangle.Bottom == _Bottom; } public override bool Equals(object Object) { if (Object is RECT) { return Equals((RECT)Object); } else if (Object is Rectangle) { return Equals(new RECT((Rectangle)Object)); } return false; } }
解决方案
核心问题是PrintWindow默认会绘制整个窗口,直接修改RECT无法限制截取区域。以下两种方案可实现需求:
方案1:裁剪完整窗口截图(简单易实现)
先捕获整个窗口的Bitmap,再从中裁剪出目标区域:
public static void PrintWindowRegion(IntPtr hwnd, int offsetX, int offsetY, int width, int height) { // 获取窗口整体矩形 RECT windowRect = new RECT(); GetWindowRect(hwnd, out windowRect); // 捕获整个窗口 Bitmap fullBmp = new Bitmap(windowRect.Width, windowRect.Height, PixelFormat.Format32bppArgb); using (Graphics gfx = Graphics.FromImage(fullBmp)) { IntPtr hdc = gfx.GetHdc(); PrintWindow(hwnd, hdc, 0); gfx.ReleaseHdc(hdc); } // 裁剪指定区域(offsetX、offsetY为窗口内部相对坐标) Bitmap regionBmp = fullBmp.Clone(new Rectangle(offsetX, offsetY, width, height), fullBmp.PixelFormat); // 保存裁剪后的Bitmap regionBmp.Save("WindowRegion.png"); // 释放资源 fullBmp.Dispose(); regionBmp.Dispose(); } // 使用示例:截取记事本窗口内从(100,100)开始,宽高100x100的区域 IntPtr hwnd = WinGetHandle("New Text Document.txt - Notepad"); PrintWindowRegion(hwnd, 100, 100, 100, 100);
方案2:直接绘制指定区域(更高效)
通过BitBlt结合窗口DC直接捕获目标区域,需额外引入API:
[DllImport("gdi32.dll")] public 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")] public static extern IntPtr GetWindowDC(IntPtr hWnd); [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDC); public static void CaptureWindowRegionDirect(IntPtr hwnd, int clientX, int clientY, int width, int height) { // 创建目标Bitmap Bitmap regionBmp = new Bitmap(width, height, PixelFormat.Format32bppArgb); using (Graphics gfx = Graphics.FromImage(regionBmp)) { IntPtr hdcDest = gfx.GetHdc(); IntPtr hdcSrc = GetWindowDC(hwnd); // 从窗口的(clientX, clientY)位置复制指定大小到目标Bitmap BitBlt(hdcDest, 0, 0, width, height, hdcSrc, clientX, clientY, 0x00CC0020); // SRCCOPY参数 ReleaseDC(hwnd, hdcSrc); gfx.ReleaseHdc(hdcDest); } regionBmp.Save("DirectRegion.png"); regionBmp.Dispose(); }
注意事项
- 方案中的
offsetX/clientX、offsetY/clientY是相对于窗口客户区的坐标(不含标题栏、边框),若需基于窗口整体坐标,需通过GetClientRect和ClientToScreen进行坐标转换。 - 针对高DPI缩放的窗口,需调整坐标计算逻辑,确保截取区域准确。
内容的提问来源于stack exchange,提问作者Zoetyc
相关产品推荐
相关产品推荐

