Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Screen Capture on Multiple Monitors

0.00/5 (No votes)
16 Feb 2013 1  
Utility to capture full or part of screen with support for multiple screens.

Introduction 

Not many people have dual or multiple monitors and thus a large chunk of screen capture/grabbers don't offer any support for second monitor. I myself never needed a software like this until I was working on extended dual monitor display; and it was a pain to move the window to first display to be able to capture a screenshot. So I thought of making a program myself that would be small, to the point and support multiple monitors.

Background

In simple sense, to understand how Windows 'display' system works on multiple monitors, see the picture below.

Monitors are stacked one after the another without any gap in either direction. Monitors may have different resolution. Total display width and height in pixels can be derived from the last monitors (who has the largest Bound value for X and Y. (Thanks Philippe Mori for the correction).

 

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 provide a Device Context to its (total) display system. This Device context is used to read the entire display data into a bitmap. This bitmap can then be saved into clipboard or a file. 

Using the code

Code is written in C#. Screen_Capture is the main class that provide functionality to capture the Full display of the windows and save it to clipboard. It does this work in these steps:

  1. Create Device Context for display device.
  2. Convert it to compatible Device Context (has to be done).

 The main method takes input the left-top XY coordinates and the width/height of the user selected area.  

//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();
}

Various APIs above are called from GDI32/USER32.dll as they are not provided by .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);  

One point of interest above is that we are painting the original compatible DC white before making the screen data transfer. This is because if the user selects display area spanning different screens of different resolutions,   the 'hidden' area will be made black by the  transfer function. We are thus painting it white with BitBlt function using the WHITENESS option of the API. 

Program, on execution, makes a Winform to get input from user to choose if he wish to select a particular screen,  whole display or part of the display.

 

 Now there can be many ways to detect the screen number on which our mouse is when the user makes a click to select it. We are following the method described below:

  1. We begin by getting the total number of monitors attached to the system. 
  2. Screen[] screens;
    screens = Screen.AllScreens;
    int noofscreens = screens.Length; 
  3. We create a full size Winform on each monitor. This winform should have the following properties: 
  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;

Making Startposition as Manual, we can explicitly place the  window in any monitor. By making its form style none, we get the entire screen as client area. Opacity of 0.3 makes the background desktop visible almost clearly. Startlocation is read from the screens.Bound.X and Y property for each screen.

We create mouse event to detect clicks on each WinForm which then triggers the closure of all these child WinForms and call the screencapturetoclipboard function with area information.

Using the mousemove event on each window, we also draw a rectangle which the user can use to see the area being selected. 

Points of Interest

There are two most important things in the code:

  1. Setting the children WinForms Size property to Manual. Without this, we can't place the windows on each monitor.
  2. During desktop copy using Bitblt API, we should use the CAPTUREBLT flag to copy the layered windows. 
  3. There can be many ways in which the user can be made to select the desktop. Primary function is getting the bitmap of the entire screen.
  4. If the user selects area from multiple screens, the 'hidden' area is colored black. To avoid this, we paint the initial target bitmap white. When making the image transfer from display to our bitmap, we scan check for the area selected by user from each screen and transfer only that area. This way we avoid showing black area for 'hidden' sections of the display. 

History

I would like to thank some websites from where I got help in making these utility, namely: tech.pro, c-sharpcorner.com, and codeproject.com.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here