Κοινή χρήση μέσω


The Xamarin.Forms Command Interface

In the Model-View-ViewModel (MVVM) architecture, data bindings are defined between properties in the ViewModel, which is generally a class that derives from INotifyPropertyChanged, and properties in the View, which is generally the XAML file. Sometimes an application has needs that go beyond these property bindings by requiring the user to initiate commands that affect something in the ViewModel. These commands are generally signaled by button clicks or finger taps, and traditionally they are processed in the code-behind file in a handler for the Clicked event of the Button or the Tapped event of a TapGestureRecognizer.

The commanding interface provides an alternative approach to implementing commands that is much better suited to the MVVM architecture. The ViewModel itself can contain commands, which are methods that are executed in reaction to a specific activity in the View such as a Button click. Data bindings are defined between these commands and the Button.

To allow a data binding between a Button and a ViewModel, the Button defines two properties:

To use the command interface, you define a data binding that targets the Command property of the Button where the source is a property in the ViewModel of type ICommand. The ViewModel contains code associated with that ICommand property that is executed when the button is clicked. You can set CommandParameter to arbitrary data to distinguish between multiple buttons if they are all bound to the same ICommand property in the ViewModel.

The Command and CommandParameter properties are also defined by the following classes:

SearchBar defines a SearchCommand property of type ICommand and a SearchCommandParameter property. The RefreshCommand property of ListView is also of type ICommand.

All these commands can be handled within a ViewModel in a manner that doesn't depend on the particular user-interface object in the View.

The ICommand Interface

The System.Windows.Input.ICommand interface is not part of Xamarin.Forms. It is defined instead in the System.Windows.Input namespace, and consists of two methods and one event:

public interface ICommand
{
    public void Execute (Object parameter);

    public bool CanExecute (Object parameter);

    public event EventHandler CanExecuteChanged;
}

To use the command interface, your ViewModel contains properties of type ICommand:

public ICommand MyCommand { private set; get; }

The ViewModel must also reference a class that implements the ICommand interface. This class will be described shortly. In the View, the Command property of a Button is bound to that property:

<Button Text="Execute command"
        Command="{Binding MyCommand}" />

When the user presses the Button, the Button calls the Execute method in the ICommand object bound to its Command property. That's the simplest part of the commanding interface.

The CanExecute method is more complex. When the binding is first defined on the Command property of the Button, and when the data binding changes in some way, the Button calls the CanExecute method in the ICommand object. If CanExecute returns false, then the Button disables itself. This indicates that the particular command is currently unavailable or invalid.

The Button also attaches a handler on the CanExecuteChanged event of ICommand. The event is fired from within the ViewModel. When that event is fired, the Button calls CanExecute again. The Button enables itself if CanExecute returns true and disables itself if CanExecute returns false.

Important

Do not use the IsEnabled property of Button if you're using the command interface.

The Command Class

When your ViewModel defines a property of type ICommand, the ViewModel must also contain or reference a class that implements the ICommand interface. This class must contain or reference the Execute and CanExecute methods, and fire the CanExecuteChanged event whenever the CanExecute method might return a different value.

You can write such a class yourself, or you can use a class that someone else has written. Because ICommand is part of Microsoft Windows, it has been used for years with Windows MVVM applications. Using a Windows class that implements ICommand allows you to share your ViewModels between Windows applications and Xamarin.Forms applications.

If sharing ViewModels between Windows and Xamarin.Forms is not a concern, then you can use the Command or Command<T> class included in Xamarin.Forms to implement the ICommand interface. These classes allow you to specify the bodies of the Execute and CanExecute methods in class constructors. Use Command<T> when you use the CommandParameter property to distinguish between multiple views bound to the same ICommand property, and the simpler Command class when that isn't a requirement.

Basic Commanding

The Person Entry page in the sample program demonstrates some simple commands implemented in a ViewModel.

The PersonViewModel defines three properties named Name, Age, and Skills that define a person. This class does not contain any ICommand properties:

public class PersonViewModel : INotifyPropertyChanged
{
    string name;
    double age;
    string skills;

    public event PropertyChangedEventHandler PropertyChanged;

    public string Name
    {
        set { SetProperty(ref name, value); }
        get { return name; }
    }

    public double Age
    {
        set { SetProperty(ref age, value); }
        get { return age; }
    }

    public string Skills
    {
        set { SetProperty(ref skills, value); }
        get { return skills; }
    }

