Still looking for a sponsor Max Paulousky is looking for a Silverlight/.Net job in the Commonwealth

Share to Facebook Tweet this! Share to MySpace Share to Google Share to Live   Share via AddThis

Bindable Application Bar Extensions for Windows Phone 7

Abstracts

Application bar is a very important way to control your application and and execute additional actions. However, this control has several limitations. First of all, I would like to have collapsible application bar. Some screens need that control but it is not used very often. For example, WinPhone Internet Explorer – I do not use favourites or tabs commands five times per minute and would like to be able to hide/show it. But there is no way to do it.

The second limitation of AppBar is it is not bindable. At all. You can bind neither properties (IsEnabled, Text, IconUri) nor commands (Button click).

There is a good bindable application bar you can use in your applications.

I would like to propose some extensions for that control that allows you using DelegateCommand, remove duplicated code and use that bar for forms that are embedded into other forms.

Solution

Refactoring of BindableApplicationBarItems

Classes BindableApplicationBarIconButton and BindableApplicationBarMenuItem have a lot of duplicated code. I am going to provide information how we can refactor them.

Both classes above have the common interface IApplicationBarMenuItem. implemented once, we can inherit it.

BindableApplicationBarMenuItem implements IApplicationBarMenuItem:

public class BindableApplicationBarMenuItem : FrameworkElement, IApplicationBarMenuItem
{
    public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(BindableApplicationBarMenuItem), new PropertyMetadata(true, OnEnabledChanged));
 
    public static readonly DependencyProperty TextProperty = DependencyProperty.RegisterAttached("Text", typeof(string), typeof(BindableApplicationBarMenuItem), new PropertyMetadata(OnTextChanged));
 
    private static void OnEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != e.OldValue)
            ((BindableApplicationBarMenuItem)d).Item.IsEnabled = (bool)e.NewValue;
    }
 
    private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (e.NewValue != e.OldValue)
            ((BindableApplicationBarMenuItem)d).Item.Text = e.NewValue.ToString();
    }
 
    public IApplicationBarMenuItem Item
    {
        get;
        set;
    }
 
    public BindableApplicationBarMenuItem()
    {
        Item = CreateItem();
        if (DesignerProperties.IsInDesignTool)
            Item.Text = "Text";
 
        Item.Click += (s, e) =>
        {
            if (Click != null)
                Click(s, e);
        };
    }
 
    protected virtual IApplicationBarMenuItem CreateItem()
    {
        return new ApplicationBarMenuItem();
    }
 
    public bool IsEnabled
    {
        get
        {
            return (bool)GetValue(IsEnabledProperty);
        }
        set
        {
            SetValue(IsEnabledProperty, value);
        }
    }
 
    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }
 
    public event EventHandler Click;
 
}

Please, take into account that this implementation does not contain Command/CommandParameter properties – I will implement commanding via CommandBehaviour

BindableApplicationBarIconButton inherits the whole implementation from BindableApplicationBarMenuItem and adds just one property IconUri:

public class BindableApplicationBarIconButton : BindableApplicationBarMenuItem, IApplicationBarIconButton 
{
    public BindableApplicationBarIconButton():base()
    {
    }
 
    public ApplicationBarIconButton Button
    {
        get
        {
            return (ApplicationBarIconButton)Item;
        }
    }
 
    protected override IApplicationBarMenuItem CreateItem()
    {
        return new ApplicationBarIconButton();
    }
 
    public Uri IconUri
    {
        get
        {
            return Button.IconUri;
        }
        set
        {
            Button.IconUri = value;
        }
    }
}

Commanding

First of all, I am going to implement static AppBarItemClick class that allows combining Click event and ICommand implementation.

/// <summary>
/// Static Class that holds all Dependency Properties and Static methods to allow 
/// the Click event of the IApplicationBarMenuItem interface to be attached to a Command. 
/// </summary>
/// <remarks>
/// This class is required, because Silverlight for WinPhone doesn't have native support for Commands. 
/// </remarks>
public static class AppBarItemClick
{
    private static readonly DependencyProperty ClickCommandBehaviorProperty = DependencyProperty.RegisterAttached(
            "ClickCommandBehavior",
            typeof(AppBarItemCommandBehaviorBase<IApplicationBarMenuItem>),
            typeof(AppBarItemClick),
            null);
 
 
    /// <summary>
    /// Command to execute on click event.
    /// </summary>
    public static readonly DependencyProperty CommandProperty = DependencyProperty.RegisterAttached(
            "Command",
            typeof(ICommand),
            typeof(AppBarItemClick),
            new PropertyMetadata(OnSetCommandCallback));
 
