The quest for a Bindable ApplicationBar – Part 1

The funny thing about MVVM is that you might get so hooked on it that when you find something that just does not work with MVVM – you start trying to figure out how to bend it to the right path. One of such things is the ApplicationBar on Windows Phone.

Because the ApplicationBar isn’t a regular Silverlight control, but rather an OS/Shell provided control – it does not provide all the features of a Silverlight control, so to not expose these unsupported features – it does not derive from FrameworkElement. That is all well, except for the fact that by not deriving from FrameworkElement – it also does not have a DataContext, which makes binding rather hard and so it spoils your MVVM implementation. Of course you don’t want your ApplicationBar to be controlled in your page code behind, right? What if you want to port your application to another platform that does not have the same ApplicationBar, like WinRT, WPF or Silverlight?

Now, MVVM is just a pattern and not a rule, so it is perfectly fine to update your ApplicationBar in the code behind, but it is dirty, error prone and where is the fun in maintaining such code?

I have seen some solutions to that – one that relied on defining the ApplicationBar in the page view and also adding bindable behaviors to the page that map to the ApplicationBar buttons by their Text property, but that is plain ugly and hard to localize.

Another one – quite neat and close to my idea – was a BindableApplicationBar that derived from an ItemsControl. It solves some problems and seems like a good foundation to add some missing features – like commanding support, but it is back to being a FrameworkElement with lots of unsupported properties, it mixes buttons and menu items together since it only supports a single children collection and since I started on my solution before I found this one – mine has to be better. Smile

Another article had a comment from Rob Eisenberg that suggested that Caliburn Micro has support for that, but I have so far shied away from trying Caliburn out. From some glimpses of it – it seems like it is doing too much magic – possibly costing in performance and losing compile time/tooling checking support, also requiring everyone on the team to learn and strictly follow the conventions. I guess I am more of an MVVM Light/Prism fan.

I set out with these requirements:

  • Bindable commands on the icon buttons and menu items that
    • Execute on tap
    • Set IsEnabled on command’s CanExecuteChanged event based on the command’s CanExecute result
    • Support CommandParameter
  • Bindable/localizable/switchable – button/menu item labels and icons
  • All other bar/button properties should be bindable
  • Stretch goal 1: Bindable ApplicationBar’s ItemsSource for ApplicationBarIconButtons with some sort of template or configuration for binding the properties of the buttons to properties of the view models.
  • Stretch goal 2: Similar, but separate bindable ApplicationBar’s ItemsSource for ApplicationBarIconButtons
  • Stretch goal 3: Support for some buttons defined explicitly and some through ItemsSource

I decided to derive my BindableApplicationBar from FrameworkElement, since that seems to be required if you want to have support for bindings, but not from ItemsControl, since I want to have two separate collections of these properties. I also want to be able to specify that application bar inside of the page element in XAML, but since a page can only have a single Content element – I need to put it in an attached property. All in all – what I have in the works makes you end up with the syntax as below that you can insert in your PhoneApplicationPage.

<bar:Bindable.ApplicationBar>
    <bar:BindableApplicationBar>
        <bar:BindableApplicationBarButton
            Text="Test"
            IconUri="Icons/Dark/appbar.add.rest.png"/>
        <bar:BindableApplicationBarButton
            Text="Test 2"
            IconUri="Icons/Dark/appbar.minus.rest.png"
            IsEnabled="False"/>
    </bar:BindableApplicationBar>
</bar:Bindable.ApplicationBar>

The problem you get though when you put your FrameworkElement in a page as a dependency property rather than as content – it does not inherit the DataContext from its parent, which you really need if you want to have something to bind to.

In order to have support for that – you need to update the DataContext of the BindableApplicationBar whenever you attach it to the page or when the page’s DataContext changes. Since Silverlight does not give you a DataContextChanged event – you need to create it yourself as in the below example:

using System;
using System.Windows;
using System.Windows.Data;

namespace BindableApplicationBar
{
    /// <summary>
    /// Allows to subscribe for any control to an event triggered when the control's DataContext changes.
    /// </summary>
    public class FlexibleDataContextChangedHelper
    {
        #region Subscribe()
        public static void Subscribe<T>(T control, Action<T> callback)
            where T : FrameworkElement
        {
            FlexibleDataContextChangedHelper<T>.Subscribe(control, callback);
        }
        #endregion

        #region Unsubscribe()
        public static void Unsubscribe<T>(T control)
            where T : FrameworkElement
        {
            FlexibleDataContextChangedHelper<T>.Unsubscribe(control);
        }
        #endregion
    }

