Real-World Tombstoning in Silverlight for Windows Phone, Part 4

Tombstoning is one of the greatest challenges in writing applications for Windows phones, which is why I decided to devote a series of blog posts to it. In Part 1 of this series, we built a photo-extras application that allows the user to perform simple image-editing chores on photos. In Part 2, we added tombstoning support so the application wouldn’t lose its state when tombstoned. And in Part 3, we fixed a bug in our tombstoning logic and refined it so that it wouldn’t untombstone anything if the app hadn’t been tombstoned in the first place. In this, the fourth and final article in the series, we’ll finally produce a finished application. But not before we squash one last bug – one that arises from an issue that’s unique to photo-extras applications.

First things first…we need to see the bug in action. To do that, you’ll need to deploy the application to your phone, because you can’t do what I’m about to do in the emulator. (Technically, that’s not quite true. The fundamental problem is that there is no Pictures library in the emulator, at least not one that you can see. There are undocumented ways to hack the emulator and unlock features that aren’t there by default, but I’m not going to get into that.)

Download the app to your phone. It’ll start up when you do. At this point, if you’re using the Zune client to connect your phone to your PC, close the Zune client. If you’re using WPConnect instead, you’re fine. WPConnect doesn’t prevent a phone tethered to your PC from accessing the media library, while the Zune client does. For more information, refer to Silverlight for Windows Phone Programming Tip #4.

Now, do the following:

  1. Press the Start button to go to the phone’s main screen.
  2. Tap the Pictures tile to open the pictures library.
  3. Select a picture. It doesn’t matter which one.
  4. Tap the ellipsis in the lower-right corner of the screen.
  5. In the ensuing menu, select “extras.”
  6. Select RealWorldTombstoningDemo to launch the sample app with the picture you just selected.

So far, so good. Now click the Open button in the application bar and select a different photo. Once again, it doesn’t matter which one as long as it isn’t the same one you selected a moment ago. You should now see the second of the two photos you selected. What do you see instead?

If you step through the OnNavigatedTo method in the debugger when the app is reactivated following the photo selection, you’ll see what’s happening. When RealWorldTombstoningDemo was launched, it was launched with a query string identifying a picture because it was launched from the “extras” menu.

When you clicked the Open button to select another photo, the app was deactivated. When you selected the photo, the app was reactivated. And here’s the key: because the app was originally launched with a query-string token, it’s reactivated with the query-string token, too. Rather than go through the code path that checks for a tombstoned bitmap, OnNavigatedTo followed the code path that loads a photo using a token. It’s as if the app was started from the “extras” menu a second time. But it wasn’t, so we need to ignore the token the second time and use the newly selected photo instead.

Now you see what I meant at the end of Part 3 when I said there’s still a major bug needing our attention. The good news is that it’s not difficult to devise a fix once you understand what’s happening. In a nutshell, we need to rewire OnNavigatedTo to do the following:

  1. If a photo is already present when OnNavigatedTo is called (which will be the case if a photo was loaded in OnPhotoSelected or the app was deactivated but not tombstoned), do nothing.
  2. If there’s no photo present (in other words, if _bitmap == null), check to see if there’s a photo awaiting untombstoning. If there is, untombstone it.
  3. If there’s no photo present and nothing to untombstone, check to see if there’s a query-string containing a picture token. If there is, load the photo from the token.

Here’s the finished OnNavigatedTo method. It’s not very different than before; it has just been restructured a bit. Basically we now give an existing bitmap precedence over one loaded from the “extras” menu. And we retain the key if _bitmap == null test that prevents us from untombstoning a bitmap if a bitmap is already present.

 

protected override void OnNavigatedTo(NavigationEventArgs e)

{

    Debug.WriteLine("OnNavigatedTo");

 

    if (_bitmap == null) // Skip this if a bitmap is already loaded

    {

        // Restore the state of the Save button

        if (State.ContainsKey(_save))

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled =

                (bool)State[_save];

 

        // Restore the WriteableBitmap

        if (State.ContainsKey(_width) && State.ContainsKey(_height))

        {

            int width = (int)State[_width];

            int height = (int)State[_height];

 

            // Create a new WriteableBitmap

            _bitmap = new WriteableBitmap(width, height);

 

            // Get the bitmap bits from isolated storage

            using (IsolatedStorageFile store =

                IsolatedStorageFile.GetUserStoreForApplication())

            {

                if (store.FileExists(_file))

                {

                    using (IsolatedStorageFileStream stream =

                        new IsolatedStorageFileStream(_file, FileMode.Open, store))

                    {

                        using (BinaryReader reader = new BinaryReader(stream))

                        {

                            int count = _bitmap.Pixels.Length * sizeof(int);

                            byte[] pixels = new byte[count];

                            reader.Read(pixels, 0, count);

                            Buffer.BlockCopy(pixels, 0, _bitmap.Pixels, 0, count);

                        }

                    }

 

                    // Clean up by deleting the file

                    store.DeleteFile(_file);

                }

            }

 

            // Show the bitmap to the user

            Photo.Source = _bitmap;

        }

        else if (NavigationContext.QueryString.ContainsKey("token"))

        {

            // If the app was invoked through the Extras menu, grab the photo and display it

            MediaLibrary library = new MediaLibrary();

            Picture picture =

                library.GetPictureFromToken(NavigationContext.QueryString["token"]);

 

            BitmapImage bitmap = new BitmapImage();

            bitmap.SetSource(picture.GetImage());

            _bitmap = new WriteableBitmap(bitmap);

            Photo.Source = _bitmap;

 

            // Disable the application bar’s Save button

            (ApplicationBar.Buttons[1] as ApplicationBarIconButton).IsEnabled = false;

        }

    }

}

 

Now, believe it or not, we have a finished app. You can download it and see for yourself. Try to trick it: load photos, click buttons, play taps on the Back button, launch from the “extras” menu, and anything else you can think of. Unless I’ve missed something, this app is ready to ship.

I did slip one additional feature into the final version. Before, if you edited the photo (clicked it to convert colors to grays) and clicked the Back button, you exited the app with unsaved data. Because the app is closed (not tombstoned) when you exit with the Back button, the changes are lost forever. That’s not a big deal for a demonstration app, but it could be a very big deal in one with richer editing features. So I added an OnBackKeyPress method that prompts the user for confirmation before exiting with unsaved changes:

screen

Check the source code to see how it works.

We deliver solutions that accelerate the value of Azure.

Ready to experience the full power of Microsoft Azure?

Start Today

Blog Home

Stay Connected

Upcoming Events

All Events