    /// <summary>
    /// Command parameter to supply on command execution.
    /// </summary>
    public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.RegisterAttached(
            "CommandParameter",
            typeof(object),
            typeof(AppBarItemClick),
            new PropertyMetadata(OnSetCommandParameterCallback));
 
 
    /// <summary>
    /// Sets the <see cref="ICommand"/> to execute on the click event.
    /// </summary>
    /// <param name="item">AppBarItem dependency object to attach command</param>
    /// <param name="command">Command to attach</param>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for IApplicationBarMenuItem")]
    public static void SetCommand(IApplicationBarMenuItem item, ICommand command)
    {
        (item as FrameworkElement).SetValue(CommandProperty, command);
    }
 
    /// <summary>
    /// Retrieves the <see cref="ICommand"/> attached to the <see cref="IApplicationBarMenuItem"/>.
    /// </summary>
    /// <param name="item">IApplicationBarMenuItem containing the Command dependency property</param>
    /// <returns>The value of the command attached</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for IApplicationBarMenuItem")]
    public static ICommand GetCommand(IApplicationBarMenuItem item)
    {
        return (item as FrameworkElement).GetValue(CommandProperty) as ICommand;
    }
 
    /// <summary>
    /// Sets the value for the CommandParameter attached property on the provided <see cref="IApplicationBarMenuItem"/>.
    /// </summary>
    /// <param name="item">IApplicationBarMenuItem to attach CommandParameter</param>
    /// <param name="parameter">Parameter value to attach</param>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for IApplicationBarMenuItem")]
    public static void SetCommandParameter(IApplicationBarMenuItem item, object parameter)
    {
        (item as FrameworkElement).SetValue(CommandParameterProperty, parameter);
    }
 
    /// <summary>
    /// Gets the value in CommandParameter attached property on the provided <see cref="IApplicationBarMenuItem"/>
    /// </summary>
    /// <param name="item">IApplicationBarMenuItem that has the CommandParameter</param>
    /// <returns>The value of the property</returns>
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "Only works for IApplicationBarMenuItem")]
    public static object GetCommandParameter(IApplicationBarMenuItem item)
    {
        return (item as FrameworkElement).GetValue(CommandParameterProperty);
    }
 
    private static void OnSetCommandCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        IApplicationBarMenuItem item = dependencyObject as IApplicationBarMenuItem;
        if (item != null)
        {
            AppBarItemCommandBehaviorBase<IApplicationBarMenuItem> behavior = GetOrCreateBehavior(item);
            behavior.Command = e.NewValue as ICommand;
        }
    }
 
    private static void OnSetCommandParameterCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
    {
        IApplicationBarMenuItem item = dependencyObject as IApplicationBarMenuItem;
        if (item != null)
        {
            AppBarItemCommandBehaviorBase<IApplicationBarMenuItem> behavior = GetOrCreateBehavior(item);
            behavior.CommandParameter = e.NewValue;
        }
    }
 
    private static AppBarItemCommandBehaviorBase<IApplicationBarMenuItem> GetOrCreateBehavior(IApplicationBarMenuItem item)
    {
        AppBarItemCommandBehaviorBase<IApplicationBarMenuItem> behavior = (item as FrameworkElement).GetValue(ClickCommandBehaviorProperty) as AppBarItemCommandBehaviorBase<IApplicationBarMenuItem>;
        if (behavior == null)
        {
            behavior = new AppBarItemCommandBehaviorBase<IApplicationBarMenuItem>(item);
            (item as FrameworkElement).SetValue(ClickCommandBehaviorProperty, behavior);
        }
 
        return behavior;
    }
}

The second class implements command behaviour for IApplicationBarMenuItem:

