Click here to Skip to main content
16,016,301 members
Articles / Desktop Programming / Win32

Screen Capture on Multiple Monitors

Rate me:
Please Sign up or sign in to vote.
4.50/5 (6 votes)
16 Feb 2013CPOL3 min read 87K   5.5K   40   24
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.

Image 1

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).

 

C#
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.  

C#
//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.

C#
[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.

 Image 2

 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. C#
    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. C#
    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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Engineer
India India
Tarsem has been working in the Oil and Gas Industry for more than 10 years as a Field Engineer acquiring well data and then as Petrophysicist, analyzing the data for oil/gas presence.

Comments and Discussions

 
QuestionDual Monitor Support Broken Pin
John Martini16-Dec-19 3:39
John Martini16-Dec-19 3:39 
QuestionWhat is the frame rate obtained? Pin
theRealGreenz125-Dec-16 5:46
theRealGreenz125-Dec-16 5:46 
GeneralThank you Pin
Tailslide9-Sep-14 6:50
Tailslide9-Sep-14 6:50 
Great stuff thanks!

This is the third screen capture source I have tried and the first that handles multiple monitors.
The only small downside is that drawing a box across monitors works but only displays the portion of the box on the monitor where the drag started.

Swapping in code from the 'greatly simplified' comment seems to work great too.
SuggestionGreatly simplified capture code Pin
Gábor Náray7-Jan-14 0:48
Gábor Náray7-Jan-14 0:48 
GeneralRe: Greatly simplified capture code Pin
rjp252530-Sep-14 11:17
rjp252530-Sep-14 11:17 
AnswerRe: Greatly simplified capture code Pin
Gábor Náray13-Dec-14 8:53
Gábor Náray13-Dec-14 8:53 
QuestionFAIL Pin
Tony Farrell18-Nov-13 5:24
Tony Farrell18-Nov-13 5:24 
QuestionMine Does That Too Pin
Patrick Harris17-Feb-13 13:47
Patrick Harris17-Feb-13 13:47 
GeneralMy vote of 2 Pin
Cristian Amarie16-Feb-13 19:00
Cristian Amarie16-Feb-13 19:00 
QuestionPaying yourself 50 cents an hour Pin
tlhIn`toq16-Feb-13 3:59
tlhIn`toq16-Feb-13 3:59 
GeneralRe: Paying yourself 50 cents an hour Pin
PIEBALDconsult16-Feb-13 4:56
mvePIEBALDconsult16-Feb-13 4:56 
GeneralRe: Paying yourself 50 cents an hour Pin
Pascal Ganaye16-Feb-13 9:31
Pascal Ganaye16-Feb-13 9:31 
GeneralRe: Paying yourself 50 cents an hour Pin
PIEBALDconsult16-Feb-13 9:34
mvePIEBALDconsult16-Feb-13 9:34 
GeneralRe: Paying yourself 50 cents an hour Pin
Pascal Ganaye16-Feb-13 9:43
Pascal Ganaye16-Feb-13 9:43 
AnswerRe: Paying yourself 50 cents an hour Pin
mittaltarsem17-Feb-13 2:49
mittaltarsem17-Feb-13 2:49 
AnswerRe: Paying yourself 50 cents an hour Pin
Gábor Náray10-Apr-14 3:20
Gábor Náray10-Apr-14 3:20 
QuestionGood, but... Pin
XTAL25615-Feb-13 14:12
XTAL25615-Feb-13 14:12 
AnswerRe: Good, but... Pin
mittaltarsem15-Feb-13 20:10
mittaltarsem15-Feb-13 20:10 
GeneralRe: Good, but... Pin
Philippe Mori16-Feb-13 3:01
Philippe Mori16-Feb-13 3:01 
GeneralRe: Good, but... Pin
mittaltarsem17-Feb-13 2:40
mittaltarsem17-Feb-13 2:40 
QuestionIncorrect description of display size Pin
Philippe Mori15-Feb-13 12:22
Philippe Mori15-Feb-13 12:22 
AnswerRe: Incorrect description of display size Pin
mittaltarsem15-Feb-13 20:08
mittaltarsem15-Feb-13 20:08 
GeneralRe: Incorrect description of display size Pin
Philippe Mori16-Feb-13 3:14
Philippe Mori16-Feb-13 3:14 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.