Monday, April 13, 2009

Enhanced Screen Capture Code

I had this crazy idea the other day. I want to see what my users screen looks like at the time an exception occurred. A quick Google search led me to this snippet of code on about.com.

After a bit of testing I found out that it only captures the main desktop or active window. Well since many of our users run with multiple monitors, it's possible our application is running on the second monitor. This means that if I capture the main desktop would potentially reveal nothing valuable.

So I rewrote the code to handle multiple monitors better.

So here is the interface section.


uses
Windows, SysUtils, Graphics, MultiMon;

type
EMonitorCaptureException = class(Exception);

TCaptureContext = (ccActiveWindow,ccDesktopMonitor,ccActiveMonitor,
ccSpecificMonitor,ccAllMonitors);


function MonitorCount : Integer;

procedure CaptureScreen(aCaptureContext : TCaptureContext; destBitmap : TBitmap;aMonitorNum : Integer = 1);

procedure CaptureRect(aCaptureRect : TRect;destBitMap : TBitmap); inline;

procedure CaptureDeviceContext(SrcDC: HDC;aCaptureRect : TRect;destBitMap : TBitmap); inline;


The enumerated type TCaptureContext allows few different options.


  • ccActiveWindow: Capture an image of the ActiveWindow only.
  • ccDesktopMonitor: Capture an image of the primary/first monitor.
  • ccActiveMonitor: Capture an image of the monitor with the active window on it. If the active window is on more than one monitor it will pick the monitor that has the larger portion of the window on it. If the active window is not in the visible space of any monitor, it will pick the closest one.
  • ccSpecificMonitor: Capture an Image of a Specific Monitor specified by the aMonitorNum parameter. Note: aMonitorNum the first monitor is 1. You can use the MonitorCount function to determine the valid range of monitors. An EMonitorCaptureException will occur if you select a monitor number outside the valid range.
  • ccAllMonitors: Capture a single image of all the monitors.


The entire code for the unit can be downloaded from my SVN Repository.

To see it in Action...

  1. File | New VCL Form Application.
  2. Add SCapture.pas to the project, or place it in your library path
  3. Drop down an Button and an Image on the Form
  4. Place the following code in your button.



var
b : TBitMap;
begin
b := TBitmap.Create;
try
CaptureScreen(ccAllMonitors,B,1);
Image1.Picture.Assign(b);
finally
b.FreeImage;
b.Free;
end;


There is also another routine that allows you to capture any Rect from the display.


var
b : TBitMap;
r : TRect;
begin
b := TBitmap.Create;
try
r.top := 20;
r.Left := 20;
r.Bottom := 200;
r.Right := 200;
CaptureRect(R,B);
Image1.Picture.Assign(b);
finally
b.FreeImage;
b.Free;
end;


Going back to the crazy idea of getting a screenshot on an exception. I have not implemented it yet, but something things to think about if you going to try.


  • EOutofMemory and EOutofResource Exceptions will most likely cause this code to fail, I would check the Exception class and if it's one of these don't take the screen shot.
  • Unhandled Exception's raised in the Exception Handler mask the original error. So if taking the screen shot fails, don't annoy the user.
  • Privacy & Security: The images of the desktop may contain information that may violate users privacy or be a security risk of image is not secured.
  • You want to capture the screen before the exception dialog appears otherwise the information you want will be masked by the dialog.


Regardless of my concerns, I will be coming up with a solution the positive outcome is greater than any potential negatives.

2 comments:

  1. Have you noticed that if your primary monitor is positioned to the RIGHT of your second monitor, this code fails. It captures a bitmap showing the primary monitor contents to the LEFT and a blank area to the right. Any thoughts?

    ReplyDelete
  2. The solution appears to be to use GetSystemMetrics(SM_XVIRTUALSCREEN) and GetSystemMetrics(SM_YVIRTUALSCREEN) for XSrc and YSrc in BitBlt instead of 0,0. 0,0 assumes your primary monitor is always the left most one.

    ReplyDelete