65.9K
CodeProject 正在变化。 阅读更多。
Home

多显示器屏幕截图

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (6投票s)

2013 年 2 月 15 日

CPOL

3分钟阅读

viewsIcon

88473

downloadIcon

5635

实用程序,支持多屏幕,用于截取全屏或部分屏幕。

介绍 

很少有人使用双显示器或多显示器,因此大量的屏幕截图/抓取工具不支持第二个显示器。直到我在使用扩展双显示器显示器时,我才真正需要这样的软件;将窗口移动到第一个显示器以截取屏幕截图是一件很痛苦的事情。所以我想到自己制作一个程序,它既小巧又实用,并支持多个显示器。

背景

简单来说,要了解 Windows 的“显示”系统在多显示器上的工作原理,请参见下图。

显示器在没有任何间隙的情况下一个接一个地堆叠在一起。显示器可能具有不同的分辨率。总显示宽度和高度(以像素为单位)可以从最后一个显示器派生(该显示器具有最大的 X 和 Y 的 Bound 值。(感谢 Philippe Mori 的更正)。

 

Screen[] screens;
screens = Screen.AllScreens;
int noofscreens = screens.Length, maxwidth = 0, maxheight = 0;
for (int i = 0; i < noofscreens; i++) 
{
if (maxwidth < (screens[i].Bounds.X + screens[i].Bounds.Width)) maxwidth = screens[i].Bounds.X + screens[i].Bounds.Width;
if (maxheight < (screens[i].Bounds.Y + screens[i].Bounds.Height)) maxheight = screens[i].Bounds.Y + screens[i].Bounds.Height;
}
capture_class.CaptureScreentoClipboard(0,0, maxwidth, maxheight); 
 

Windows 为其(总)显示系统提供了一个设备上下文 (Device Context)。此设备上下文用于将整个显示数据读取到位图中。然后可以将此位图保存到剪贴板或文件中。

使用代码

代码是用 C# 编写的。 Screen_Capture 是主类,它提供捕获 Windows 的全显示并将它保存到剪贴板的功能。它通过以下步骤完成这项工作

  1. 为显示设备创建设备上下文。
  2. 将其转换为兼容的设备上下文(必须执行)。

主方法将左上角 XY 坐标和用户选择区域的宽度/高度作为输入。

//function to capture screen section       
public static void CaptureScreentoClipboard(int x,int y, int wid, int hei) 
{
   //create DC for the entire virtual screen
   int hdcSrc = CreateDC("DISPLAY", null, null, IntPtr.Zero);  
   int hdcDest = CreateCompatibleDC(hdcSrc);
   int hBitmap = CreateCompatibleBitmap(hdcSrc, wid, hei); 
   SelectObject(hdcDest, hBitmap);
   
// set the destination area White - a little complicated
     Bitmap bmp = new Bitmap(wid, hei);
     Image ii = (Image)bmp;
     Graphics gf = Graphics.FromImage(ii);
     IntPtr hdc = gf.GetHdc();
     //use whiteness flag to make destination screen white
     BitBlt(hdcDest, 0, 0, wid, hei, (int)hdc, 0, 0, 0x00FF0062);
     gf.Dispose();
     ii.Dispose();
     bmp.Dispose();
 
//Now copy the areas from each screen on the destination hbitmap
   Screen[] screendata = Screen.AllScreens;
   int X, X1, Y, Y1;
   for (int i = 0; i < screendata.Length; i++)
   {
       if (screendata[i].Bounds.X > (x + wid) || (screendata[i].Bounds.X + 
          screendata[i].Bounds.Width) < x || screendata[i].Bounds.Y > (y + hei) || 
          (screendata[i].Bounds.Y + screendata[i].Bounds.Height) < y )
       {// no common area
       }
       else { 
       // something  common
           if (x < screendata[i].Bounds.X) X = screendata[i].Bounds.X; else X = x;
           if ((x + wid) > (screendata[i].Bounds.X + screendata[i].Bounds.Width)) 
               X1 = screendata[i].Bounds.X + screendata[i].Bounds.Width; else X1 = x + wid;
           if (y < screendata[i].Bounds.Y) Y = screendata[i].Bounds.Y; else Y = y;
           if ((y + hei) > (screendata[i].Bounds.Y + screendata[i].Bounds.Height))
               Y1 = screendata[i].Bounds.Y + screendata[i].Bounds.Height; else Y1 = y + hei;
           // Main API that does memory data transfer
           BitBlt(hdcDest, X-x, Y-y , X1-X, Y1-Y, hdcSrc, X, Y, 
                    0x40000000 | 0x00CC0020); //SRCCOPY AND CAPTUREBLT
       }
   }
 
// send image to clipboard
   Image  imf = Image.FromHbitmap(new IntPtr(hBitmap));
   Clipboard.SetImage(imf);
   DeleteDC(hdcSrc);
   DeleteDC(hdcDest);
   DeleteObject(hBitmap);
   imf.Dispose();
}

上面的各种 API 从 GDI32/USER32.dll 中调用,因为 .NET 没有提供它们。

[DllImport("GDI32.dll")]
public static extern bool BitBlt(int hdcDest,int nXDest,int nYDest,
       int nWidth,int nHeight,int hdcSrc,int nXSrc,int nYSrc,int dwRop);
 
[DllImport("GDI32.dll")]
public static extern int CreateCompatibleBitmap(int hdc,int nWidth, int nHeight);[DllImport("GDI32.dll")]
public static extern int CreateCompatibleDC(int hdc);
 
[DllImport("GDI32.dll")]
public static extern bool DeleteDC(int hdc);
 
[DllImport("GDI32.dll")]
public static extern bool DeleteObject(int hObject);
 
 
[DllImport("gdi32.dll")]
static extern int CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
 
[DllImport("GDI32.dll")]
public static extern int GetDeviceCaps(int hdc,int nIndex);
 
[DllImport("GDI32.dll")]
public static extern int SelectObject(int hdc,int hgdiobj);
 
[DllImport("User32.dll")]
public static extern int GetDesktopWindow();
 
[DllImport("User32.dll")]
public static extern int GetWindowDC(int hWnd);
 
[DllImport("User32.dll")]
public static extern int ReleaseDC(int hWnd,int hDC);  

上面一个值得关注的点是,我们在进行屏幕数据传输之前,将原始兼容的 DC 绘制成白色。这是因为如果用户选择的显示区域跨越具有不同分辨率的不同屏幕,则“隐藏”区域将被传输函数变成黑色。因此,我们使用 API 的 WHITENESS 选项,使用 BitBlt 函数将其绘制成白色。

程序在执行时,会创建一个 Winform,以获取用户输入,选择他是否希望选择特定的屏幕、整个显示器或部分显示器。

 

现在,当用户单击选择时,可以通过多种方式检测鼠标所在的屏幕编号。我们采用以下描述的方法

  1. 我们首先获取连接到系统的显示器的总数。
  2. Screen[] screens;
    screens = Screen.AllScreens;
    int noofscreens = screens.Length; 
  3. 我们在每个显示器上创建一个全尺寸的 Winform。这个 winform 应该具有以下属性:
  4. this.StartPosition = System.Windows.Forms.FormStartPosition.Manual;
    this.Opacity = 0.3;
    this.ControlBox = false;
    this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None;
    this.Location = new Point(xstart, ystart);
    this.WindowState = FormWindowState.Maximized;

Startposition 设置为 Manual,我们可以显式地将窗口放置在任何显示器上。通过将其窗体样式设置为 none,我们得到整个屏幕作为客户端区域。透明度为 0.3 使得背景桌面几乎清晰可见。Startlocation 从每个屏幕的 screens.Bound.XY 属性中读取。

我们创建鼠标事件来检测对每个 WinForm 的点击,然后触发所有这些子 WinForms 的关闭,并使用区域信息调用 screencapturetoclipboard 函数。

使用每个窗口上的 mousemove 事件,我们还绘制一个矩形,用户可以使用它来查看正在选择的区域。

关注点

代码中有两个最重要的事情

  1. 将子 WinForms 的 Size 属性设置为 Manual。没有这个,我们无法将窗口放置在每个显示器上。
  2. 在使用 Bitblt API 进行桌面复制期间,我们应该使用 CAPTUREBLT 标志来复制分层窗口。
  3. 用户可以选择桌面的方式有很多种。主要功能是获取整个屏幕的位图。
  4. 如果用户从多个屏幕中选择区域,则“隐藏”区域将被涂成黑色。为了避免这种情况,我们将初始目标位图涂成白色。从显示器向我们的位图进行图像传输时,我们扫描检查用户从每个屏幕中选择的区域,并且只传输该区域。这样我们就避免了显示器“隐藏”部分的黑色区域。

历史

我想感谢一些网站,从这些网站我获得了制作这些实用程序的帮助,例如:tech.pro、c-sharpcorner.com 和 codeproject.com。

© . All rights reserved.