    public override string ToString()
    {
        return Name + ", age " + Age;
    }

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

The PersonCollectionViewModel shown below creates new objects of type PersonViewModel and allows the user to fill in the data. For that purpose, the class defines properties IsEditing of type bool and PersonEdit of type PersonViewModel. In addition, the class defines three properties of type ICommand and a property named Persons of type IList<PersonViewModel>:

public class PersonCollectionViewModel : INotifyPropertyChanged
{
    PersonViewModel personEdit;
    bool isEditing;

    public event PropertyChangedEventHandler PropertyChanged;

    ···

    public bool IsEditing
    {
        private set { SetProperty(ref isEditing, value); }
        get { return isEditing; }
    }

    public PersonViewModel PersonEdit
    {
        set { SetProperty(ref personEdit, value); }
        get { return personEdit; }
    }

    public ICommand NewCommand { private set; get; }

    public ICommand SubmitCommand { private set; get; }

    public ICommand CancelCommand { private set; get; }

    public IList<PersonViewModel> Persons { get; } = new ObservableCollection<PersonViewModel>();

    bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Object.Equals(storage, value))
            return false;

        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }

    protected void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

This abbreviated listing does not include the class's constructor, which is where the three properties of type ICommand are defined, which will be shown shortly. Notice that changes to the three properties of type ICommand and the Persons property do not result in PropertyChanged events being fired. These properties are all set when the class is first created and do not change thereafter.

Before examining the constructor of the PersonCollectionViewModel class, let's look at the XAML file for the Person Entry program. This contains a Grid with its BindingContext property set to the PersonCollectionViewModel. The Grid contains a Button with the text New with its Command property bound to the NewCommand property in the ViewModel, an entry form with properties bound to the IsEditing property, as well as properties of PersonViewModel, and two more buttons bound to the SubmitCommand and CancelCommand properties of the ViewModel. The final ListView displays the collection of persons already entered:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.PersonEntryPage"
             Title="Person Entry">
    <Grid Margin="10">
        <Grid.BindingContext>
            <local:PersonCollectionViewModel />
        </Grid.BindingContext>

        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <!-- New Button -->
        <Button Text="New"
                Grid.Row="0"
                Command="{Binding NewCommand}"
                HorizontalOptions="Start" />

        <!-- Entry Form -->
        <Grid Grid.Row="1"
              IsEnabled="{Binding IsEditing}">

            <Grid BindingContext="{Binding PersonEdit}">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                    <RowDefinition Height="Auto" />
                </Grid.RowDefinitions>

                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto" />
                    <ColumnDefinition Width="*" />
                </Grid.ColumnDefinitions>

                <Label Text="Name: " Grid.Row="0" Grid.Column="0" />
                <Entry Text="{Binding Name}"
                       Grid.Row="0" Grid.Column="1" />

                <Label Text="Age: " Grid.Row="1" Grid.Column="0" />
                <StackLayout Orientation="Horizontal"
                             Grid.Row="1" Grid.Column="1">
                    <Stepper Value="{Binding Age}"
                             Maximum="100" />
                    <Label Text="{Binding Age, StringFormat='{0} years old'}"
                           VerticalOptions="Center" />
                </StackLayout>

                <Label Text="Skills: " Grid.Row="2" Grid.Column="0" />
                <Entry Text="{Binding Skills}"
                       Grid.Row="2" Grid.Column="1" />

            </Grid>
        </Grid>

        <!-- Submit and Cancel Buttons -->
        <Grid Grid.Row="2">
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="*" />
                <ColumnDefinition Width="*" />
            </Grid.ColumnDefinitions>

            <Button Text="Submit"
                    Grid.Column="0"
                    Command="{Binding SubmitCommand}"
                    VerticalOptions="CenterAndExpand" />

            <Button Text="Cancel"
                    Grid.Column="1"
                    Command="{Binding CancelCommand}"
                    VerticalOptions="CenterAndExpand" />
        </Grid>

        <!-- List of Persons -->
        <ListView Grid.Row="3"
                  ItemsSource="{Binding Persons}" />
    </Grid>
</ContentPage>

Here's how it works: The user first presses the New button. This enables the entry form but disables the New button. The user then enters a name, age, and skills. At any time during the editing, the user can press the Cancel button to start over. Only when a name and a valid age have been entered is the Submit button enabled. Pressing this Submit button transfers the person to the collection displayed by the ListView. After either the Cancel or Submit button is pressed, the entry form is cleared and the New button is enabled again.

The iOS screen at the left shows the layout before a valid age is entered. The Android screen shows the Submit button enabled after an age has been set:

Person Entry

The program does not have any facility for editing existing entries, and does not save the entries when you navigate away from the page.

