The problem of the Windows Phone 7 ApplicationBar and not (yet) updated Bindings

As a MVVM enthusiast I’m trying to use MVVM as much as possible, but not for things that are almost impossible to do with MVVM. But simple things, like a TextBox Text property is always bound to a ViewModel’s property. That’s something that works very well…until you start having ApplicationBar buttons and menu items.

A lot of people already know that the ApplicationBar is something special, it’s not just a control like other stuff in your application. When clicking a button on the ApplicationBar the Binding is not yet updated. This is basically because the focus which is on the TextBox for example won’t be changed when clicking ApplicationBar buttons. This is different compared to normal buttons that you put somewhere on your page.

The Solution?

I’ve read different solutions on the web. For example, force focus on a different control by calling the Focus method on a Control. While this is working, I found a technically nicer solution. By getting the Binding of the current focused control and updating that binding.

I wrote a small helper class that implements this Binding updating.

public static class ApplicationBarHelper
{
    public static void UpdateBindingOnFocussedControl()
    {
        object focusedElement = FocusManager.GetFocusedElement();
        if (focusedElement != null && focusedElement is TextBox)
        {
            var binding = (focusedElement as TextBox).GetBindingExpression(TextBox.TextProperty);
            if (binding != null)
                binding.UpdateSource();
        }
    } 
}

The first thing you do in the Event handlers for the ApplicationBar Click events is call.

ApplicationBarHelper.UpdateBindingOnFocussedControl();

What about the BindableApplicationBar?

A lot of use MVVM enthusiasts are using the BindableApplicationBar that’s part of the Phone7.Fx library. Can we use this in combination with the BindableApplicationBar? Yes we can, actually the best way would be to have this integrated with the BindableApplicationBar itself. I have just downloaded the source and manipulated two methods in two classes.

public class BindableApplicationBarIconButton : FrameworkElement, IApplicationBarIconButton
{
    // Other code that hasn't been changed in this class.

    void ApplicationBarIconButtonClick(object sender, EventArgs e)
    {
        ApplicationBarHelper.UpdateBindingOnFocussedControl();
        if (Command != null && CommandParameter != null)
            Command.Execute(CommandParameter);
        else if (Command != null)
            Command.Execute(CommandParameterValue);
        if (Click != null)
            Click(this, e);
    }
}

public class BindableApplicationBarMenuItem : FrameworkElement, IApplicationBarMenuItem
{
    // Other code that hasn't been changed in this class.

    private void ApplicationBarMenuItemClick(object sender, EventArgs e)
    {
        ApplicationBarHelper.UpdateBindingOnFocussedControl();
        if (Command != null && CommandParameter != null)
            Command.Execute(CommandParameter);
        else if (Command != null)
            Command.Execute(CommandParameterValue);
        if (Click != null)
            Click(this, e);
    }
}

Adding Tactile Feedback to your app the easy way

About a week ago there was a very good article with tips to make your wp7 app a killer app. Tip 24 was about tactile feedback. Tactile feedback can be something like 30 milliseconds running the VibrateController. That’s very easy, just like this:

VibrateController vibrateController = VibrateController.Default;
vibrateController.Start(new TimeSpan(0, 0, 0, 0, 30));

Because I just want to call one method instead of two lines of code, I created a small static method.

public static void GiveTactileFeedback()
{
    VibrateController vibrateController = VibrateController.Default;
    vibrateController.Start(new TimeSpan(0, 0, 0, 0, 30));
}

Alright, but now I had to add this method call to every Button Click, ListBox SelectionChanged, and ApplicationBar menu or button use. Probably there are some more places where I want to give a little bit of feedback. So I’m thinking about a solution, a generic solution. I remember how easy it was to apply the TiltEffect. So my mind was going the direction of a similar solution. So I started with some Attached Dependency Properties, the same mechanism that’s used to implement the TiltEffect. So enable it for example in the root of your Phone Page and only suppress the effect if you don’t want to apply it a special occasion.

<phone:PhoneApplicationPage ... 
                            WPUI:TactileFeedbackEffect.IsTactileFeedbackEnabled="true">

And suppress it in this way.

<Button Content="Feedback surpressed"
        WPUI:TactileFeedbackEffect.SuppressTactileFeedback="true" />

Basically I want to walk through all the children of the element that have the IsTactileFeedbackEnabled property set to true. So I started walking recursively through the Visual Tree.

public static IEnumerable<FrameworkElement> GetSelfAndChildren(this FrameworkElement node)
{
    var elements = new List<FrameworkElement>();
    int childrenCount = VisualTreeHelper.GetChildrenCount(node);
    elements.Add(node);
    for (int i = 0; i < childrenCount; i++)
    {
        var childFe = VisualTreeHelper.GetChild(node, i) as FrameworkElement;
        if (childFe != null)
        {
            elements.AddRange(GetSelfAndChildren(childFe));
        }
    }
    return elements;
}

Using this Visual Tree of elements to check against controls that should support the Tactile Feedback: ButtonBase (root of all buttons), Selector (root of the different types of ListBoxes), PhoneApplicationPage (only way to get a hold of the  ApplicationBar’s buttons and menu items). For the menu items and buttons, it’s just simply attaching to the Click event. The Selector has a SelectionChanged event to which we can listen. Not that much of rocket science.

The initial idea I had was to make use of the Loaded event of the Element that has  the DependencyProperty applied to start walking though the visual tree. But what happened when the Visual Tree changes? Caused for example by Data Binding changes, or any of the other ways that enable you to manipulate the Visual Tree. So I attached to the LayoutUpdated event as well, I had to hack a bit, because the sender in the LayoutUpdated event is always null.