/// <summary>
/// Behavior to handle connecting a <see cref="IApplicationBarMenuItem"/> to a Command.
/// </summary>
/// <typeparam name="T">The target object must derive from IApplicationBarMenuItem</typeparam>
public class AppBarItemCommandBehavior<T>
        where T : IApplicationBarMenuItem
{
    private ICommand command;
    private object commandParameter;
    private readonly WeakReference targetObject;
    private readonly EventHandler commandCanExecuteChangedHandler;
 
    /// <summary>
    /// Constructor specifying the target object.
    /// </summary>
    /// <param name="targetObject">The target object the behavior is attached to.</param>
    public AppBarItemCommandBehavior(T targetObject)
    {
        this.targetObject = new WeakReference(targetObject);
        commandCanExecuteChangedHandler = new EventHandler(this.CommandCanExecuteChanged);
        ((T)this.targetObject.Target).Click += (s, e) => ExecuteCommand();
    }
 
    /// <summary>
    /// Corresponding command to be execute and monitored for <see cref="ICommand.CanExecuteChanged"/>
    /// </summary>
    public ICommand Command
    {
        get
        {
            return command;
        }
        set
        {
            if (this.command != null)
                this.command.CanExecuteChanged -= this.commandCanExecuteChangedHandler;
 
            this.command = value;
            if (this.command != null)
            {
                this.command.CanExecuteChanged += this.commandCanExecuteChangedHandler;
                UpdateEnabledState();
            }
        }
    }
 
    /// <summary>
    /// The parameter to supply the command during execution
    /// </summary>
    public object CommandParameter
    {
        get
        {
            return this.commandParameter;
        }
        set
        {
            if (this.commandParameter != value)
            {
                this.commandParameter = value;
                this.UpdateEnabledState();
            }
        }
    }
 
    /// <summary>
    /// Object to which this behavior is attached.
    /// </summary>
    protected T TargetObject
    {
        get
        {
            return (T)targetObject.Target;
        }
    }
 
    /// <summary>
    /// Updates the target object's IsEnabled property based on the commands ability to execute.
    /// </summary>
    protected virtual void UpdateEnabledState()
    {
        if (TargetObject == null)
        {
            this.Command = null;
            this.CommandParameter = null;
        }
        else if (this.Command != null)
            TargetObject.IsEnabled = this.Command.CanExecute(this.CommandParameter);
    }
 
    private void CommandCanExecuteChanged(object sender, EventArgs e)
    {
        this.UpdateEnabledState();
    }
 
    /// <summary>
    /// Executes the command, if it's set, providing the <see cref="CommandParameter"/>
    /// </summary>
    protected virtual void ExecuteCommand()
    {
        if (this.Command != null)
            this.Command.Execute(this.CommandParameter);
    }
}

Using of commanding is very simple:

xaml:
<phone:PhoneApplicationPage
    xmlns:ctrlShell="clr-namespace:Controls.Shell;assembly=Controls"
    xmlns:abicmd="clr-namespace:Controls.Commands.ApplicationBarItem;assembly=Controls">
.......................
<ctrlShell:BindableApplicationBar x:Name="AppBar">
    <ctrlShell:BindableApplicationBarIconButton abicmd:AppBarItemClick.Command="{Binding ApplySettingsCommand}" Text="apply" IconUri="/icons/appbar.check.rest.png" />
    <ctrlShell:BindableApplicationBarIconButton abicmd:AppBarItemClick.Command="{Binding CancelSettingsCommand}" Text="cancel" IconUri="/icons/appbar.cancel.rest.png" />
</ctrlShell:BindableApplicationBar>
.......................
 
cs:
private void RegisterCommands()
{
    CancelSettingsCommand = new DelegateCommand<object>(DoCancelSettings, CanCancelSettings);
    ApplySettingsCommand = new DelegateCommand<object>(DoApplySettings, CanApplySettings);
}

Displaying Application bar on embedded pages

If you define an Application bar on an embedded PhonePage it will not be displayed. Because only host page can display the Application bar. In that case we have to assign that bar to the host page at runtime.

It can be done on Loaded event of the BindableApplicationBar.

public BindableApplicationBar()
{
    _applicationBar = new ApplicationBar();
    
    this.Loaded += new RoutedEventHandler(BindableApplicationBar_Loaded);
}
 
void page_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
{
    SetApplicationBar(null);
}
 
void BindableApplicationBar_Loaded(object sender, RoutedEventArgs e)
{
    SetApplicationBar(_applicationBar);
}
 
private void SetApplicationBar(IApplicationBar bar)
{
    var page = this.GetVisualAncestors().Where(c => c is PhoneApplicationPage).LastOrDefault() as PhoneApplicationPage;
    if (page != null)
        page.ApplicationBar = bar;
    
    if (bar != null)
        page.BackKeyPress += new EventHandler<System.ComponentModel.CancelEventArgs>(page_BackKeyPress);
    else
        page.BackKeyPress -= page_BackKeyPress;
}

When page loads, it loads BindableApplicationBar and after that it finds host page and set BindableApplicationBar to ApplicationBar property of the host. Also, when the bar is loaded, it assigns BackKeyPress handler to clear ApplicationBar property when user goes back. But when user goes forward from page with activated ApplicationBar, last one should be deactivated. That behaviour can be implemented on Screen.Deactivate(), if you use Screen Framework for Silverlight applications from John Papa

var shellPage = (Application.Current.RootVisual as FrameworkElement).GetShell();
 
IScreen newScreen = EnsureScreenExists(args.ScreenKey, args.RegionName);
 
if (((FrameworkElement)newScreen.View).GetVisualDescendents().Where(e => e.GetType().Name.Equals("BindableApplicationBar")).Count() == 0)
{
    if (shellPage != null)
        shellPage.ApplicationBar = null;
}

Also, we can subscribe on PhoneApplicationFrame event Navigated and clear ApplicationBar property.

Sources

You can grab sources here.

Conclusion

