Chapter 18: MVVM (VIII)

Keywords: iOS Attribute Lambda

Simple Method Execution
Let's look at a simple example. A program called PowersOfThree allows you to explore the powers of 3 using two buttons. One button increases the index and the other button decreases the index.
The PowersViewModel class is derived from the ViewModelBase class in the Xamarin.FormsBook.Toolkit library, but the ViewModel itself is located in the PowersOfThree application project. It is not limited to the power of 3, but the constructor needs a parameter, which is used as the base value of power calculation and is exposed as the BaseValue attribute. Because this property has a private set accessor and does not change after the constructor ends, it does not trigger the PropertyChanged event.
Two other properties, Exponent and Power Point, trigger PropertyChanged events, but they also have private set accessors. Exponent attributes are increased and decreased only by clicking on external buttons.
To implement the response to the Button tap, the PowersViewModel class defines two properties of type ICommand, named IncreaseExponentCommand and DecreaseExponentCommand. Similarly, both properties have private set accessors. As you can see, the constructor sets these two properties by instantiating Command objects that follow the constructor and refer to very few private methods. These two small methods are called when Command's Execute method is called. ViewModel uses the Command class instead of Command, because the program does not use any parameters of the Execute method:

class PowersViewModel : ViewModelBase
{
    double exponent, power;
    public PowersViewModel(double baseValue)
    {
        // Initialize properties.
        BaseValue = baseValue;
        Exponent = 0;
        // Initialize ICommand properties.
        IncreaseExponentCommand = new Command(ExecuteIncreaseExponent);
        DecreaseExponentCommand = new Command(ExecuteDecreaseExponent);
    }
    void ExecuteIncreaseExponent()
    {
        Exponent += 1;
    }
    void ExecuteDecreaseExponent()
    {
        Exponent -= 1;
    }
    public double BaseValue { private set; get; }
    public double Exponent
    {
        private set
        {
            if (SetProperty(ref exponent, value))
            {
                Power = Math.Pow(BaseValue, exponent);
            }
        }
        get
        {
            return exponent;
        }
    }
    public double Power
    {
        private set { SetProperty(ref power, value); }
        get { return power; }
    }
    public ICommand IncreaseExponentCommand { private set; get; }
    public ICommand DecreaseExponentCommand { private set; get; }
}

Both Execute Increase Exponent and Execute Decrease Exponent methods change the Exponent property (triggering the Property Changed event), and the Exponent property recalculates the Power property, which also triggers the Property Changed event.
In general, ViewModel instantiates its Command object by passing lambda functions to the Command constructor. This method allows these methods to be defined in the ViewModel constructor, as follows:

IncreaseExponentCommand = new Command(() =>
    {
        Exponent += 1;
    });
DecreaseExponentCommand = new Command(() =>
    {
        Exponent -= 1;
    });

The PowersOfThreePage XAML file binds the Text attributes of the three Label elements to the BaseValue, Exponent and Power attributes of the PowersViewModel class, and binds the Command attributes of the two Button elements to the Increase Exponent Command and and Decrease Exponent Command attributes of the ViewModel.
Note how to pass parameter 3 to the constructor of PowersViewModel because it is instantiated in the Resources dictionary. Passing parameters to the ViewModel constructor is the main reason for the existence of the x: Arguments tag:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:PowersOfThree"
             x:Class="PowersOfThree.PowersOfThreePage">
    <ContentPage.Resources>
        <ResourceDictionary>
            <local:PowersViewModel x:Key="viewModel">
                <x:Arguments>
                    <x:Double>3</x:Double>
                </x:Arguments>
            </local:PowersViewModel>
        </ResourceDictionary>
    </ContentPage.Resources>
    <StackLayout BindingContext="{StaticResource viewModel}">
        <StackLayout Orientation="Horizontal"
                     Spacing="0"
                     HorizontalOptions="Center"
                     VerticalOptions="CenterAndExpand">
            <Label FontSize="Large"
                   Text="{Binding BaseValue, StringFormat='{0}'}" />
 
            <Label FontSize="Small"
                   Text="{Binding Exponent, StringFormat='{0}'}" />

            <Label FontSize="Large"
                   Text="{Binding Power, StringFormat=' = {0}'}" />
        </StackLayout>
        <StackLayout Orientation="Horizontal"
                     VerticalOptions="CenterAndExpand">
            <Button Text="Increase"
                    Command="{Binding IncreaseExponentCommand}"
                    HorizontalOptions="CenterAndExpand" />
            <Button Text="Decrease"
                    Command="{Binding DecreaseExponentCommand}"
                    HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </StackLayout>
</ContentPage> 

This is what happens when you press a button or another button several times:

Again, when you need to change the view, you will find the wisdom to separate the user interface from the underlying business logic. For example, suppose you want to replace the button with an element with TapGesture Recognizer. Fortunately, TapGesture Recognizer has a Command attribute:

<StackLayout Orientation="Horizontal"
             VerticalOptions="CenterAndExpand">
    <Frame OutlineColor="Accent"
           BackgroundColor="Transparent"
           Padding="20, 40"
           HorizontalOptions="CenterAndExpand">
        <Frame.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding IncreaseExponentCommand}" />
        </Frame.GestureRecognizers>
        <Label Text="Increase"
               FontSize="Large" />
    </Frame>
    <Frame OutlineColor="Accent"
           BackgroundColor="Transparent"
           Padding="20, 40"
           HorizontalOptions="CenterAndExpand">
        <Frame.GestureRecognizers>
            <TapGestureRecognizer Command="{Binding DecreaseExponentCommand}" />
        </Frame.GestureRecognizers>
        <Label Text="Decrease"
               FontSize="Large" />
    </Frame>
</StackLayout>

Without touching the ViewModel or even renaming the event handler so that it can be applied to clicks rather than buttons, the program works the same way, but looks different:

Posted by ifubad on Sun, 03 Feb 2019 01:21:15 -0800