Mimic the Game Hub Application Bar with a Behavior on the BindableApplicationBar

A couple of days ago Sébastien Lachance wrote an excellent article about mimicking the application bar of the game hub. Really interesting for one of my applications which makes use of a minimal applicationbar that’s also transparant. However quite often the texts of the menu are difficult to read when they don’t have a solid backgroud. That’s exactly what is solved in the game hub: a transparant applicationbar, but when expanded to view the menu it has a solid background. And Sébastien also found a solution how to fix this in our own applications.

However Sébastien is using the ApplicationBar, I’m using the wrapper around the ApplicationBar, called BindableApplicationBar that’s part of the Phone7.Fx project. So how to solve this?

The ApplicationBar and BindableApplicationBar both implement the IApplicationBar interface which prescribes the StateChanged event that is used by Sébastien in his solution. So it looks like we can use the same event, let’s look a little bit closer…

Seems that the code inside the BindableApplicationBar never fires the StateChanged event, so let’s fix this with a little bit of code in the BindableApplicationBar. Please be aware that you need to use the source version of the BindableApplicationBar to be able to fix this. So let’s subscribe the underlying ApplicationBar first, we do this in t he constructor of the BindableApplicationBar by adding one line.

public BindableApplicationBar()
{
    _applicationBar = new Microsoft.Phone.Shell.ApplicationBar();
    _applicationBar.StateChanged += HandleStateChanged;
    Loaded += BindableApplicationBarLoaded;
}

Now we still need to implement the HandleStateChanged method, which is a pretty straight forward implementation you’ve probably seen thousands of times.

private void HandleStateChanged(object sender, ApplicationBarStateChangedEventArgs e)
{
    if (StateChanged != null)
    {
        StateChanged(this, e);
    }
}

So we now have a BindableApplicationBar that has all the features we need to mimic the ApplicationBar of the game hub. The rest of the implementation could be similar to Sébastien’s code. However I thought this is a typical thing that you can easily implement and reuse in a Behavior. I’m not writing behaviors daily, so I thought giving a look at the blog of the Behavior Master himself, Joost van Schaik. About a year ago he wrote a pattern for safe event detachment in behaviors.

So I started with this pattern as a basis. However my eagerness to make this Behavior perfect by immediately using this pattern caused me a lot of trouble. The pattern also tries to safely remove the events when the AssociatedObject aka Control is unloaded. The control I use is of a special type, the BindableApplicationBar, which immediately calls the unloaded event, don’t know why, but it took me almost an hour to find this out. In the end I did a step back and removed the Safe Pattern, and came to the following behavior. The essence is in the highlighted line where I set the BarOpacity property.

public class TransparantToFillApplicationBarBehavior : Behavior<BindableApplicationBar>
{
    private double? _originalBarOpacity;

    protected override void OnAttached()
    {
        base.OnAttached();
        AssociatedObject.StateChanged += StateChanged;
    }

    private void StateChanged(object sender, ApplicationBarStateChangedEventArgs e)
    {
        if (!_originalBarOpacity.HasValue)
            _originalBarOpacity = AssociatedObject.BarOpacity;
        AssociatedObject.BarOpacity = e.IsMenuVisible ? 1 : _originalBarOpacity.Value;
    }

    protected override void OnDetaching()
    {
        AssociatedObject.StateChanged -= StateChanged;
        base.OnDetaching();
    }
}

And now you want to use it in your BindableApplicationBar, of course.

<ApplicationBar:BindableApplicationBar Mode="Minimized"
                                        BarOpacity="0.6"
                                        IsVisible="{Binding LoggedIn}" BackgroundColor="{StaticResource PhoneBackgroundColor}">
    <i:Interaction.Behaviors>
        <ApplicationBar:TransparantToFillApplicationBarBehavior />
    </i:Interaction.Behaviors>
    <ApplicationBar:BindableApplicationBarMenuItem Text="about+settings"
                                                    Command="{Binding AboutCommand}" />
</ApplicationBar:BindableApplicationBar>

And the end result is like this. Transparent when the menu is collapsed.

Transparant

And non-transparent when the menu is visible.

Fill

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());
    }
}

MeXperience – Step 3 – Architecture, implementing pipes and filters