As you can see from my article, it is not easy to use Application Bar control in applications based on MVVM pattern. But it is possible – it is needed to implement command behaviour for commanding, assign to ApplicationBar property of the host page, implement specific behaviour on  Load/Unload events.

This work is licensed under a Creative Commons Attribution By license.

Leave a Comment [ RSS ]

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Alex says:

    Max, nice code.
    But what about theming? do you support both light and dark icons?

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Yes, I do
    because I wrap native ApplicationBar, ApplicationBarIconButton and ApplicationBarMenuItem

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Henrik says:

    I am new to WP7/Silverlight development, I want to be able bind to the IconUri to toggle the icon depending on a state in the ViewModel,.

    I tried changing the IconUri to a dependency property but I get an ArgumentNullException in OnItemChanged
    _applicationBar.Buttons.Add(button.Button);
    button.Button.IconUri == null,

    Is this the recommended way to do it?

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Henrik, could you please provide sources of your application where you have mentioned issue? just send me an archive to aximus (at) tut.by

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... friso says:

    Hi Max,

    I'm trying to use this bindable appbar, but with your code as well as with the original code from Nicolas, I get a xamlparseexception when I try to add a bindable appbar to my page (even without using any binding feature). Any Ideas on what I could be doint wrong. I just added the code files to my project. Thanks in advance.

    <phone:PhoneApplicationPage.ApplicationBar>
    <ctrl:BindableApplicationBar IsVisible="True" IsMenuEnabled="True">
    <ctrl:BindableApplicationBarIconButton IconUri="/icons/appbar.add.rest.png" Text="add" />
    </ctrl:BindableApplicationBar>
    </phone:PhoneApplicationPage.ApplicationBar>

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... friso says:

    Solved it. You should not put the bindable app bar in the <phone:PhoneApplicationPage.ApplicationBar> tags

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Rupert says:

    Hi,
    I'm trying to use the Bindable APplicationBar and have taken the code and included it in my project, but am getting an error for the AppBarItemCommandBehaviourBase type - could not be found.

    I have looked in the original code by Nicolas Humann but can't find it there either - what am I doing wrong?

    Cheers,
    Rupert.

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Rupert, I am really sorry. That's my mistake
    Please, use AppBarItemCommandBehavior<> instead of AppBarItemCommandBehaviorBase<>

    @friso, glad to hear it

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... mikeloaf says:

    Thanx for this ApplicationBar!

    A bit of advise:
    when you update the menuitems collection you use the following statement:

    foreach (BindableApplicationBarMenuItem button in Items.Where(c => c is BindableApplicationBarMenuItem))
    _applicationBar.MenuItems.Add(button.Item);

    but since the BindableApplicationBarIconButton inherits from BindableApplicationBarMenuItem, every buttons text is also added to the menuitems
    to avoid this just make sure its not a button:

    foreach (BindableApplicationBarMenuItem button in Items.Where(c => c is BindableApplicationBarMenuItem && !(c is BindableApplicationBarIconButton)))
    _applicationBar.MenuItems.Add(button.Item);

    Mike

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Thanks, Mike!

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Petr says:

    Hello, I spent a day investigating why Command binded to bindable application bar from Phone7.Fx.Preview doesn't raise canexecutechanged event.
    Than I found your extensions that do just that - thank you very much!!!

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Petr says:

    And once again :) I just read mikeloaf's comment about menuitems collection and I love him!!! Nice! Thanks to both of you!

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Andre says:

    Thank you for a helpful component!

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Abhinav says:

    Can I use this code in an app which will be sold in market place?

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... Denis Osipov says:

    Thanks for great component!

    A small advice

    When BackKeyPress occur you just call SetApplicationBar(null); and It close BindableApplicationBar even if e.Cancel == true

    to avoid this just check e.Cancel state:
    BindableApplicationBar::page_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e)
    {
    if (e.Cancel != true)
    SetApplicationBar(null);
    }

  • re: Bindable Application Bar Extensions for Windows Phone 7

    @Abhinav
    yes, you can. You need just follow next rules

  • re: Bindable Application Bar Extensions for Windows Phone 7

    @Denis Osipov
    Thank you! I will check it

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... rukai says:

    Good job!

    btw, does the Text can be bindable as well?

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... rukai says:

    I think i can implement based on your code ; )

    thanks!

  • re: Bindable Application Bar Extensions for Windows Phone 7

    Requesting Gravatar... SENNA says:

    I am using the Bindable Application Bar, thanks for your code. However, I am doing something wrong. I have one page with add/edit/delete buttons. When edit is clicked it opens a second page with a save option. If I then hit the back button, the menu icons are momentarily displayed and then they disappear and only the empty gray menu bar is visible! I can't see where this occurs in the code. Can you give me any pointers on what is causing the problem?

Comments have been closed on this topic.