locked
Page AppBars - using Command not working

    Question

  • I have not been able to find the answer to this (simple) question - how do you bind Commands to AppBarButtons. I have created a simple Store app (W8.1) with 3 pages (used blank pages template) called HomePage, SummaryPage and ReportsPage, added 3 RelayCommand properties in code behind, works fine from buttons on the page, can't get it to work with AppBarButtons

    Here's my XAML for one of the pages:

    <Page
        x:Name="pageRoot"
        DataContext="{Binding RelativeSource={RelativeSource Mode=Self}}"
        x:Class="CommandTest.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:CommandTest"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Page.TopAppBar>
            <CommandBar>
                <CommandBar.SecondaryCommands>
                    <AppBarButton 
                        Icon="Home" Label="Home"
                        Command="{Binding GoToHomeCommand}" />
                    <AppBarButton 
                        Icon="SelectAll" Label="Summary"
                        Command="{Binding GoToSummaryCommand}" />
                    <AppBarButton 
                        Icon="Document" Label="Reports"
                        Command="{Binding GoToReportsCommand}" />
                </CommandBar.SecondaryCommands>
            </CommandBar>
        </Page.TopAppBar>
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <TextBlock Text="Home page" Margin="40,40,0,0" Style="{StaticResource HeaderTextBlockStyle}"/>
            <StackPanel Margin="100,120,0,0">
                <Button Content="Home" VerticalAlignment="Top"
                        Command="{Binding GoToHomeCommand}"/>
                <Button Content="Summary" VerticalAlignment="Top"
                        Command="{Binding GoToSummaryCommand}"/>
                <Button Content="Reports" VerticalAlignment="Top"
                        Command="{Binding GoToReportsCommand}"/>
            </StackPanel>
    
        </Grid>
    </Page>
    

    I have tried various approaches without success, including creating a separate view model and binding the view model to the CommandBar. It would be very helpful if someone could provide an idiots guide to using commands in the AppBars - this would seem to be a fundamental use of these components!! Only MS examples I've found use event handlers - sooooo naughties!!

    Thanks in advance for any help.

    Thursday, April 17, 2014 7:34 AM

Answers

  • SOLVED: I think the answer lies in the fact that the AppBars do not have the page as their parent. I discovered that if you set the Top- and BottomAppBars' DataContext in the page's Loaded event handler everything works!! Here's the code behind MainPage as updated:

        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                this.Loaded += MainPage_Loaded;
    
                SayHelloCommand = new Common.RelayCommand(() => SayHello(), null);
    
            }
    
            void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                this.BottomAppBar.DataContext = this;
                this.TopAppBar.DataContext = this;
            }
    
            
    
            private void SayHello()
            {
                helloTextBlock.Text = string.IsNullOrEmpty(helloTextBlock.Text) ? "Hello, world!" : string.Empty;
                helloButton.Content = (helloButton.Content as string) == "Say hello" ? "Say goodbye" : "Say hello";
            }
    
            public ICommand SayHelloCommand { get; set; }
        }
    


    DogFather2

    • Marked as answer by DogFatherOf2 Sunday, May 25, 2014 1:21 PM
    Sunday, May 25, 2014 1:21 PM

All replies

  • I have never seen such issue, can you post an App with this issue so that I can try to help?

    -- Vishal Kaushik --

    Please 'Mark as Answer' if my post answers your question and 'Vote as Helpful' if it helps you. Happy Coding!!!

    Thursday, April 17, 2014 6:58 PM
  • Hi Vishal,

    Thanks for reply, sorry about delay in getting back to you. If written a very simple app to demonstrate the issue. Here's the project code:

    <Page
        x:Name="mainPage"
        x:Class="VerySimpleApp.MainPage"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:VerySimpleApp"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
        
        <Page.BottomAppBar>
            <CommandBar>
                <CommandBar.SecondaryCommands>
                    <AppBarButton Icon="Phone" Label="Say hello" Command="{Binding SayHelloCommand}"/>
                    <!--
                        This didn't work
                        <AppBarButton Icon="Phone" Label="Say hello" Command="{Binding SayHelloCommand, ElementName=mainPage}"/>
                    -->
                </CommandBar.SecondaryCommands>
            </CommandBar>
        </Page.BottomAppBar>
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <TextBlock x:Name="helloTextBlock" FontSize="36" VerticalAlignment="Center" HorizontalAlignment="Center" />
            <Button x:Name="helloButton" Content="Say hello" HorizontalAlignment="Left" Margin="100,100,0,0" VerticalAlignment="Top"
                    Command="{Binding SayHelloCommand, ElementName=mainPage}"/>
        </Grid>
    </Page>
    


        /// <summary>
        /// An empty page that can be used on its own or navigated to within a Frame.
        /// </summary>
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
    
                SayHelloCommand = new Common.RelayCommand(() => SayHello(), null);
    
                // This didn't work
                //this.BottomAppBar.DataContext = this;
            }
    
            private void SayHello()
            {
                helloTextBlock.Text = string.IsNullOrEmpty(helloTextBlock.Text) ? "Hello, world!" : string.Empty;
                helloButton.Content = (helloButton.Content as string) == "Say hello" ? "Say goodbye" : "Say hello";
            }
    
            public ICommand SayHelloCommand { get; set; }
        }
    

    and the RelayCommand used:

        /// <summary>
        /// A command whose sole purpose is to relay its functionality 
        /// to other objects by invoking delegates. 
        /// The default return value for the CanExecute method is 'true'.
        /// <see cref="RaiseCanExecuteChanged"/> needs to be called whenever
        /// <see cref="CanExecute"/> is expected to return a different value.
        /// </summary>
        public class RelayCommand : ICommand
        {
            private readonly Action _execute;
            private readonly Func<bool> _canExecute;
    
            /// <summary>
            /// Raised when RaiseCanExecuteChanged is called.
            /// </summary>
            public event EventHandler CanExecuteChanged;
    
            /// <summary>
            /// Creates a new command that can always execute.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            public RelayCommand(Action execute)
                : this(execute, null)
            {
            }
    
            /// <summary>
            /// Creates a new command.
            /// </summary>
            /// <param name="execute">The execution logic.</param>
            /// <param name="canExecute">The execution status logic.</param>
            public RelayCommand(Action execute, Func<bool> canExecute)
            {
                if (execute == null)
                    throw new ArgumentNullException("execute");
                _execute = execute;
                _canExecute = canExecute;
            }
    
            /// <summary>
            /// Determines whether this <see cref="RelayCommand"/> can execute in its current state.
            /// </summary>
            /// <param name="parameter">
            /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
            /// </param>
            /// <returns>true if this command can be executed; otherwise, false.</returns>
            public bool CanExecute(object parameter)
            {
                return _canExecute == null ? true : _canExecute();
            }
    
            /// <summary>
            /// Executes the <see cref="RelayCommand"/> on the current command target.
            /// </summary>
            /// <param name="parameter">
            /// Data used by the command. If the command does not require data to be passed, this object can be set to null.
            /// </param>
            public void Execute(object parameter)
            {
                _execute();
            }
    
            /// <summary>
            /// Method used to raise the <see cref="CanExecuteChanged"/> event
            /// to indicate that the return value of the <see cref="CanExecute"/>
            /// method has changed.
            /// </summary>
            public void RaiseCanExecuteChanged()
            {
                var handler = CanExecuteChanged;
                if (handler != null)
                {
                    handler(this, EventArgs.Empty);
                }
            }
        }
    

    Hope you can see what I'm doing wrong!

    Thanks.

    Sunday, May 18, 2014 1:57 PM
  • SOLVED: I think the answer lies in the fact that the AppBars do not have the page as their parent. I discovered that if you set the Top- and BottomAppBars' DataContext in the page's Loaded event handler everything works!! Here's the code behind MainPage as updated:

        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
                this.Loaded += MainPage_Loaded;
    
                SayHelloCommand = new Common.RelayCommand(() => SayHello(), null);
    
            }
    
            void MainPage_Loaded(object sender, RoutedEventArgs e)
            {
                this.BottomAppBar.DataContext = this;
                this.TopAppBar.DataContext = this;
            }
    
            
    
            private void SayHello()
            {
                helloTextBlock.Text = string.IsNullOrEmpty(helloTextBlock.Text) ? "Hello, world!" : string.Empty;
                helloButton.Content = (helloButton.Content as string) == "Say hello" ? "Say goodbye" : "Say hello";
            }
    
            public ICommand SayHelloCommand { get; set; }
        }
    


    DogFather2

    • Marked as answer by DogFatherOf2 Sunday, May 25, 2014 1:21 PM
    Sunday, May 25, 2014 1:21 PM
  • Yes, this is right, as I had the same problem and figured it out by myself, but that I think should be considered a bug. At least, I was expecting to have DataContext propagated from the Page through its relative CommandBar.

    Tommaso Scalici

    Thursday, October 30, 2014 8:24 PM
  • What you want to do is to set the AppBar on the LayoutAwarePage since all pages derives from that page. For the AppBar's content you might want to use a UserControl, for ease of coding and styling.

    First create the UserControl:

    <UserControl
    x:Class="AppBarGlobal.AppbarContent"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:AppBarGlobal"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    d:DesignHeight="300"
    d:DesignWidth="400">
    
    <StackPanel Orientation="Horizontal">
        <Button
            Content="1"
            Style="{StaticResource AppBarButtonStyle}" />
        <Button
            Content="2"
            Style="{StaticResource AppBarButtonStyle}" />
    </StackPanel> </UserControl>

    And then in the constructor of the LayoutAwarePage you want to create the AppBar, set the content to the UserControl, and add it to your Buttom or TopAppBar on the page - in this sample I use the BottomAppBar and set everthing in the consturctor, like this:

        public LayoutAwarePage()
        {
            bar = new AppBar();
            bar.Content = new AppbarContent();
            this.BottomAppBar = bar;
            //and the remaining code for the constructor hereafter.

    This should allow you to have a Global App bar inside your Windows Store app on all the pages that derive from LayoutAwarePage.

    Monday, November 3, 2014 7:37 AM