    /// <summary>
    /// Allows to subscribe for any control to an event triggered when the control's DataContext changes.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    internal class FlexibleDataContextChangedHelper<T> where T : FrameworkElement
    {
        private Action<T> _callback;

        #region InternalDataContext
        private const string InternalDataContextPropertyName = "InternalDataContext";
        public static readonly DependencyProperty InternalDataContextProperty =
            DependencyProperty.Register(
                InternalDataContextPropertyName,
                typeof(Object),
                typeof(T),
                new PropertyMetadata(InternalDataContextChanged));

        private static void InternalDataContextChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            T control = (T)sender;
            var handler = GetFlexibleDataContextChangedHandler(control);

            if (handler != null)
                handler.InvokeCallback(control);
        }
        #endregion

        #region FlexibleDataContextChangedHandler

        /// <summary>
        /// FlexibleDataContextChangedHandler Attached Dependency Property
        /// </summary>
        public static readonly DependencyProperty FlexibleDataContextChangedHandlerProperty =
            DependencyProperty.RegisterAttached(
                "FlexibleDataContextChangedHandler",
                typeof(FlexibleDataContextChangedHelper<T>),
                typeof(FlexibleDataContextChangedHelper<T>), //typeof(T), //?
                new PropertyMetadata(null));

        /// <summary>
        /// Gets the FlexibleDataContextChangedHandler property. This dependency property
        /// indicates the FlexibleDataContextChangedHelper instance responsible for routing the callback that is executed when the control's DataContext changes.
        /// </summary>
        public static FlexibleDataContextChangedHelper<T> GetFlexibleDataContextChangedHandler(DependencyObject d)
        {
            return (FlexibleDataContextChangedHelper<T>)d.GetValue(FlexibleDataContextChangedHandlerProperty);
        }

        /// <summary>
        /// Sets the FlexibleDataContextChangedHandler property. This dependency property
        /// indicates the FlexibleDataContextChangedHelper instance responsible for routing the callback that is executed when the control's DataContext changes.
        /// </summary>
        public static void SetFlexibleDataContextChangedHandler(DependencyObject d, FlexibleDataContextChangedHelper<T> value)
        {
            d.SetValue(FlexibleDataContextChangedHandlerProperty, value);
        }

        #endregion

        #region Subscribe()
        public static void Subscribe(T control, Action<T> callback)
        {
            Unsubscribe(control);

            control.SetBinding(InternalDataContextProperty, new Binding());
            var handler = new FlexibleDataContextChangedHelper<T>(callback);
            SetFlexibleDataContextChangedHandler(control, handler);
        }
        #endregion

        #region Unsubscribe()
        public static void Unsubscribe(T control)
        {
            control.SetValue(InternalDataContextProperty, null);

            var handler = GetFlexibleDataContextChangedHandler(control);

            if (handler != null)
            {
                handler._callback = null;
                SetFlexibleDataContextChangedHandler(control, null);
            }
        }
        #endregion

        #region CTOR
        private FlexibleDataContextChangedHelper(Action<T> callback)
        {
            _callback = callback;
        }
        #endregion

        #region InvokeCallback()
        private void InvokeCallback(T control)
        {
            if (_callback != null)
                _callback(control);
        }
        #endregion
    }
}

The FlexibleDataContextChangedHelper builds on the DataContextChangedHelper by Jeremy Likness, but allows any object to handle DataContext changes of any control using simple apis as below:

FlexibleDataContextChangedHelper.Subscribe(page, PageDataContextChanged);
FlexibleDataContextChangedHelper.Unsubscribe(page);

private void PageDataContextChanged(PhoneApplicationPage page)
{
}

Now I seem to have all the tools to start building the BindableApplicationBar…

Posts regarding Bindable ApplicationBar

  1. The quest for a Bindable ApplicationBar – Part 1
  2. The quest for a Bindable ApplicationBar – Part 2
  3. The quest for a Bindable ApplicationBar – Part 3
  4. BindableApplicationBar RC1 + Windows Phone Prism Application Template
  5. BindableApplicationBar for Windows Phone – Now On NuGet
Advertisements
Tagged , , , , , , , , ,

6 thoughts on “The quest for a Bindable ApplicationBar – Part 1

  1. […] The quest for a Bindable ApplicationBar – Part 1 […]

  2. […] The quest for a Bindable ApplicationBar – Part 1 […]

  3. […] Which implies that if you are so much used to MVVM and wants to bind things to ApplicationBar, its not easy; you need to create your own […]

  4. […] Which implies that if you are so much used to MVVM and wants to bind things to ApplicationBar, its not easy; you need to create your own […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: