Page 1 of 1

Taking a screen shot in C#

#1 PsychoCoder  Icon User is offline

  • Google.Sucks.Init(true);
  • member icon

Reputation: 1638
  • View blog
  • Posts: 19,853
  • Joined: 26-July 07

Post icon  Posted 07 October 2007 - 04:49 PM

Welcome to my tutorial on Taking a Screen Shot with C#. This tutorial is a little on the advanced side and assumes you have a fairly good working knowledge of the System.Drawing Namespace and a working knowledge Win32 API Calls as well, since we use 9 of them.

As with any class or control you create, the first thing to add is your Namespace references. You add these to keep you from having to type out something like System.IO.Directory.CreateDirectory, if you had using System.IO you would only need to type Directory.CreateDirectory, so add these references to your class

#region Class Imports 
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using System.ComponentModel;
#endregion



NOTE:Once again, I use #region to separate my code files into sections, but thats a design choice I use, you may choose to not use them.

Next we have our global variables. There are many people who say using global variables are a bad idea, and just as many who say they're ok. I don't think this debate will ever end, but I think both sides have a valid argument, at least part of their argument, so I use them sparingly and cautiously:

#region Global Variables
private Bitmap _screenShot;
protected static IntPtr newBMP;
#endregion



Normally when I use Constants in a project I put them in their own Code Module, but since this control only uses 3 Constant I went ahead and put them in this file as well:

#region Constants
public const int SRCCOPY = 13369376;
public const int SCREEN_X = 0;
public const int SCREEN_Y = 1;
#endregion



One of our global variables is actually assigned to our single control Property. This property is what returns the actual screen shot to the calling form:

#region Class Properties
[Description("Gets the screenshot image")]
public Bitmap ScreenImage
{
	get { return _screenShot; }
}
#endregion



Now we come to our Constructors, constructors are very important as they allow us to instantiate our class in other areas, like in the form they're to be used in. I have seen many new developers who forget their constructors then cant figure out why they cant instantiate their objects, so we need then. For this control, we have a single constructor:

#region Constructor
/// <summary>
/// Constructors for our class
/// </summary>
[Description("Empty constructor, instantiating _screenShot to nothing")]
public ScreenShot()
{
	_screenShot = null;
}			  
#endregion



In this constructor we're initialize our variable _screenShot to nothing (null in C#), that way each time the class is instantiated it creates a new Bitmap. Now we move on to the last 2 methods of this class. The first method actually does all the work. Here is where our Win32 API calls are. We create a handle to the desktops device context, then we create a memory device context. From there we pass our SCREEN_X and SCREEN_Y Constants to the Win32 API GetSystemMetrics, which will get us the X and Y coordinates of the screen.

We then create a new Bitmap using the CreateCompatibleBitmap Win32 API. When we go to check and make sure a handle was actually created, since our newBMP is a IntPtr data type we cant check for a null as you cant compare an IntPrt with null, so we use IntPtr.Zero.