In het end I came to the conclusion that I didn’t have a way to suppress feedback on the ApplicationBar items.This is mainly because the ApplicationBar items are not inheriting from DependencyObject.

Full code is below for both the VisualTreeHelper and the TactileFeedbackEffect. Please let me know if you have any questions or improvements.

/// <summary>
/// Simple helpers for walking the visual tree
/// </summary>
internal static class TreeHelpers
{
    public static IEnumerable<FrameworkElement> GetSelfAndChildren(this FrameworkElement node)
    {
        var elements = new List<FrameworkElement>();
        int childrenCount = VisualTreeHelper.GetChildrenCount(node);
        elements.Add(node);
        for (int i = 0; i < childrenCount; i++)
        {
            var childFe = VisualTreeHelper.GetChild(node, i) as FrameworkElement;
            if (childFe != null)
            {
                elements.AddRange(GetSelfAndChildren(childFe));
            }
        }
        return elements;
    }
}

public class TactileFeedbackEffect : DependencyObject
{
    /// <summary>
    /// Set this Attached Dependency Property to true to enable tactile feedback on an element and it's children.
    /// </summary>
    public static readonly DependencyProperty IsTactileFeedbackEnabledProperty = DependencyProperty.RegisterAttached
        (
            "IsTactileFeedbackEnabled",
            typeof (bool),
            typeof (TactileFeedbackEffect),
            new PropertyMetadata(OnIsTactileFeedbackEnabledChanged)
        );

    /// <summary>
    /// Set this Attached Dependency Property to true to suppress tactile feedback on the element.
    /// </summary>
    public static readonly DependencyProperty SuppressTactileFeedbackProperty = DependencyProperty.RegisterAttached(
        "SuppressTactileFeedback",
        typeof (bool),
        typeof (TactileFeedbackEffect),
        new PropertyMetadata(false));

    static TactileFeedbackEffect()
    {
        TactileFeedbackItems = new List<Type>
                                    {typeof (ButtonBase), typeof (PhoneApplicationPage), typeof (Selector)};
    }

    private TactileFeedbackEffect()
    {
    }

    public static List<Type> TactileFeedbackItems { get; private set; }

    public static bool GetIsTactileFeedbackEnabled(DependencyObject source)
    {
        return (bool) source.GetValue(IsTactileFeedbackEnabledProperty);
    }


    public static void SetIsTactileFeedbackEnabled(DependencyObject source, bool value)
    {
        source.SetValue(IsTactileFeedbackEnabledProperty, value);
    }

    public static bool GetSuppressTactileFeedback(DependencyObject source)
    {
        return (bool) source.GetValue(SuppressTactileFeedbackProperty);
    }


    public static void SetSuppressTactileFeedback(DependencyObject source, bool value)
    {
        source.SetValue(SuppressTactileFeedbackProperty, value);
    }

    private static void OnIsTactileFeedbackEnabledChanged(DependencyObject target,
                                                            DependencyPropertyChangedEventArgs args)
    {
        if (target is FrameworkElement)
        {
            if ((bool) args.NewValue)
            {
                (target as FrameworkElement).Loaded += (s, e) =>
                                                            {
                                                                var senderDo = s as DependencyObject;
                                                                TryAttachFeedback(senderDo);
                                                                if (senderDo is FrameworkElement)
                                                                    (senderDo as FrameworkElement).LayoutUpdated +=
                                                                        (s2, e2) =>
                                                                            {
                                                                                TryDetachFeedback(senderDo);
                                                                                TryAttachFeedback(senderDo);
                                                                            };
                                                            };
            }
            else
            {
                TryDetachFeedback(target);
            }
        }
    }

    private static void TryAttachFeedback(DependencyObject target)
    {
        foreach (FrameworkElement element in (target as FrameworkElement).GetSelfAndChildren())
        {
            foreach (Type t in TactileFeedbackItems)
            {
                if (t.IsAssignableFrom(element.GetType()))
                {
                    if ((bool) element.GetValue(SuppressTactileFeedbackProperty) != true)
                    {
                        if (element is ButtonBase)
                        {
                            (element as ButtonBase).Click += TactileFeedbackEffectClick;
                        }
                        if (element is PhoneApplicationPage)
                        {
                            var page = element as PhoneApplicationPage;
                            if (page.ApplicationBar != null)
                            {
                                foreach (IApplicationBarMenuItem button in
                                    page.ApplicationBar.Buttons ?? new List<IApplicationBarMenuItem>())
                                {
                                    button.Click += TactileFeedbackEffectApplicationBarClick;
                                }
                                foreach (IApplicationBarMenuItem menuItem in
                                    page.ApplicationBar.MenuItems ?? new List<IApplicationBarMenuItem>())
                                {
                                    menuItem.Click += TactileFeedbackEffectApplicationBarClick;
                                }
                            }
                        }
                        if (element is Selector)
                        {
                            (element as Selector).SelectionChanged += TactileFeedbackEffectSelectionChanged;
                        }
                    }
                }
            }
        }
    }

