在进行演示或监控任务时,可能需要对某些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);
}
}
}