All the logic for the New, Submit, and Cancel buttons is handled in PersonCollectionViewModel through definitions of the NewCommand, SubmitCommand, and CancelCommand properties. The constructor of the PersonCollectionViewModel sets these three properties to objects of type Command.

A constructor of the Command class allows you to pass arguments of type Action and Func<bool> corresponding to the Execute and CanExecute methods. It's easiest to define these actions and functions as lambda functions right in the Command constructor. Here is the definition of the Command object for the NewCommand property:

public class PersonCollectionViewModel : INotifyPropertyChanged
{

    ···

    public PersonCollectionViewModel()
    {
        NewCommand = new Command(
            execute: () =>
            {
                PersonEdit = new PersonViewModel();
                PersonEdit.PropertyChanged += OnPersonEditPropertyChanged;
                IsEditing = true;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return !IsEditing;
            });

        ···

    }

    void OnPersonEditPropertyChanged(object sender, PropertyChangedEventArgs args)
    {
        (SubmitCommand as Command).ChangeCanExecute();
    }

    void RefreshCanExecutes()
    {
        (NewCommand as Command).ChangeCanExecute();
        (SubmitCommand as Command).ChangeCanExecute();
        (CancelCommand as Command).ChangeCanExecute();
    }

    ···

}

When the user clicks the New button, the execute function passed to the Command constructor is executed. This creates a new PersonViewModel object, sets a handler on that object's PropertyChanged event, sets IsEditing to true, and calls the RefreshCanExecutes method defined after the constructor.

Besides implementing the ICommand interface, the Command class also defines a method named ChangeCanExecute. Your ViewModel should call ChangeCanExecute for an ICommand property whenever anything happens that might change the return value of the CanExecute method. A call to ChangeCanExecute causes the Command class to fire the CanExecuteChanged method. The Button has attached a handler for that event and responds by calling CanExecute again, and then enabling itself based on the return value of that method.

When the execute method of NewCommand calls RefreshCanExecutes, the NewCommand property gets a call to ChangeCanExecute, and the Button calls the canExecute method, which now returns false because the IsEditing property is now true.

The PropertyChanged handler for the new PersonViewModel object calls the ChangeCanExecute method of SubmitCommand. Here's how that command property is implemented:

public class PersonCollectionViewModel : INotifyPropertyChanged
{

    ···

    public PersonCollectionViewModel()
    {

        ···

        SubmitCommand = new Command(
            execute: () =>
            {
                Persons.Add(PersonEdit);
                PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
                PersonEdit = null;
                IsEditing = false;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return PersonEdit != null &&
                       PersonEdit.Name != null &&
                       PersonEdit.Name.Length > 1 &&
                       PersonEdit.Age > 0;
            });

        ···
    }

    ···

}

The canExecute function for SubmitCommand is called every time there's a property changed in the PersonViewModel object being edited. It returns true only when the Name property is at least one character long, and Age is greater than 0. At that time, the Submit button becomes enabled.

The execute function for Submit removes the property-changed handler from the PersonViewModel, adds the object to the Persons collection, and returns everything to initial conditions.

The execute function for the Cancel button does everything that the Submit button does except add the object to the collection:

public class PersonCollectionViewModel : INotifyPropertyChanged
{

    ···

    public PersonCollectionViewModel()
    {

        ···

        CancelCommand = new Command(
            execute: () =>
            {
                PersonEdit.PropertyChanged -= OnPersonEditPropertyChanged;
                PersonEdit = null;
                IsEditing = false;
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return IsEditing;
            });
    }

    ···

}

The canExecute method returns true at any time a PersonViewModel is being edited.

These techniques could be adapted to more complex scenarios: A property in PersonCollectionViewModel could be bound to the SelectedItem property of the ListView for editing existing items, and a Delete button could be added to delete those items.

It isn't necessary to define the execute and canExecute methods as lambda functions. You can write them as regular private methods in the ViewModel and reference them in the Command constructors. However, this approach does tend to result in a lot of methods that are referenced only once in the ViewModel.

Using Command Parameters

It is sometimes convenient for one or more buttons (or other user-interface objects) to share the same ICommand property in the ViewModel. In this case, you use the CommandParameter property to distinguish between the buttons.

You can continue to use the Command class for these shared ICommand properties. The class defines an alternative constructor that accepts execute and canExecute methods with parameters of type Object. This is how the CommandParameter is passed to these methods.

However, when using CommandParameter, it's easiest to use the generic Command<T> class to specify the type of the object set to CommandParameter. The execute and canExecute methods that you specify have parameters of that type.