    private static void TryDetachFeedback(DependencyObject target)
    {
        foreach (FrameworkElement element in (target as FrameworkElement).GetSelfAndChildren())
        {
            foreach (Type t in TactileFeedbackItems)
            {
                if (t.IsAssignableFrom(element.GetType()))
                {
                    if ((bool) element.GetValue(SuppressTactileFeedbackProperty) != true)
                    {
                        if (element is ButtonBase)
                        {
                            (element as ButtonBase).Click -= TactileFeedbackEffectClick;
                        }
                        if (element is PhoneApplicationPage)
                        {
                            var page = element as PhoneApplicationPage;
                            if (page.ApplicationBar != null)
                            {
                                foreach (IApplicationBarMenuItem button in
                                    page.ApplicationBar.Buttons ?? new List<IApplicationBarMenuItem>())
                                {
                                    button.Click -= TactileFeedbackEffectApplicationBarClick;
                                }
                                foreach (IApplicationBarMenuItem menuItem in
                                    page.ApplicationBar.MenuItems ?? new List<IApplicationBarMenuItem>())
                                {
                                    menuItem.Click -= TactileFeedbackEffectApplicationBarClick;
                                }
                            }
                        }
                        if (element is Selector)
                        {
                            (element as Selector).SelectionChanged -= TactileFeedbackEffectSelectionChanged;
                        }
                    }
                }
            }
        }
    }

    private static void TactileFeedbackEffectSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        GiveTactileFeedback();
    }

    private static void TactileFeedbackEffectApplicationBarClick(object sender, EventArgs e)
    {
        GiveTactileFeedback();
    }

    private static void TactileFeedbackEffectClick(object sender, RoutedEventArgs e)
    {
        GiveTactileFeedback();
    }

    public static void GiveTactileFeedback()
    {
        VibrateController vibrateController = VibrateController.Default;
        vibrateController.Start(new TimeSpan(0, 0, 0, 0, 30));
    }
}

Cache as Fallback using Reactive Extensions

In my previous article I wrote about how to use the Isolated Storage using Reactive Extensions. Let’s combine that with the ability to access online resources using Reactive Extensions.

What I want to achieve can be explained best by a picture. Basically if downloading of a resource fails it should fall back to the cache.

image

Let’s combine some code from the previous article.

First of all we want to download a resource from the internet, and if that’s successful we want to cache that data.

public static IObservable<MemoryStream> CacheAsFallback(Uri onlineResource, string cacheKey)
{
    IObservable<MemoryStream> observableOnlineResource = FromInternet(onlineResource);
    return observableOnlineResource
        .Select(stream =>
                    {
                        CacheResource(stream, cacheKey);
                        stream.Position = 0;
                        return stream;
                    });
}

Yes that’s all, it’s easy to combine Reactive Extensions (Rx) with Linq. So the above code will make sure that the resource that’s downloaded is cached before it’s returned to the caller. Please take a notion on the resetting of the stream position.

Catch an exception in Reactive Extensions

The next step is a little bit less seen in the Rx examples found on the web. Catch an exception and continue with a different Observable.

public static IObservable<TSource> Catch<TSource, TException>(
    this IObservable<TSource> source,
    Func<TException, IObservable<TSource>> handler
    )
    where TException : Exception

So Catch is an Extension method that accepts a Func<Exception, IObservable<T>>. In our case that would be an IObservable<MemoryStream>. If you want to catch only specific Exception types, you could do that as well, but I want to fall back in case of any error. Further more the FromCache Observable returns null when nothing is found. I want that to be translated to an Exception.

public static IObservable<MemoryStream> CacheAsFallback(Uri onlineResource, string cacheKey)
{
    IObservable<MemoryStream> observableOnlineResource = FromInternet(onlineResource);
    return observableOnlineResource
        .Select(stream =>
                    {
                        CacheResource(stream, cacheKey);
                        stream.Position = 0;
                        return stream;
                    })
        .Catch<MemoryStream, Exception>(
            exception => FromCache(cacheKey)
                                .Select(cacheStream =>
                                            {
                                                if (cacheStream == null)
                                                    throw new Exception("No cache");
                                                return cacheStream;
                                            }));
}

So now I can write below code to get a resource, and in case of network connectivity problems it will fallback to a cache value.

const string url = "http://mark.mymonster.nl/Content/photo.jpg";
IObservable<MemoryStream> observable = ObservableResource.CacheAsFallback(new Uri(url), "photo.jpg");
observable
    .ObserveOnDispatcher()
    .Subscribe(
        stream =>
            {
                if (stream != null)
                {
                    var image = new BitmapImage();
                    image.SetSource(stream);
                    TargetImage.Source = image;
                }
            },
        exc => Debug.WriteLine(string.Format("Oops: {0}", exc.Message)),
        () => Debug.WriteLine("Completed."));

Now let’s combine everything we have again, toggle below to view it.

public static class StreamExtensions
{
    public static MemoryStream ToMemoryStream(this Stream stream)
    {
        var br = new BinaryReader(stream);
        var outputStream = new MemoryStream();
        var buffer = new byte[1024];
        int cb;
        while ((cb = br.Read(buffer, 0, buffer.Length)) > 0)
        {
            outputStream.Write(buffer, 0, cb);
        }
        outputStream.Position = 0;
        return outputStream;
    }
}

public static class ObservableResource
{
    private const string CacheFolder = "cache";

    public static IObservable<MemoryStream> CacheAsFallback(Uri onlineResource, string cacheKey)
    {
        IObservable<MemoryStream> observableOnlineResource = FromInternet(onlineResource);
        return observableOnlineResource
            .Select(stream =>
                        {
                            CacheResource(stream, cacheKey);
                            stream.Position = 0;
                            return stream;
                        })
            .Catch<MemoryStream, Exception>(
                exception => FromCache(cacheKey)
                                    .Select(cacheStream =>
                                                {
                                                    if (cacheStream == null)
                                                        throw new Exception("No cache");
                                                    return cacheStream;
                                                }));
    }