In step 2 I explained about the Architecture of MeXperience I had in mind. This article explains the implementation of the pipes and filters pattern to filter the list of experience objects. I will start to tell that my implementation is based on an article from Oren Eini. The Filter In MeXperience there are currently only two types of filters: by tag and by role. But I could think about others like a filter by year of experience. The idea of the filter in the pipes and filters patterns is to have a simple operation, and a lot of combine simple operation in one pipeline make a complex operation. My filters are also used as objects to represent an item in the TagCloud. This is my base.
public abstract class CloudItem
{
    public CloudItem()
    {
        Weight = 1;
    }

    public int Weight { get; set; }
    public string Name { get; set; }

    public abstract IEnumerable<Experience> Filter(IEnumerable<Experience> experiences);
}
Yes I know it’s abstract and there’s no filter implementation. First the signature, there’s an enumeration of experiences coming as input, and there’s an enumeration as output. Let’s see one of the implementations, the of CloudItemTag.
public class CloudItemTag : CloudItem
{
    public override IEnumerable<Experience> Filter(IEnumerable<Experience> experiences)
    {
        foreach (Experience experience in experiences)
        {
                if (experience.Tags.Where(t => t.Name == Name).Count() > 0)
                    yield return experience;
        }
    }
}
The Filter implementation is a simple loop through the experiences, and if the experience corresponds to the filter it will yield return this experience. You can probably figure out how the CloudItemRole would look like. These filters are simple, let’s combine the pipeline of filters. The Pipeline Code explains more than words.
private IEnumerable<Experience> ApplyFilter(IEnumerable<CloudItem> filter)
{
    if (filter == null)
        return _experiences;
    IEnumerable<Experience> current = _experiences;
    foreach (CloudItem filterItem in filter)
    {
        current = filterItem.Filter(current);
    }
    IEnumerator<Experience> enumerator = current.GetEnumerator();
    while (enumerator.MoveNext()) ;

    return current;
}
Alright a little bit of explanation. First we start with the list of filters to apply, if the list is null we will not filter, else we will start with chaining all filters together in line 8. After everything is chained together we see all the magic happen in line 11. Until line 11 no filter has been executed, but by iterating through the experiences filter chain we will get a filtered list of experiences. What’s to come? There will at least be one more article on the MVVM implementation, but this is how it’s going to look like. Or look at this video. image

MeXperience – Step 2 – Architecture and more

Besides the purpose of the application itself, I want to make sure I expand my knowledge on Silverlight. This would be especially on the architecture of Silverlight applications. Architecture - MVVM I’ve read quite a lot of articles on MVVM, but there weren’t many article series that were as complete as the series on RIA, MEF and MVVM by Shawn Wildermuth (1,2,3,4). I have no intend to write an article or series on MVVM because it’s not really in my fingers yet. But to know more on MVVM please read the fantastic series by Shawn. But then again my intend is to make use of MVVM for MeXperience. The idea is to introduce two ViewModels (please let me know if you’d advice a different setup for ViewModels). 1. The ExperienceFilterViewModel, which supports showing all experience-tags and the ability to form a filter. 2. The ExperienceViewModel, which has control over all experience-parts that are found in the data store and can interact independent from the filter but can have filters applied as well. Because I chose to use the articles by Shawn as my knowledge base for MVVM, I will make use of the same components: MEF and MVVM Light Toolkit. If you have suggestions for other libraries, I’m interested, but this will be my start. Architecture – Pipes and Filters For the purpose of filtering the experience I want to introduce a Pipes and Filters design pattern. I know it’s not absolutely necessary but it makes sense for this purpose. Architecture – Experience Data Store It’s very standard to give an application a database for it’s data store. But to be honest there are many situation where a database isn’t the best choice. So in this occasion I think an xml file containing all the experience information will do. For now MeXperience will only be about the presentation of the experience. Maybe some time in the future there will be an application to edit this xml file. User Interface Components I’ve searched around the web (being aware of some licenses I won during Silverlight Control Builder Contest) for some User Interface components that could really help implementing the User Interface that I proposed in my prototype in the first part of my series. MeXperience Basic ViewMeXperience Detailed View The first piece of User Interface that I want to cover is the TagCloud. But when I search on Google, I first find a component on Codeplex which has tight integration with WCF, second result is an article from my own hand (July 2008), third is the very nice 3D-Tagcloud by Peter Gerritsen. But in the end all that looks like the best suitable component is the Silverlight Tag Cloud by Infragistics (I’m lucky to have the license). Further on the experience table-tile-view thingy. After some investigation I’m sure, it’s called a Tile View. Both Infragistics and Telerik have such a control. But because I already found a Tag Cloud by Infragistics I will use the Infragistics controls. Hope this will be a good choice.