When creating applications like screen recorders and remote viewers, captured screenshots need to include the mouse cursor. It’s easy to capture a screenshot with C#, however, it’s a little trickier to include the mouse cursor, because it involves unmanaged calls to the Windows API.
This post will show you how to create a utility class that captures the screen with the mouse cursor in the correct position. If you don’t want to be bored with the details, you can download the example project from here.
Capture a screenshot without the mouse cursor
You can capture a screenshot without the mouse cursor with several lines of code like this:
public static class ScreenUtil
{
public static Bitmap Capture(int x, int y, int width, int height)
{
var bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(x, y, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy);
}
return bitmap;
}
}
The code above shows a static method called Capture
which is part of a utility class used to generate the screenshot.
The method arguments are used to specify what portion of the screen to capture.
The x
and y
arguments are used to specify where the top left point of the capture area on the screen starts from, and the width
and height
arguments are used to determine the size of the captured area. For example, if you wanted to capture the full screen of a 1024×768 monitor, you would call Capture(0, 0, 1024, 768)
.
The rest of the code is pretty self explanatory. We are creating a Bitmap
image to contain the screenshot, and using the Graphics
object to capture the specified portion of the screen. Notice the use of the using statement to make sure the Graphics
object is properly disposed.
Now, lets take a look at how we can expand the code above to include the mouse cursor image.
Capture a screenshot WITH the mouse cursor
If you call the Capture
method in the previous example, you’ll get a copy of what’s on the screen without the cursor. If you need to include the cursor, you’ll need to call the Win32 API to get the cursor image, and then draw this image on top of the captured screen image.
There are two simple things we need to do, first we need to create a native wrapper class, and then, we need to modify the Capture
method to copy the mouse cursor onto the captured screenshot.
1. User32 native wrapper class
The native wrapper class is used to define the external API calls to the Win32 API. For this example, we’ll call the class User32
because the calls are to the user32.dll, feel free to call it something better if you like:
public static class User32
{
public const Int32 CURSOR_SHOWING = 0x00000001;
[StructLayout(LayoutKind.Sequential)]
public struct ICONINFO
{
public bool fIcon;
public Int32 xHotspot;
public Int32 yHotspot;
public IntPtr hbmMask;
public IntPtr hbmColor;
}
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public Int32 x;
public Int32 y;
}
[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
public Int32 cbSize;
public Int32 flags;
public IntPtr hCursor;
public POINT ptScreenPos;
}
[DllImport("user32.dll")]
public static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
public static extern IntPtr CopyIcon(IntPtr hIcon);
[DllImport("user32.dll")]
public static extern bool DrawIcon(IntPtr hdc, int x, int y, IntPtr hIcon);
[DllImport("user32.dll")]
public static extern bool GetIconInfo(IntPtr hIcon, out ICONINFO piconinfo);
}
In the code above, we’re specifying a list of unmanaged external methods from the Windows API that we wan’t to call from inside our managed code, this is known as PInvoking.
The DllImport
attribute combined with the static extern
keyword is what makes the call to unmanaged code possible. We are importing the unmanaged methods from the user32.dll because it contains the Windows API calls that we need to get information about the mouse cursor.
The structures are defined for passing the data to and from the managed/unmanaged code. With this class created, we can now modify the Capture
method to copy the cursor image onto the correct location of the captured screenshot.
2. Modify Capture method to include cursor image
Change the Capture
to the following:
public static Bitmap Capture(int x, int y, int width, int height)
{
var bitmap = new Bitmap(width, height, PixelFormat.Format24bppRgb);
using (Graphics g = Graphics.FromImage(bitmap))
{
g.CopyFromScreen(x, y, 0, 0, bitmap.Size, CopyPixelOperation.SourceCopy);
User32.CURSORINFO cursorInfo;
cursorInfo.cbSize = Marshal.SizeOf(typeof(User32.CURSORINFO));
if (User32.GetCursorInfo(out cursorInfo))
{
// if the cursor is showing draw it on the screen shot
if (cursorInfo.flags == User32.CURSOR_SHOWING)
{
// we need to get hotspot so we can draw the cursor in the correct position
var iconPointer = User32.CopyIcon(cursorInfo.hCursor);
User32.ICONINFO iconInfo;
int iconX, iconY;
if (User32.GetIconInfo(iconPointer, out iconInfo))
{
// calculate the correct position of the cursor
iconX = cursorInfo.ptScreenPos.x - ((int)iconInfo.xHotspot);
iconY = cursorInfo.ptScreenPos.y - ((int)iconInfo.yHotspot);
// draw the cursor icon on top of the captured screen image
User32.DrawIcon(g.GetHdc(), iconX, iconY, cursorInfo.hCursor);
// release the handle created by call to g.GetHdc()
g.ReleaseHdc();
}
}
}
}
return bitmap;
}
The difference in the code above, from the first Capture
method, is what happens after the call to CopyFromScreen
.
First, the cursor info is retrieved with the call to GetCursorInfo
. Then, if the cursor is showing it gets the icon hotspot by calling the GetIconInfo
.
It’s important to get the hotspot so we can adjust the position of the cursor on the screen. If we just called the DrawIcon
with the ptScreenPos.x
and ptScreenPos.y
, the cursor would be drawn slightly offset. We fix this by taking away the hostpot x and y from the ptScreenPos x and y.
The DrawIcon
method draws the cursor onto the captured screen image at the position in iconX
and iconY
. The handle is then released with the call to ReleaseHdc
and then the Bitmap
is returned.
Testing the Capture method out
You can test the Capture
method by adding the following code to the KeyUp
event of your WPF window, or by downloading the example project from here:
if (e.Key == Key.Space)
{
using (var bitmap = ScreenUtil.Capture(0, 0, 500, 500))
{
bitmap.Save("screen.png");
}
}
Now, when you press space, a screenshot containing the desktop and mouse cursor will be saved into the Debug folder called screen.png.
Conclusion
In this post, we’ve learned how to create a utility class that can take a screenshot which includes the mouse cursor. This simple class can be used as the base for a new Snipping tool, or a GifRecorder, or even the next VNC 🙂
Download the example try it out.