    public static void CacheResource(MemoryStream toCache, string cacheKey)
    {
        string targetFile = Path.Combine(CacheFolder, cacheKey);
        using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (!isoFile.DirectoryExists(CacheFolder))
                isoFile.CreateDirectory(CacheFolder);
            if (isoFile.FileExists(targetFile))
                isoFile.DeleteFile(targetFile);
            using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(targetFile))
            {
                toCache.WriteTo(outputStream);
            }
        }
    }

    public static IObservable<MemoryStream> FromCache(string cacheKey)
    {
        return new AnonymousObservable<MemoryStream>(
            observer =>
                {
                    string targetFile = Path.Combine(CacheFolder, cacheKey);

                    using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        if (!isoFile.FileExists(targetFile))
                        {
                            observer.OnNext(null);
                        }
                        else
                        {
                            using (IsolatedStorageFileStream inputStream =
                                isoFile.OpenFile(targetFile, FileMode.Open, FileAccess.Read))
                            {
                                observer.OnNext(inputStream.ToMemoryStream());
                            }
                        }
                    }
                    observer.OnCompleted();
                    return Disposable.Empty;
                });
    }

    public static IObservable<MemoryStream> FromInternet(Uri onlineResource)
    {
        return
            new AnonymousObservable<WebResponse>(
                observer =>
                    {
                        var httpWebRequest =
                            (HttpWebRequest) WebRequest.Create(onlineResource);
                        httpWebRequest.BeginGetResponse(
                            iar =>
                                {
                                    WebResponse response;
                                    try
                                    {
                                        var requestState = (HttpWebRequest) iar.AsyncState;
                                        response = requestState.EndGetResponse(iar);
                                    }
                                    catch (Exception exception)
                                    {
                                        observer.OnError(exception);
                                        return;
                                    }
                                    observer.OnNext(response);
                                    observer.OnCompleted();
                                }, httpWebRequest);
                        return Disposable.Empty;
                    })
                .Select(
                    response => response.GetResponseStream().ToMemoryStream());
    }
}

Isolated Storage as Cache using Reactive Extensions

A lot of people are used to write some data to the Isolated Storage to use it as a Cache. I’m doing that as well, it’s easy to write and read to the Isolated Storage. Some people just write text/xml data and want to put the content in a string. I have however a lot of situations where caching of images is important, so my approach doesn’t make use of a string but a MemoryStream instead. So I have a static method that writes a MemoryStream to a file in Isolated Storage.

private const string CacheFolder = "cache";
public static void CacheResource(MemoryStream toCache, string cacheKey)
{
    string targetFile = Path.Combine(CacheFolder, cacheKey);
    using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
    {
        if (!isoFile.DirectoryExists(CacheFolder))
            isoFile.CreateDirectory(CacheFolder);
        if (isoFile.FileExists(targetFile))
            isoFile.DeleteFile(targetFile);
        using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(targetFile))
        {
            toCache.WriteTo(outputStream);
        }
    }
}

That’s the easy part writing. I must admit reading isn’t much more complicated, unless you want to read it in a special way, namely using Reactive Extensions (Rx). Reactive Extensions offer a different way to look at asynchronous code, instead of listening to events, it allows to subscribe to things that happen. In addition to this it supports Linq on observable objects. I’m not going to dig deep on Rx, there are other blogs that can do that much better. The mechanism described in this article is intended for use in Windows Phone, but should work in Silverlight as well.

Reactive Extensions (Rx) over WebRequest

So why did I want to do use Rx? It’s simple: I want to have a similar signature and way of usage of code when I access content online (Async required) and when I access content from the cache (Standard only sync access). So how do I access online content?

In this example, I’m downloading online content and setting the MemoryStream as the Source of a BitmapImage and put the BitmapImage as the source of an Image control. It’s a regular Rx way of reacting on an Observable, where I put a lambda to react on OnNext, OnError and OnCompleted. Further more because I’m interacting with the User Interface it’s important to run that code on the Dispatcher. Running code on the dispatcher can be done manually or you can use Rx combined with ObserveOnDispatcher method.

const string url = "http://mark.mymonster.nl/Content/photo.jpg";
IObservable<MemoryStream> observable = ObservableResource.FromInternet(new Uri(url));
observable
    .ObserveOnDispatcher()
    .Subscribe(
        stream =>
        {
            if (stream != null)
            {
                var image = new BitmapImage();
                image.SetSource(stream);
                TargetImage.Source = image;
            }
        },
        exc => Debug.WriteLine(string.Format("Oops: {0}", exc.Message)),
        () => Debug.WriteLine("Completed."));

So how does my ResourceHelper.OnlineResource method look like? I’m using an AnonymousObservable to construct an observable from the Async pattern in a WebRequest. Please don’t tell me I could use Observable.FromAsyncPattern, I’ve used it in the past, but have stopped using it, I’m now writing my own Observable code to be sure I’m in control.

public class AnonymousObservable<T> : IObservable<T>
{
    private readonly Func<IObserver<T>, IDisposable> _subscribeAction;

    public AnonymousObservable(Func<IObserver<T>, IDisposable> subscribeAction)
    {
        _subscribeAction = subscribeAction;
    }

    #region IObservable<T> Members

    public IDisposable Subscribe(IObserver<T> observer)
    {
        return _subscribeAction(observer);
    }

    #endregion
}

This anonymous Observable class is a simple implementation that get’s a lambda passed in which will be executed on Subscribe. So finally we have the FromInternet implementation combined with a operation that reads a stream to a MemoryStream.

