ScreenShots.cs: How to capture screenshots of your app on your device

One thing that’s surprisingly hard to do on the Windows Phone is take screenshots of your application when it is running. Here’s a really simple solution that I coded up while working with some customers – you just give it an interval of time and it’ll take screenshots every few seconds, storing the resulting image as a JPEG in isolated storage.

This is useful for apps that need to be running on a real device, or if you need to capture transitions or difficult-to-reproduce actions.

Using ScreenShots.cs

Drop the code into your project, or use NuGet. Then, open up App.xaml.cs (where the performance counters and other debug viz tools are) and add this code to enable the screenshots:

ScreenShots.BeginTakingPictures();

Of course you could also add it just to a specific page.

The default interval for captures is every 2 seconds, but you can optionally provide a value for that, too. For example, to take two pictures a second,

ScreenShots.BeginTakingPictures(0.5);

Reminder: don’t ship with this as you’ll fill your phone’s storage with captures! I’d recommend commenting out the code most of the time. You could also add screen shot capability in a hidden debug screen in your app, etc.

Getting the screenshots from isolated storage

To actually get the screenshots, you can use the “isolated storage tool" that is hidden away in the new 7.1 SDK. From a command prompt, just run ISETool.exe:

pushd C:\Program Files (x86)\Microsoft SDKs\Windows Phone\v7.1\Tools\IsolatedStorageExplorerTool

ISETool ts de <GUID>

You can also provide a location to store the assets. I believe it uses the current directory otherwise.

You can get your app ID from the WMAppManifest.xml file that’s in your project’s Properties folder.

Another option is to use Oren’s nice Windows Phone Power Tools (CodePlex link) to super easily do this (I highly recommend this):

CaptureTool

Get ScreenShot.cs

I’ve published to NuGet this source file:

Or here’s the source:

//
// Copyright (c) 2010-2011 Jeff Wilcox
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//

using System;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.IsolatedStorage;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;

namespace System.Windows
{
    /// <summary>
    /// Offers the ability to store images every two seconds into the isolated
    /// storage that can then be retrieved using the isolated storage tool in
    /// the 7.1 SDK.
    /// </summary>
    public class ScreenShots
    {
        private ScreenShots()
        {
            _isf = IsolatedStorageFile.GetUserStoreForApplication();

            try
            {
                _isf.CreateDirectory("screenshots");
            }
            catch
            {
                // OK the directory already exists.
            }
        }

        private DispatcherTimer _dt;
        private double _interval;
        private IsolatedStorageFile _isf;
        private static ScreenShots _instance;

        public static void BeginTakingPictures(double interval = 2.0)
        {
            if (_instance == null)
            {
                _instance = new ScreenShots();
                _instance.Start(interval);
            }
            else if (_instance._dt != null)
            {
                _instance._dt.Start();
            }
        }

        public static void Stop()
        {
            if (_instance != null && _instance._dt != null)
            {
                _instance._dt.Stop();
            }
        }

        private void Start(double interval)
        {
            _interval = interval;

            if (_dt == null)
            {
                _dt = new DispatcherTimer();
                _dt.Interval = TimeSpan.FromSeconds(_interval);
                _dt.Tick += OnTick;
                _dt.Start();
            }
        }

        private void OnTick(object sender, EventArgs e)
        {
            var ui = Application.Current.RootVisual;
            try
            {
                if (ui != null)
                {
                    FrameworkElement fe = ui as FrameworkElement;
                    if (fe != null)
                    {
                        var width = fe.ActualWidth;
                        var height = fe.ActualHeight;

                        WriteableBitmap wb = new WriteableBitmap(ui,
                            new TranslateTransform());
                        wb.Render(ui, new TranslateTransform());
                        byte[] bb = EncodeToJpeg(wb);

                        string filename = "screenshots\\"
                            + DateTime.Now.Ticks
                            .ToString(CultureInfo.InvariantCulture)
                            + ".jpg";
                        using (var st = _isf.CreateFile(filename))
                        {
                            st.Write(bb, 0, bb.Length);
                        }

                        Debug.WriteLine("Saved screenshot to " + filename);
                    }
                }
            }
            catch (Exception)
            {
            }
        }

        public byte[] EncodeToJpeg(WriteableBitmap wb)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                wb.SaveJpeg(
                    stream,
                    wb.PixelWidth,
                    wb.PixelHeight,
                    0,
                    85);
                return stream.ToArray();
            }
        }
    }
}

Hope this helps.

  • Anonymous

    Will the result inlclude the ApplicationBar in the image if it is present?

  • http://blogs.msdn.com/b/delay/ Delay

    If you want 100% accurate screenshots (i.e., ones without JPEG compression artifacts), you might consider encoding to PNG instead.

    My PngEncoder class should be suitable for that: http://blogs.msdn.com/b/delay/archive/2011/02/07/what-it-lacks-in-efficiency-it-makes-up-for-in-efficiency-silverlight-ready-png-encoder-implementation-shows-one-way-to-use-net-ienumerables-effectively.aspx

  • http://twitter.com/RogueCode Matt

    I blogged about a similar thing here: http://roguecode.co.za/blog/ViewPost.aspx?Post=13
    Minus the remote isolated storage access.

  • Lee

    This worked great!

    However, I have a web browser component to display some HTML and this shows a blank white rectangle in the screenshots.

    Is there any way to get the screenshot class to capture this?

  • Anonymous

    Unfortunately the web browser control has a security restriction where the bitmap capture will not work.

  • http://hfrmobile.blogspot.com/ hfrmobile

    Thanks a lot for sharing. Will give it a try asap!

    Greetings from Austria,
    Harald-René Flasch (aka hfrmobile)

  • http://twitter.com/andyhammar Andreas Hammar

    Thanks for sharing!

    Why do you Render(..) after creating the WriteableBitmap?
    You’re sending the “ui” object to the bitmap twice – I thought once was enough.

    WriteableBitmap wb = new WriteableBitmap(ui, new TranslateTransform());
    wb.Render(ui, new TranslateTransform());

    Thank you

  • Anonymous

    I might be duplicating, sorry!
    ——————————

  • http://twitter.com/andyhammar Andreas Hammar

    No problemo – just trying to understand over here :)

    I’ve been having problems rendering images with overlays for a live tile, that’s why I’m looking into this.

  • http://hfrmobile.blogspot.com/ hfrmobile

    Tried to post on your blog but clicking the “Post!” button nothing happens.

    Original post:
    Tried the new screen-shot feature but got: The method or operation is not implemented

  • http://fujifilmax25014mpdigital-camera.blogspot.com/ John @Syncing Ipod Shuffle

    I have tried your advice and it works. It is like a magic.

  • http://twitter.com/ruiespinho Rui Marinho

    Awesome utility as always jeff :) saves me a ton of time, encoding in png instead of jpg will help because the app submition process only accepts png.