The Decimal Keyboard page illustrates this technique by showing how to implement a keypad for entering decimal numbers. The BindingContext for the Grid is a DecimalKeypadViewModel. The Entry property of this ViewModel is bound to the Text property of a Label. All the Button objects are bound to various commands in the ViewModel: ClearCommand, BackspaceCommand, and DigitCommand:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.DecimalKeypadPage"
             Title="Decimal Keyboard">

    <Grid WidthRequest="240"
          HeightRequest="480"
          ColumnSpacing="2"
          RowSpacing="2"
          HorizontalOptions="Center"
          VerticalOptions="Center">

        <Grid.BindingContext>
            <local:DecimalKeypadViewModel />
        </Grid.BindingContext>

        <Grid.Resources>
            <ResourceDictionary>
                <Style TargetType="Button">
                    <Setter Property="FontSize" Value="32" />
                    <Setter Property="BorderWidth" Value="1" />
                    <Setter Property="BorderColor" Value="Black" />
                </Style>
            </ResourceDictionary>
        </Grid.Resources>

        <Label Text="{Binding Entry}"
               Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3"
               FontSize="32"
               LineBreakMode="HeadTruncation"
               VerticalTextAlignment="Center"
               HorizontalTextAlignment="End" />

        <Button Text="CLEAR"
                Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2"
                Command="{Binding ClearCommand}" />

        <Button Text="&#x21E6;"
                Grid.Row="1" Grid.Column="2"
                Command="{Binding BackspaceCommand}" />

        <Button Text="7"
                Grid.Row="2" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="7" />

        <Button Text="8"
                Grid.Row="2" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="8" />

        <Button Text="9"
                Grid.Row="2" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="9" />

        <Button Text="4"
                Grid.Row="3" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="4" />

        <Button Text="5"
                Grid.Row="3" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="5" />

        <Button Text="6"
                Grid.Row="3" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="6" />

        <Button Text="1"
                Grid.Row="4" Grid.Column="0"
                Command="{Binding DigitCommand}"
                CommandParameter="1" />

        <Button Text="2"
                Grid.Row="4" Grid.Column="1"
                Command="{Binding DigitCommand}"
                CommandParameter="2" />

        <Button Text="3"
                Grid.Row="4" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="3" />

        <Button Text="0"
                Grid.Row="5" Grid.Column="0" Grid.ColumnSpan="2"
                Command="{Binding DigitCommand}"
                CommandParameter="0" />

        <Button Text="&#x00B7;"
                Grid.Row="5" Grid.Column="2"
                Command="{Binding DigitCommand}"
                CommandParameter="." />
    </Grid>
</ContentPage>

The 11 buttons for the 10 digits and the decimal point share a binding to DigitCommand. The CommandParameter distinguishes between these buttons. The value set to CommandParameter is generally the same as the text displayed by the button except for the decimal point, which for purposes of clarity is displayed with a middle dot character.

Here's the program in action:

Decimal Keyboard

Notice that the button for the decimal point in all three screenshots is disabled because the entered number already contains a decimal point.

The DecimalKeypadViewModel defines an Entry property of type string (which is the only property that triggers a PropertyChanged event) and three properties of type ICommand:

public class DecimalKeypadViewModel : INotifyPropertyChanged
{
    string entry = "0";

    public event PropertyChangedEventHandler PropertyChanged;

    ···

    public string Entry
    {
        private set
        {
            if (entry != value)
            {
                entry = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Entry"));
            }
        }
        get
        {
            return entry;
        }
    }

    public ICommand ClearCommand { private set; get; }

    public ICommand BackspaceCommand { private set; get; }

    public ICommand DigitCommand { private set; get; }
}

The button corresponding to the ClearCommand is always enabled and simply sets the entry back to "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged
{

    ···

    public DecimalKeypadViewModel()
    {
        ClearCommand = new Command(
            execute: () =>
            {
                Entry = "0";
                RefreshCanExecutes();
            });

        ···

    }

    void RefreshCanExecutes()
    {
        ((Command)BackspaceCommand).ChangeCanExecute();
        ((Command)DigitCommand).ChangeCanExecute();
    }

    ···

}

Because the button is always enabled, it is not necessary to specify a canExecute argument in the Command constructor.

The logic for entering numbers and backspacing is a little tricky because if no digits have been entered, then the Entry property is the string "0". If the user types more zeroes, then the Entry still contains just one zero. If the user types any other digit, that digit replaces the zero. But if the user types a decimal point before any other digit, then Entry is the string "0.".

The Backspace button is enabled only when the length of the entry is greater than 1, or if Entry is not equal to the string "0":

public class DecimalKeypadViewModel : INotifyPropertyChanged
{

    ···