public static IObservable<MemoryStream> FromInternet(Uri onlineResource)
{
    return
        new AnonymousObservable<WebResponse>(
            observer =>
                {
                    var httpWebRequest =
                        (HttpWebRequest) WebRequest.Create(onlineResource);
                    httpWebRequest.BeginGetResponse(
                        iar =>
                            {
                                WebResponse response;
                                try
                                {
                                    var requestState = (HttpWebRequest) iar.AsyncState;
                                    response = requestState.EndGetResponse(iar);
                                }
                                catch (Exception exception)
                                {
                                    observer.OnError(exception);
                                    return;
                                }
                                observer.OnNext(response);
                                observer.OnCompleted();
                            }, httpWebRequest);
                    return Disposable.Empty;
                })
            .Select(
                response => response.GetResponseStream().ToMemoryStream());
}

What you can see in the Lambda that get’s passed into the AnonymousObservable for Online Resources, it’s similar to what we would have done in the past, but now it’s calling observer.OnNext, observer.OnError and observer.OnCompleted operations. Further more you can see that I attached a Select operation we are used to in Linq to convert a WebResponse to a MemoryStream using the ToMemoryStream extension method.

public static class StreamExtensions
{
    public static MemoryStream ToMemoryStream(this Stream stream)
    {
        var br = new BinaryReader(stream);
        var outputStream = new MemoryStream();
        var buffer = new byte[1024];
        int cb;
        while ((cb = br.Read(buffer, 0, buffer.Length)) > 0)
        {
            outputStream.Write(buffer, 0, cb);
        }
        outputStream.Position = 0;
        return outputStream;
    }
}

Reactive Extensions (Rx) over Isolated Storage

So far it has all been preparation for the Reactive Extensions over Isolated Storage. Where the implementation is actually really simple, it’s now wrapped in an Anonymous Observable.

public static IObservable<MemoryStream> FromCache(string cacheKey)
{
    return new AnonymousObservable<MemoryStream>(
        observer =>
            {
                string targetFile = Path.Combine(CacheFolder, cacheKey);

                using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
                {
                    if (!isoFile.FileExists(targetFile))
                    {
                        observer.OnNext(null);
                    }
                    else
                    {
                        using (IsolatedStorageFileStream inputStream =
                            isoFile.OpenFile(targetFile, FileMode.Open, FileAccess.Read))
                        {
                            observer.OnNext(inputStream.ToMemoryStream());
                        }
                    }
                }
                observer.OnCompleted();
                return Disposable.Empty;
            });
}

This now enables me to get something from cache in the same way as I can get something from the internet. Please mark the “photo.jpg” is a key for accessing the cache, which in the end results in data. If you choose a different caching-strategy you should remind this is a cache key.

IObservable<MemoryStream> observable = ObservableResource.FromCache("photo.jpg");
observable
    .ObserveOnDispatcher()
    .Subscribe(
        stream =>
        {
            if (stream != null)
            {
                var image = new BitmapImage();
                image.SetSource(stream);
                TargetImage.Source = image;
            }
        },
        exc => Debug.WriteLine(string.Format("Oops: {0}", exc.Message)),
        () => Debug.WriteLine("Completed."));

You can find the full source code here, by toggling.

public static class StreamExtensions
{
    public static MemoryStream ToMemoryStream(this Stream stream)
    {
        var br = new BinaryReader(stream);
        var outputStream = new MemoryStream();
        var buffer = new byte[1024];
        int cb;
        while ((cb = br.Read(buffer, 0, buffer.Length)) > 0)
        {
            outputStream.Write(buffer, 0, cb);
        }
        outputStream.Position = 0;
        return outputStream;
    }
}

public static class ObservableResource
{
    private const string CacheFolder = "cache";

    public static void CacheResource(MemoryStream toCache, string cacheKey)
    {
        string targetFile = Path.Combine(CacheFolder, cacheKey);
        using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
        {
            if (!isoFile.DirectoryExists(CacheFolder))
                isoFile.CreateDirectory(CacheFolder);
            if (isoFile.FileExists(targetFile))
                isoFile.DeleteFile(targetFile);
            using (IsolatedStorageFileStream outputStream = isoFile.CreateFile(targetFile))
            {
                toCache.WriteTo(outputStream);
            }
        }
    }

    public static IObservable<MemoryStream> FromCache(string cacheKey)
    {
        return new AnonymousObservable<MemoryStream>(
            observer =>
                {
                    string targetFile = Path.Combine(CacheFolder, cacheKey);

                    using (IsolatedStorageFile isoFile = IsolatedStorageFile.GetUserStoreForApplication())
                    {
                        if (!isoFile.FileExists(targetFile))
                        {
                            observer.OnNext(null);
                        }
                        else
                        {
                            using (IsolatedStorageFileStream inputStream =
                                isoFile.OpenFile(targetFile, FileMode.Open, FileAccess.Read))
                            {
                                observer.OnNext(inputStream.ToMemoryStream());
                            }
                        }
                    }
                    observer.OnCompleted();
                    return Disposable.Empty;
                });
    }

    public static IObservable<MemoryStream> FromInternet(Uri onlineResource)
    {
        return
            new AnonymousObservable<WebResponse>(
                observer =>
                    {
                        var httpWebRequest =
                            (HttpWebRequest) WebRequest.Create(onlineResource);
                        httpWebRequest.BeginGetResponse(
                            iar =>
                                {
                                    WebResponse response;
                                    try
                                    {
                                        var requestState = (HttpWebRequest) iar.AsyncState;
                                        response = requestState.EndGetResponse(iar);
                                    }
                                    catch (Exception exception)
                                    {
                                        observer.OnError(exception);
                                        return;
                                    }
                                    observer.OnNext(response);
                                    observer.OnCompleted();
                                }, httpWebRequest);
                        return Disposable.Empty;
                    })
                .Select(
                    response => response.GetResponseStream().ToMemoryStream());
    }
}

Solving 3 problems with the ShellTileSchedule

Are the problems with the Shell Tile Schedule? Yes there are, at least I’m in the impression that there are some problems. Although we have those problems I really like the ShellTileSchedule because it enables an app to have an updated tile without the requirement to write server-side code to notify the client for a new tile. The smallest schedule that’s supported is 1-hour. In that situation every hour the tile will be updated with a tile located on the web (static url, which can return a dynamic image of course).

There are three problems I identified so far.

1. You can’t get the status of the ShellTileSchedule. Worse, although you started the schedule it might be stopped because for whatever reason (ex. phone is on Airplane mode) the downloading of the tile failed.

2. You have to wait at least 1 hour before the tile is updated for the first time.

3. After you stopped the ShellTileSchedule, the tile will be the last downloaded tile forever. It would be better if automatically the original tile (from the .xap package) is put back.

Combined a diagram to show the problems.

image

Solution 1 for problem 1

Alright, we can’t get the status. So what do we do? We store it locally, in the Isolated Storage. I really like the approach that’s explained by Joost van Schaik who created some extension methods for storing the settings in the Isolated Storage. He’s storing and retrieving to the Phone State on the Tomb Stoning events: Activated and Deactivated. I did store and retrieve from Isolated on all events: Activated, Deactivated, Launching and Closing.

That’s all about making sure we have a status. But still this status won’t be updated when the schedule stopped. The Windows Phone team suggests to start the schedule on every application start. The code of the Application_Launching event in the App.xaml.cs could look like this.

private void Application_Launching(object sender, LaunchingEventArgs e)
{
    Settings = this.RetrieveFromIsolatedStorage<SettingsViewModel>() ?? new SettingsViewModel();
    if(Settings.TileUpdatesEnabled)
    {
        new ShellTileSchedule
        {
            Interval = UpdateInterval.EveryHour,
            MaxUpdateCount = 0,
            Recurrence = UpdateRecurrence.Interval,
            RemoteImageUri = new Uri(@"http://mark.mymonster.nl/Uploads/2010/12/servertile.png"),
            StartTime = DateTime.Now
        }.Start();
    }
}

Joost mentions that the ViewModelBase of MVVM Light isn’t serializable. So I created a basic ViewModelBase that has the functionality that I’m always using in ViewModelBase (RaisePropertyChanged) and decorated it with the DataContract attribute.

