Skip Navigation LinksHome > View Post

Part III. Grabbing the ScreenShot

Previously, we introduced a series of posts that would show the interesting parts of a (very basic) feedback service implementation. This is part II, here are the other bits:

Grabbing the Screenshot

As I mentioned in the intro. I want my Smart Client's feedback mechanism to support optionally grabbing a screenshot and including that in the data (we don't want to enforce this as there are obvious privacy concerns).

Grabbing images from the screen is surprisingly easy thanks to .NET's rich BCL. As you can imagine, we're going to be diving into the System.Drawing and System.Drawing.Imaging namespaces to achieve our goal here.

Firstly, lets assume that we've created our Service Reference that will allow our client to call our WCF service (either by choosing 'Add Service Reference' in Visual Studio or using svcutil.exe on the command line). This will have created a 'copy' of the Feedback class (introduced in Part I). One of the properties of this class, called ScreenGrab is of type byte[] and this is where we need to store the image data (if the user has opted into sending a grab).

Let's take a look at the code.

We need to decide which screen we're going to grab (remember, the user may have multiple displays). For now, we'll just assume it's the primary screen but we'll improve on this later.

public static byte[] GrabScreenPng()
{
    //Use the Primary screen for now...
    Screen screen = Screen.Primary;

    // How big is the screen
    Rectangle rect = screen.Bounds;

    // Create a bitmap to store the grab
    Bitmap bmp = new Bitmap(rect.Width, rect.Height, PixelFormat.Format32bppArgb);

    // Create a graphics object from the bitmap
    Graphics graphics = Graphics.FromImage(bmp);

    // Copy from the screen (this is the magic sauce)
    graphics.CopyFromScreen(0, 0, 0, 0, new Size(rect.Width, rect.Height));

    // create a byte array to store the image
    byte[] bytes;

    using (MemoryStream ms = new MemoryStream())
    {
        // save in PNG format to our memorystream
        bmp.Save(ms, ImageFormat.Png);

        using (BinaryReader br = new BinaryReader(ms))
        {
         ms.Seek(0, SeekOrigin.Begin);
         // read out the bytes
         bytes = br.ReadBytes(Convert.ToInt32(ms.Length));
        }
    }
    
    return bytes;
}

Easy eh? Also, note how we've opted for PNG format (because I like it).

Now, we actually want to grab the screen that contains most of our application which might not be the Primary screen. Fortunately the Screen type has a static method called FromHandle(IntPtr hwnd) that can help us here: Retrieves a System.Windows.Forms.Screen for the display that contains the largest portion of the object referred to by the specified handle..

So all we need to do is pass the Handle of our main window which should be easy enough. Except that I'm using WPF and, unlike System.Windows.Forms.Form type, WPF's Window doesn't have a Handle property :(.

Not an insurmountable problem though - we just need to invoke the WindowsInteropHelper (hiding in the System.Windows.Interop namespace):

IntPtr windowHandle = new WindowInteropHelper(Application.Current.MainWindow).Handle;

Now we can remove the use of the primary screen:

//Use the screen that contains most of our main screen
Screen screen = Screen.FromHandle(windowHandle);
    
Of course, we could grab all screens and join them up into one huge image. I'll leave that as an exercise for the reader if that's what you want to do.

Showing a Preview

Yesterday we had a brief interlude with this post but the timing wasn't entirely coincidental. I want to use the workaround in this article today.

Now you're probably wondering why we need to use an Image control. Simple - in order to increase the chances that the user will submit the screenshot we're going to give them a little preview in the feedback window.

To do that we'll save the image to disk* and then we can point an Image element at the file and we have our preview. Because the user could potentially have two applications open and be sending two bits of feedback at the same time (unlikely I know) it's not enough to hardcode a location for your image, we need a random image name (I used a GUID, e.g.: string fileName = Guid.NewGuid().ToString("N") + ".png";).

The important thing here is to clean up this random image when the dialog is finished with (we don't want to litter the user's computer with screen shots) and we can't do this unless we use the BitmapSource element mentioned in the workaround. As always, I'm using binding so I need the second part of the workaround also.

To tempt you in, here's what our Send Feedback window is going to look like (leave a comment if you'd like me to cover how to build a layout like this in WPF).

the Send Feedback dialog

Notice that I've gone a little further and collect the user's name and also give them the option to send (and preview) the application's log file.

Next, we'll finish looking at our WCF client.

Part IV: Sending the Data from the Client

Tags: .NET

 
Josh Post By Josh Twist
1:30 AM
08 Aug 2007

» Next Post: Part IV. Sending the Data from the Client
« Previous Post: WPF Image element locks my local file

Comments are closed for this post.

Posted by Inferis @ 08 Aug 2007 3:06 AM
That's one tall dialog.

Posted by Josh @ 08 Aug 2007 4:18 AM
Agreed, at 780 pixels it is a little tall but it is resizable :)

Posted by Inferis @ 08 Aug 2007 6:08 AM
Wouldn't a horizontal approach be more beneficial?

Btw, nice series of articles...

Posted by Josh @ 08 Aug 2007 6:41 AM
You're the second person to say that so I'm starting to think you must be right (Bruusi, my co-blogger said the same thing).

My reply was that the dialog would be almost as wide then as it is tall now. Specifically, what's the problem with the height? Are you worried it wouldn't fit on a low-res screen?

Bruusi - had another great suggestion: why not skip the whole preview and just have a 'view screenshot' link as I did with the logfile...

BTW, I like this blogging by committee thing so thanks for the feedback and keep it coming. Also, glad to hear you're enjoying the series.

Posted by Josh @ 08 Aug 2007 6:48 AM
I've swapped the image for how big the dialog is by default in my application (I had resized it to make the image clearer previously).

400x700, Is that any better?

(you may need to CTRL+Refresh)

Posted by Inferis @ 08 Aug 2007 4:03 PM
Well, I still like wider dialogs than taller dialogs. After all, you have more screen real estate horizontally than vertically.

However, I can see how that moving to a more horizontal approach might complicate the user experience. You have everything is a nice ordered way (although I'd have the comment field before the name field because the comment is more valuable to you than who's submitting it).

The view screenshot link is a good idea, probably. ;)

© 2005 - 2014 Josh Twist - All Rights Reserved.