In this check, if we discover an handle was actually created we select the new image into memory, copy the old image into memory, we release our device context variables (mem, dsk) to free up the resources, we then use the [rl=http://msdn2.microsoft.com/en-us/library/system.drawing.image.fromhbitmap.aspx]Image.FromHbitmap[/url] Method to create an image from the windows handle we created earlier:

#region Methods
/// <summary>
/// Method for creating an image of the current desktop
/// </summary>
/// <returns>A Bitmap image</returns>
[Description("Creates an image of the current desktop")]
public Bitmap GetScreen()
{
	int xLoc;
	int yLoc;
	IntPtr dsk;
	IntPtr mem;
	Bitmap currentView;

	//get the handle of the desktop DC
	dsk = Win32API.GetDC(Win32API.GetDesktopWindow());

	//create memory DC
	mem = Win32API.CreateCompatibleDC(dsk);

	//get the X coordinates of the screen
	xLoc = Win32API.GetSystemMetrics(SCREEN_X);

	//get the Y coordinates of screen.
	yLoc = Win32API.GetSystemMetrics(SCREEN_Y);

	//create a compatible image the size of the desktop
	newBMP = Win32API.CreateCompatibleBitmap(dsk, xLoc, yLoc);

	//check against IntPtr (cant check IntPtr values against a null value)
	if (newBMP != IntPtr.Zero)
	{
		//select the image in memory
		IntPtr oldBmp = (IntPtr)Win32API.SelectObject(mem, newBMP);
		//copy the new bitmap into memory
		Win32API.BitBlt(mem, 0, 0, xLoc, yLoc, dsk, 0, 0, SRCCOPY);
		//select the old bitmap into memory
		Win32API.SelectObject(mem, oldBmp);
		//delete the memoryDC since we're through with it
		Win32API.DeleteDC(mem);
		//release dskTopDC to free up the resources
		Win32API.ReleaseDC(Win32API.GetDesktopWindow(), dsk);
		//create out BitMap
		currentView = Image.FromHbitmap(newBMP);
		//return the image
		return currentView;
	}
	else  //null value returned
	{
		return null;
	}	
}
#endregion



I know that looks big and ugly, but I added many comments so you can see exactly what's going on in there. You will notice a reference to Win32API in this method, that's the class file we create later than does nothing but hold our Win32 API Functions.

Only 1 more method remaining in this class, and that's the method that takes the image we created in GetScreen() and saves it to a location we specify, with the name we specify, then returns it to our form via the ScreenImage Property of this class. This method also checks to verify that the folder name provided exists, if it doesn't then it creates the folder before saving the image:

#region Helpers
[Description("Takes the information from GetScreen and creates a Bitmap image")]
public void GetScreenShot(string folder, string name)
{
	//check to see if the folder provided exists
	if (!Directory.Exists(Application.StartupPath + "\\" + folder))
	{
		//if it doesnt exist then we need to create it
		Directory.CreateDirectory(Application.StartupPath + "\\" + folder);
	}
	//set the ScreenImage Property to the
	//BitMap created in GetScreen()
	_screenShot = new Bitmap(GetScreen());
	//create a name based on the name passed in
	string ingName = Application.StartupPath + "\\" + folder + "\\" + name + ".bmp";
	//save the image
	_screenShot.Save(ingName);
}
#endregion



Well thats the end of the first class, this class actually does all the work, the next class is relatively small as it's only for our Win32 API Functions. In this class, as with all class files you create, you need to add your Namespace References, here are the references we need for this class:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.ComponentModel;



Half of our Win32 API Functions import the user32.dll, the other half import gdi32.dll. As you'll notice, in this class we have no Constructors, we don't need constructors here as all the functions are public static, meaning we can call them without instantiating the class. First here are the user32 API Functions:

#region user32 API
[DllImport("user32.dll", EntryPoint = "GetDC")]
	  public static extern IntPtr GetDC(IntPtr ptr);
[DllImport("user32.dll", EntryPoint = "GetDesktopWindow")]
	  public static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", EntryPoint = "ReleaseDC")]
	  public static extern IntPtr ReleaseDC(IntPtr hWnd, IntPtr hDc);
[DllImport("user32.dll", EntryPoint = "GetSystemMetrics")]
	  public static extern int GetSystemMetrics(int abc);
#endregion



Next our gdi32 API Functions:

#region gdi32 API
[DllImport("gdi32", EntryPoint = "CreateCompatibleDC")]
	  public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
[DllImport("gdi32", EntryPoint = "CreateCompatibleBitmap")]
	  public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
[DllImport("gdi32", EntryPoint = "SelectObject")]
	  public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);
[DllImport("gdi32", EntryPoint = "BitBlt")]
	  public static extern bool BitBlt(IntPtr hDestDC, int X, int Y, int nWidth, int nHeight, IntPtr hSrcDC, int SrcX, int SrcY, int Rop);
[DllImport("gdi32", EntryPoint = "DeleteDC")]
	  public static extern IntPtr DeleteDC(IntPtr hDC);
#endregion



Granted, we really didn't have to create a separate class for our functions, but in my opinion doing it this way, which is really a design choice, makes all the code much more readable and easy to maintain. Well thats it, thats what it takes to create a screen shot from code. Now, just create your form, add a picture box and a button, and call your class like so:

private void button1_Click(object sender, EventArgs e)
{
	shot = new ScreenShot();
	shot.GetScreenShot("","");
	viewer1.ImageSizeMode = PC.Viewer.SizeMode.Scrollable;
	viewer1.AutoScroll = true;
	viewer1.BackgroundImage = shot.ScreenImage;
	viewer1.BackgroundImageLayout = ImageLayout.Center;
	viewer1.Image = shot.ScreenImage;
}



viewer1 is the name of my custom user control, which inherits from the PictureBox, but you can use a standard PictureBox for this, you don't need to have a custom control. I will be providing both class files used for this tutorial. Remember, both files are under a GNU General Public License meaning you can modify, use and distribute as you see fit, but the header file must stay intact. I hope you found this tutorial informative and useful, and thank you for reading.

Attached File  PC_Screenshot.zip (14.05K)
Number of downloads: 3130

Happy Coding :)

Is This A Good Question/Topic? 0
  • +

Page 1 of 1