[DataContract]
public class ViewModelBase : INotifyPropertyChanged
{
    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    protected void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Solution 2 for problem 2

Not many people know it’s possible, but Matthijs Hoekstra from Microsoft helped me in the direction to solve this problem.

Solution: Send a Push Notification from the phone itself. And afterwards close the channel and start the ShellTileSchedule.

It’s important that you send the Push Notification before the actual call to the start the ShellTileSchedule because the ShellTileSchedule is a kind of NotificationChannel of which only one can exist.

A lot of articles have been written already about how to do Push Notifications. After opening a NotificationChannel you will get a url. After that you send some xml to that url. The xml that has to be send looks like this.

?<?xml version="1.0" encoding="utf-8"?>
<wp:Notification xmlns:wp="WPNotification">
  <wp:Tile>
    <wp:BackgroundImage>http://someserver/servertile.png</wp:BackgroundImage>
    <wp:Count>0</wp:Count>
  </wp:Tile>
</wp:Notification>

The setup of the xml in code:

public void SendTile(Uri notificationUrl, string tileUri, int? count, string title, Action onComplete)
{
    var stream = new MemoryStream();
    var settings = new XmlWriterSettings {Indent = true, Encoding = Encoding.UTF8};
    XmlWriter writer = XmlWriter.Create(stream, settings);
    writer.WriteStartDocument();
    writer.WriteStartElement("wp", "Notification", "WPNotification");
    writer.WriteStartElement("wp", "Tile", "WPNotification");
    if (!string.IsNullOrEmpty(tileUri))
    {
        writer.WriteStartElement("wp", "BackgroundImage", "WPNotification");
        writer.WriteValue(tileUri);
        writer.WriteEndElement();
    }
    if (count.HasValue)
    {
        writer.WriteStartElement("wp", "Count", "WPNotification");
        writer.WriteValue(count.ToString());
        writer.WriteEndElement();
    }
    if (!string.IsNullOrEmpty(title))
    {
        writer.WriteStartElement("wp", "Title", "WPNotification");
        writer.WriteValue(title);
        writer.WriteEndElement();
    }
    writer.WriteEndElement();
    writer.Close();
    byte[] payload = stream.ToArray();

	...
}

If you take a look at the Windows Phone 7 Training Kit you will see an example on how to do a Push Notification. It can be used almost one on one in the Windows Phone application itself. I removed some of the code (for the Raw and Toast Notifications) and refactored it to fit my needs.

So after the setup of the xml, the sending of the xml and setting of the HTTP headers looks like this:

public void SendTile(Uri notificationUrl, string tileUri, int? count, string title, Action onComplete)
{
	...
	
	byte[] payload = stream.ToArray();

    //Check the length of the payload and reject it if too long
    if (payload.Length > MaxPayloadLength)
        throw new ArgumentOutOfRangeException(
            string.Format("Payload is too long. Maximum payload size shouldn't exceed {0} bytes",
                            MaxPayloadLength));

    //Create and initialize the request object
    var request = (HttpWebRequest) WebRequest.Create(notificationUrl);
    request.Method = "POST";
    request.ContentType = "text/xml; charset=utf-8";
    //request.ContentLength = payload.Length;
    request.Headers["X-MessageID"] = Guid.NewGuid().ToString();
    request.Headers["X-NotificationClass"] = 1.ToString();
    request.Headers["X-WindowsPhone-Target"] = "token";

    request.BeginGetRequestStream(
        ar =>
            {
                //Once async call returns get the Stream object
                Stream requestStream = request.EndGetRequestStream(ar);

                //and start to write the payload to the stream asynchronously
                requestStream.BeginWrite(
                    payload, 0, payload.Length,
                    iar =>
                        {
                            //When the writing is done, close the stream
                            requestStream.EndWrite(iar);
                            requestStream.Close();

                            //and switch to receiving the response from MPNS
                            request.BeginGetResponse(
                                iarr =>
                                    {
                                        if (onComplete != null)
                                            onComplete();
                                    },
                                null);
                        },
                    null);
            },
        null);
}

Of course we shouldn’t forget the important part: Subscribing to the Notification channel. Take special notice to the Thread.Sleep in line 21. This is to make sure that the Tile update is completed before the unbinding, and more starting the ShellTileSchedule.

private void UpdateTileBeforeOperation(Uri imageUri, Action onComplete)
{
    HttpNotificationChannel channel = HttpNotificationChannel.Find("OneTime");
    if (channel != null)
        channel.Close();
    else
    {
        channel = new HttpNotificationChannel("OneTime");
        channel.ChannelUriUpdated +=
            (s, e) =>
                {
                    if (imageUri.IsAbsoluteUri)
                        channel.BindToShellTile(new Collection<Uri> {imageUri});
                    else
                        channel.BindToShellTile();

                    SendTile(e.ChannelUri, imageUri.ToString(), 0, "",
                                () =>
                                    {
                                        //Give it some time to let the update propagate
                                        Thread.Sleep(
                                            TimeSpan.FromSeconds(1));

                                        channel.UnbindToShellTile();
                                        channel.Close();
                                        //Do the operation
                                        if (onComplete != null)
                                            onComplete();
                                    }
                        );
                };
        channel.Open();
    }
}

Solution 3 for problem 3

The solution for problem 3 is similar to solution 2. But instead of a remote url the url is an relative url, local to the .xap file.

public void Stop(Action onComplete)
{
    UpdateTileBeforeOperation(new Uri("/Background.png", UriKind.Relative),
                                () => { if (onComplete != null) onComplete(); });
}

Again it allows you to include an action that will be called upon completion of the Stop method.

 

Full solution diagram

Alright, all problems solved. The diagram now looks like this.

image

Of course you want to have the full code for the SmartShellTileSchedule.

public class SmartShellTileSchedule
{
    private const int MaxPayloadLength = 1024;

    public UpdateRecurrence Recurrence { get; set; }

    public int MaxUpdateCount { get; set; }

    public DateTime StartTime { get; set; }

    public UpdateInterval Interval { get; set; }

    public Uri RemoteImageUri { get; set; }

    /// <summary>
    /// If the schedule is enabled (store this in application settings) this operation should be 
    /// called upon each application start.
    /// </summary>
    public void CheckForStart()
    {
        DelegateSchedule().Start();
    }

    /// <summary>
    /// This will enable the schedule and make sure the tile is updated immediately. Don't call 
    /// this operation on each application start.
    /// </summary>
    public void Start()
    {
        Start(null);
    }

    /// <summary>
    /// This will enable the schedule and make sure the tile is updated immediately. Don't call 
    /// this operation on each application start.
    /// </summary>
    /// <param name="onComplete">will be called upon completion</param>
    public void Start(Action onComplete)
    {
        UpdateTileBeforeOperation(RemoteImageUri, () =>
                                                        {
                                                            CheckForStart();
                                                            if (onComplete != null) onComplete();
                                                        });
    }

    /// <summary>
    /// This will stop the schedule and make sure the tile is replaced with the original logo-tile.
    /// Assumption is that the logo-tile is called "Background.png"
    /// </summary>
    public void Stop()
    {
        Stop(null);
    }

    /// <summary>
    /// This will stop the schedule and make sure the tile is replaced with the original logo-tile.
    /// Assumption is that the logo-tile is called "Background.png"
    /// </summary>
    /// <param name="onComplete">will be called upon completion</param>
    public void Stop(Action onComplete)
    {
        UpdateTileBeforeOperation(new Uri("/Background.png", UriKind.Relative),
                                    () => { if (onComplete != null) onComplete(); });
    }

    private void UpdateTileBeforeOperation(Uri imageUri, Action onComplete)
    {
        HttpNotificationChannel channel = HttpNotificationChannel.Find("OneTime");
        if (channel != null)
            channel.Close();
        else
        {
            channel = new HttpNotificationChannel("OneTime");
            channel.ChannelUriUpdated +=
                (s, e) =>
                    {
                        if (imageUri.IsAbsoluteUri)
                            channel.BindToShellTile(new Collection<Uri> {imageUri});
                        else
                            channel.BindToShellTile();

                        SendTile(e.ChannelUri, imageUri.ToString(), 0, "",
                                    () =>
                                        {
                                            //Give it some time to let the update propagate
                                            Thread.Sleep(
                                                TimeSpan.FromSeconds(1));

                                            channel.UnbindToShellTile();
                                            channel.Close();
                                            //Do the operation
                                            if (onComplete != null)
                                                onComplete();
                                        }
                            );
                    };
            channel.Open();
        }
    }

