Using attached dependency properties to toggle an Application Bar Icon from viewmodel
Application Bar Icon buttons are a look-and-feel-wise very consistent and easy to understand way for every Windows Phone 7 application to give it’s users access to the most important functions. The drawback of this thing is that it’s not a real FrameworkElement, thus you cannot bind commands and other stuff to it, but as I described earlier, this has been solved by the BindableApplicationBar.
But even the BindableApplicationBar has its issues. The IconUri attribute of a BindableApplicationBarIconButton cannot be bound to – for it is no Dependecy Property. Now you can fix that, since Nicolas Humann includes the source code of his excellent solution. But I prefer to threat someone else’s code as third party component, and solved the problem using a very powerful way of extending of Silverlight/Windows Phone 7: attached dependency properties.
The problem I wanted to solve is this: I have changed my MapMania App in such a way that it now tracks your location continually if you press the “Satellite” button. But I want the user to be able to stop tracking as well, using the same button. So what used to be a simple activation button, is now a toggle button. When the user is tracking his own phone, I want the button image to change from the image of of a satellite to an image of a satellite with a big cross through it, indicating “if you press me again, I will stop tracking”. And I wanted this, of course, to be steered from the viewmodel.
This is how I solved it, with three attached dependency properties:
using System; using System.Windows; using Phone7.Fx.Preview; namespace LocalJoost.Utilities { /// <summary> /// Supports toggle of a BindableApplicationBarIconButton's icon /// </summary> public static class AppBarIconFlipper { #region IconUri public static readonly DependencyProperty IconUriProperty = DependencyProperty.RegisterAttached("IconUri", typeof(Uri), typeof(AppBarIconFlipper), new PropertyMetadata(IconUriPropertyChanged)); // Called when Property is retrieved public static Uri GetIconUri(DependencyObject obj) { return obj.GetValue(IconUriProperty) as Uri; } // Called when Property is set public static void SetIconUri( DependencyObject obj, Uri value) { obj.SetValue(IconUriProperty, value); } // Called when property is changed private static void IconUriPropertyChanged( object sender, DependencyPropertyChangedEventArgs args) { var attachedObject = sender as BindableApplicationBarIconButton; if (attachedObject == null) return; attachedObject.IconUri = (bool)attachedObject.GetValue(ShowAlernateIconUriProperty) ? (Uri)attachedObject.GetValue(AlernateIconUriProperty) : (Uri)args.NewValue; } #endregion #region AlernateIconUri public static readonly DependencyProperty AlernateIconUriProperty = DependencyProperty.RegisterAttached("AlernateIconUri", typeof(Uri), typeof(AppBarIconFlipper), new PropertyMetadata(AlernateIconUriPropertyChanged)); // Called when Property is retrieved public static Uri GetAlernateIconUri(DependencyObject obj) { return obj.GetValue(AlernateIconUriProperty) as Uri; } public static void SetAlernateIconUri( DependencyObject obj, Uri value) { obj.SetValue(AlernateIconUriProperty, value); } private static void AlernateIconUriPropertyChanged( object sender, DependencyPropertyChangedEventArgs args) { var attachedObject = sender as BindableApplicationBarIconButton; if (attachedObject == null) return; attachedObject.IconUri = (bool)attachedObject.GetValue(ShowAlernateIconUriProperty) ? (Uri)args.NewValue : (Uri)attachedObject.GetValue(IconUriProperty); } #endregion #region ShowAlernateIconUri public static readonly DependencyProperty ShowAlernateIconUriProperty = DependencyProperty.RegisterAttached("ShowAlernateIconUri", typeof(bool), typeof(AppBarIconFlipper), new PropertyMetadata(ShowAlernateIconUriPropertyChanged)); public static bool GetShowAlernateIconUri(DependencyObject obj) { return (bool)obj.GetValue(ShowAlernateIconUriProperty); } public static void SetShowAlernateIconUri( DependencyObject obj, bool value) { obj.SetValue(ShowAlernateIconUriProperty, value); } private static void ShowAlernateIconUriPropertyChanged( object sender, DependencyPropertyChangedEventArgs args) { var attachedObject = sender as BindableApplicationBarIconButton; if (attachedObject == null) return; var value = (bool)args.NewValue; attachedObject.IconUri = value ? (Uri)attachedObject.GetValue(AlernateIconUriProperty) : (Uri)attachedObject.GetValue(IconUriProperty); } #endregion } }The only interesting code is in the “***Changed” methods, the rest is just the necessary plumbing. Anyway, in stead of setting the IconUri of the BindableApplicationBarIconButton directly, you use the attached dependency properties like this:
<Phone7Fx:BindableApplicationBarIconButton Command="{Binding ShowLocation}" Text="Track" LocalJoostUtils:AppBarIconFlipper.IconUri="/icons/gps.png" LocalJoostUtils:AppBarIconFlipper.AlernateIconUri="/icons/gps_stop.png" LocalJoostUtils:AppBarIconFlipper.ShowAlernateIconUri="{Binding IsTracking, Mode=TwoWay}"/>
and depending on the value of the Viewmodel property IsTracking the icon will flip from gps.png (false) to gps_stop (true) – or back.
Once again it shows Microsoft’s XAML-based platforms are as flexible as a rubber band, and wherever are holes in binding, you can almost always plug them using attached dependency properties – the super glue of MVVM, IMHO.