    public DecimalKeypadViewModel()
    {

        ···

        BackspaceCommand = new Command(
            execute: () =>
            {
                Entry = Entry.Substring(0, Entry.Length - 1);
                if (Entry == "")
                {
                    Entry = "0";
                }
                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                return Entry.Length > 1 || Entry != "0";
            });

        ···

    }

    ···

}

The logic for the execute function for the Backspace button ensures that the Entry is at least a string of "0".

The DigitCommand property is bound to 11 buttons, each of which identifies itself with the CommandParameter property. The DigitCommand could be set to an instance of the regular Command class, but it's easier to use the Command<T> generic class. When using the commanding interface with XAML, the CommandParameter properties are usually strings, and that's the type of the generic argument. The execute and canExecute functions then have arguments of type string:

public class DecimalKeypadViewModel : INotifyPropertyChanged
{

    ···

    public DecimalKeypadViewModel()
    {

        ···

        DigitCommand = new Command<string>(
            execute: (string arg) =>
            {
                Entry += arg;
                if (Entry.StartsWith("0") && !Entry.StartsWith("0."))
                {
                    Entry = Entry.Substring(1);
                }
                RefreshCanExecutes();
            },
            canExecute: (string arg) =>
            {
                return !(arg == "." && Entry.Contains("."));
            });
    }

    ···

}

The execute method appends the string argument to the Entry property. However, if the result begins with a zero (but not a zero and a decimal point) then that initial zero must be removed using the Substring function.

The canExecute method returns false only if the argument is the decimal point (indicating that the decimal point is being pressed) and Entry already contains a decimal point.

All the execute methods call RefreshCanExecutes, which then calls ChangeCanExecute for both DigitCommand and ClearCommand. This ensures that the decimal point and backspace buttons are enabled or disabled based on the current sequence of entered digits.

Asynchronous Commanding for Navigation Menus

Commanding is convenient for implementing navigation menus. Here's part of MainPage.xaml:

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:DataBindingDemos"
             x:Class="DataBindingDemos.MainPage"
             Title="Data Binding Demos"
             Padding="10">
    <TableView Intent="Menu">
        <TableRoot>
            <TableSection Title="Basic Bindings">

                <TextCell Text="Basic Code Binding"
                          Detail="Define a data-binding in code"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:BasicCodeBindingPage}" />

                <TextCell Text="Basic XAML Binding"
                          Detail="Define a data-binding in XAML"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:BasicXamlBindingPage}" />

                <TextCell Text="Alternative Code Binding"
                          Detail="Define a data-binding in code without a BindingContext"
                          Command="{Binding NavigateCommand}"
                          CommandParameter="{x:Type local:AlternativeCodeBindingPage}" />

                ···

            </TableSection>
        </TableRoot>
    </TableView>
</ContentPage>

When using commanding with XAML, CommandParameter properties are usually set to strings. In this case, however, a XAML markup extension is used so that the CommandParameter is of type System.Type.

Each Command property is bound to a property named NavigateCommand. That property is defined in the code-behind file, MainPage.xaml.cs:

public partial class MainPage : ContentPage
{
    public MainPage()
    {
        InitializeComponent();

        NavigateCommand = new Command<Type>(
            async (Type pageType) =>
            {
                Page page = (Page)Activator.CreateInstance(pageType);
                await Navigation.PushAsync(page);
            });

        BindingContext = this;
    }

    public ICommand NavigateCommand { private set; get; }
}

The constructor sets the NavigateCommand property to an execute method that instantiates the System.Type parameter and then navigates to it. Because the PushAsync call requires an await operator, the execute method must be flagged as asynchronous. This is accomplished with the async keyword before the parameter list.

The constructor also sets the BindingContext of the page to itself so that the bindings reference the NavigateCommand in this class.

The order of the code in this constructor makes a difference: The InitializeComponent call causes the XAML to be parsed, but at that time the binding to a property named NavigateCommand cannot be resolved because BindingContext is set to null. If the BindingContext is set in the constructor before NavigateCommand is set, then the binding can be resolved when BindingContext is set, but at that time, NavigateCommand is still null. Setting NavigateCommand after BindingContext will have no effect on the binding because a change to NavigateCommand doesn't fire a PropertyChanged event, and the binding doesn't know that NavigateCommand is now valid.

Setting both NavigateCommand and BindingContext (in any order) prior to the call to InitializeComponent will work because both components of the binding are set when the XAML parser encounters the binding definition.

Data bindings can sometimes be tricky, but as you've seen in this series of articles, they are powerful and versatile, and help greatly to organize your code by separating underlying logic from the user interface.