    private ShellTileSchedule DelegateSchedule()
    {
        return new ShellTileSchedule
                    {
                        Interval = Interval,
                        MaxUpdateCount = MaxUpdateCount,
                        Recurrence = Recurrence,
                        RemoteImageUri = RemoteImageUri,
                        StartTime = StartTime
                    };
    }

    public void SendTile(Uri notificationUrl, string tileUri, int? count, string title, Action onComplete)
    {
        var stream = new MemoryStream();
        var settings = new XmlWriterSettings {Indent = true, Encoding = Encoding.UTF8};
        XmlWriter writer = XmlWriter.Create(stream, settings);
        writer.WriteStartDocument();
        writer.WriteStartElement("wp", "Notification", "WPNotification");
        writer.WriteStartElement("wp", "Tile", "WPNotification");
        if (!string.IsNullOrEmpty(tileUri))
        {
            writer.WriteStartElement("wp", "BackgroundImage", "WPNotification");
            writer.WriteValue(tileUri);
            writer.WriteEndElement();
        }
        if (count.HasValue)
        {
            writer.WriteStartElement("wp", "Count", "WPNotification");
            writer.WriteValue(count.ToString());
            writer.WriteEndElement();
        }
        if (!string.IsNullOrEmpty(title))
        {
            writer.WriteStartElement("wp", "Title", "WPNotification");
            writer.WriteValue(title);
            writer.WriteEndElement();
        }
        writer.WriteEndElement();
        writer.Close();
        byte[] payload = stream.ToArray();

        //Check the length of the payload and reject it if too long
        if (payload.Length > MaxPayloadLength)
            throw new ArgumentOutOfRangeException(
                string.Format("Payload is too long. Maximum payload size shouldn't exceed {0} bytes",
                                MaxPayloadLength));

        //Create and initialize the request object
        var request = (HttpWebRequest) WebRequest.Create(notificationUrl);
        request.Method = "POST";
        request.ContentType = "text/xml; charset=utf-8";
        //request.ContentLength = payload.Length;
        request.Headers["X-MessageID"] = Guid.NewGuid().ToString();
        request.Headers["X-NotificationClass"] = 1.ToString();
        request.Headers["X-WindowsPhone-Target"] = "token";

        request.BeginGetRequestStream(
            ar =>
                {
                    //Once async call returns get the Stream object
                    Stream requestStream = request.EndGetRequestStream(ar);

                    //and start to write the payload to the stream asynchronously
                    requestStream.BeginWrite(
                        payload, 0, payload.Length,
                        iar =>
                            {
                                //When the writing is done, close the stream
                                requestStream.EndWrite(iar);
                                requestStream.Close();

                                //and switch to receiving the response from MPNS
                                request.BeginGetResponse(
                                    iarr =>
                                        {
                                            if (onComplete != null)
                                                onComplete();
                                        },
                                    null);
                            },
                        null);
                },
            null);
    }
}

Additional I also included my SettingsViewModel which is fully bindable.

[DataContract]
public class SettingsViewModel : ViewModelBase
{
    private ICommand _enforceTileUpdatesState;
    private bool _executing;
    private ICommand _setScheduleIfEnabled;
    private bool _tileUpdatesEnabled;

    [DataMember]
    public bool TileUpdatesEnabled
    {
        get { return _tileUpdatesEnabled; }
        set
        {
            if (value != _tileUpdatesEnabled)
            {
                _tileUpdatesEnabled = value;
                RaisePropertyChanged("TileUpdatesEnabled");
            }
        }
    }

    public bool Executing
    {
        get { return _executing; }
        set
        {
            if (value != _executing)
            {
                _executing = value;
                RaisePropertyChanged("Executing");
            }
        }
    }

    public ICommand SetScheduleIfEnabled
    {
        get
        {
            if (_setScheduleIfEnabled == null)
            {
                _setScheduleIfEnabled = new RelayCommand(
                    () =>
                        {
                            if (TileUpdatesEnabled)
                            {
                                Executing = true;
                                GetSchedule().CheckForStart();
                                Executing = false;
                            }
                        });
            }
            return _setScheduleIfEnabled;
        }
    }

    public ICommand EnforceTileUpdatesState
    {
        get
        {
            if (_enforceTileUpdatesState == null)
            {
                _enforceTileUpdatesState = new RelayCommand(
                    () =>
                        {
                            if (TileUpdatesEnabled)
                            {
                                Executing = true;
                                GetSchedule().Start(
                                    () =>
                                    Deployment.Current.Dispatcher.
                                        BeginInvoke(() => Executing = false));
                            }
                            else
                            {
                                Executing = true;
                                GetSchedule().Stop(
                                    () =>
                                    Deployment.Current.Dispatcher.
                                        BeginInvoke(() => Executing = false));
                            }
                        });
            }
            return _enforceTileUpdatesState;
        }
    }


    private SmartShellTileSchedule GetSchedule()
    {
        return new SmartShellTileSchedule
                    {
                        Interval = UpdateInterval.EveryHour,
                        RemoteImageUri =
                            new Uri(@"http://someserver/servertile.png"),
                        StartTime = DateTime.Now,
                        Recurrence = UpdateRecurrence.Interval
                    };
    }
}