I was recently updating some documentation and wanted to programmatically capture some screenshots of the application in different states. This article describes how you can easily capture screenshots in your own applications.
Using the Win32 API
This article makes use of a number of Win32 API methods. Although you may not have much call to use them directly in day to day .NET (not to mention Microsoft wanting everyone to use universal "apps" these days), they are still extraordinarily useful and powerful.
This article does assume you know the basics of platform invoke so I won't cover it here. In regards to the actual API's I'm using, you can find lots of information about them either on MSDN, or PInvoke.net.
A number of the API's used in this article are GDI calls. Generally, when you're using the Win32 GDI API, you need to do things in pairs. If something is created (pens, brushes, bitmaps, icons etc.), then it usually needs to be explicitly destroyed when finished with (there are some exceptions just to keep you on your toes). Although there haven't been GDI limits in Windows for some time now (as far as I know!), it's still good not to introduce memory leaks. In addition, device contexts always have a number of objects associated with them. If you assign a new object to a context, you must restore the original object when you're done. I'm a little rusty with this so hopefully I'm not missing anything out.
Setting up a device context for use with BitBlt
To capture a screenshot, I'm going to be using the
This copies information from one device context to another,
meaning I'm going to need a source and destination context to
The source is going to be the desktop, so first I'll use the
GetWindowDC calls to obtain this. As
GetWindowDC essentially places a lock on it, I also
need to release it when I'm finished with it.
Now for the destination - for this, I'm going to create a memory
CreateCompatibleDC. When you call this API, you
pass in an existing DC and the new one will be created based on
There's still one last step to perform - by itself, that memory
DC isn't hugely useful. We need to create and assign a GDI
bitmap to it. To do this, first create a bitmap using
CreateCompatibleBitmap and then attach it to the DC using
SelectObject will also return the relevant old
object which we need to restore (again using
when we're done. We also use
DeleteObject to clean up the
Although this might seem like a lot of effort, it's not all that
different from using objects implementing
IDisposable in C#,
just C# makes it a little easier with things like the
Calling BitBlt to capture a screenshot
With the above setup out the way, we have a device context which
provides access to a bitmap of the desktop, and we have a new
device context ready to transfer data to. All that's left to do
is make the
If you've ever used the
DrawImage method of a
object before, this call should be fairly familiar - we pass in
the DC to write too, along with the upper left corner where data
will be copied (
0, 0 in this example), followed by the
height of the rectangle - this applies to both the source
and destination. Finally, we pass in the source device context,
and the upper left corner where data will be copied from, along
with flags that detail how the data will be copied.
In my old VB6 days, I would just use
SRCCOPY (direct copy),
but in those days windows were simpler things. The
flag ensures the call works properly with layered windows.
If the call fails, I throw a new
Win32Exception object without
any parameters - this will take care of looking up the result
code for the
BitBlt failure and filling in an appropriate
Now that our destination bitmap has been happily "painted" with
the specified region from the desktop we need to get it into
.NET-land. We can do this via the
FromHbitmap static method of
Image class - this method accepts a GDI bitmap handle and
return a fully fledged .NET
Bitmap object from it.
Putting it all together
As the above code is piecemeal, the following helper method will
Rectangle which describes which part of the desktop
you want to capture and will then return a
containing the captured information.
try ... finallyblock used to try and free GDI resources if the
FromHbitmapcalls fail. Also note how the clean-up is the exact reverse of creation/selection.
Now that we have this method, we can use it in various ways as demonstrated below.
Capturing a single window
If you want to capture a window in your application, you could
Capture with the value of the
Bounds property of your
Form. But if you want to capture an external window then
you're going to need to go back to the Win32 API. The
GetWindowRect function will return any window's boundaries.
Win32 has its own version of .NET's
Rectangle structure, named
RECT. This differs slightly from the .NET version in that it
bottom properties, not
Rectangle class has a helper method,
Rectangle from left, top, right and bottom
properties which means you don't need to perform the subtraction
Depending on the version of Windows you're using, you may find that you get slightly unexpected results when calling
GetWindowRect. As I don't want to digress to much, I'll follow up why and how to resolve in another post (the attached sample application includes the complete code for both articles).
Capturing the active window
As a slight variation on the previous section, you can use the
GetForegroundWindow API call to get the handle of the active
Capturing a single monitor
.NET offers the
Screen static class which provides access to
all monitors on your system via the
AllScreens property. You
can use the
FromControl method to find out which monitor a
form is hosted on, and get the region that represents the
monitor - with or without areas covered by the task bar and
other app bars. This means it trivial to capture the contents of
a given monitor.
Capturing the entire desktop
It is also quite simple to capture the entire desktop without
having to know all the details of monitor arrangements. We just
need to enumerate the available monitors and use
Rectangle.Union to merge two rectangles together. When this is
complete, you'll have one rectangle which describes all
There is one slight problem with this approach - if the resolutions of your monitors are different sizes, or are misaligned from each other, the gaps will be filled in solid black. It would be nicer to make these areas transparent, however at this point in time I don't need to capture the whole desktop so I'll leave this either as an exercise for the reader, or a subsequent update.
Capturing an arbitrary region
Of course, you could just call
CaptureRegion with a custom
rectangle to pick up some arbitrary part of the desktop. The
above helpers are just that, helpers!
A note on display scaling and high DPI monitors
Although I don't have a high DPI monitor, I did temporarily scale the display to 125% to test that the correct regions were still captured. I tested with a manifest stating that the application supported high DPI and again without, in both cases the correct sized images were captured.
The demo program
A demonstration program for the techniques in this article is available from the links below. It's also available on GitHub.
- 2017-08-27 - First published
- 2020-11-22 - Updated formatting
Like what you're reading? Perhaps you like to buy us a coffee?