2017/6/20

Xamarin.Forms 教學系列文(十八.貳) MVVM - Command,當按鈕也要執行 ViewModel 的 Function




學習目標
  • Visual Element 與 Command 的綁定
  • CanExecute - 控制 Command 能否執行

Data binding 很強很好用,能幫我們把 ViewModel 的屬性和 View 的屬性連結起來,不用寫半行 事件控制 。

但,(萬惡的 But)

不是所有東西都是屬性啊... 有時候 ViewModel 的屬性要更改,還是得由事件來觸發。

問題來了*
當 Button 點擊時,同時要去更改 ViewModel 的屬性,該怎麼做?

回頭寫 Clicked 嗎?? 錯!!
ViewModel 提供了一個接口,叫做 Command Interface

簡單的說:
Button 可以綁定 ViewModel 內的方法,點擊時順便去更改 ViewModel 的屬性,再經由 INPC 去通知 View 做更動。

Command Interface

Visual Element 提供了兩個 Command 要用的屬性:
  1. Command // 拿來綁定 ViewModel 的 ICommand
  2. CommandParameter //參數傳遞

而 Xamarin 有 8 個 Class 支援 Command Interface:
  • Button
  • MenuItem
  • SearchBar
  • TextCell, ImageCell
  • ListView
  • TapGestureRecognizer

為了實現 Commanding,我們會有兩個步驟:
  1. ViewModel 宣告 ICommand 屬性
  2. 於建構子將 Command 實作並注入宣告的  ICommand

ICommand 介面定義了兩個方法和一個事件:

public interface ICommand 
    void Execute(object arg); // Business logic
    bool CanExecute(object arg);  // 設定此 Command 是否能被執行
    event EventHandler CanExecuteChanged; // 此事件觸發時會去執行 CanExecute() 方法
}


這裡只有定義介面... 所以我說那個實作呢 ? 

對此,Xamarin 很貼心的提供便便的 Command Command<T> 類別給我們這群懶人使用~


來看個簡單的範例,這程式利用按鈕的增減來控制 指數

ViewModel 內定義了兩個按鈕要用的 ICommand ,並在建構子初始化,
最後將含有方法的 Command 類別指派給這兩個 ICommand 屬性。

ViewModel:
class PowersViewModel : ViewModelBase
{
    double exponent, power;

    // 先宣告 Command 屬性
    public ICommand IncreaseExponentCommand { private set; get; }
    public ICommand DecreaseExponentCommand { private set; get; }

    public PowersViewModel(double baseValue)
    {
        // 初始值
        BaseValue = baseValue;
        Exponent = 0;

        // 初始 ICommand
        IncreaseExponentCommand = new Command(ExecuteIncreaseExponent);
        DecreaseExponentCommand = new Command(ExecuteDecreaseExponent);
    }

    // Command 要做的事,指數 +1
    void ExecuteIncreaseExponent()
    {
        Exponent += 1;
    }

    // 指數 -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;
        }
    }

}

P.S. 建構子的 Command 可換成 Lambda 寫法:
        IncreaseExponentCommand = new Command(() =>
        {
            Exponent += 1;
        });

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

XAML:
<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">
            <!-- 指數 +1 -->
            <Button Text="Increase" 
                    Command="{Binding IncreaseExponentCommand}"
                    HorizontalOptions="CenterAndExpand" />

            <!-- 指數 -1 -->
            <Button Text="Decrease" 
                    Command="{Binding DecreaseExponentCommand}" 
                    HorizontalOptions="CenterAndExpand" />
        </StackLayout>
    </StackLayout>
</ContentPage>

執行畫面:


更進一步

接著更進一步來熟悉 Execute、CanExecute 和 CommandParameter

底下是一個只有加法的計算機,
這小節不會將所有程式碼貼出,完整範例可以參考原文書 p.523 。



這個計算機有個規則,按下數字時,加法的按鈕才能執行,當加法的按鈕按下時,小數點的按鈕不能按,除非你再按下數字。

來看幾段 ViewModel 的程式碼:

一般數字鍵:
        NumericCommand = new Command(
            execute: (string parameter) => 
            {
                // 從 View 帶進來的 parameter
                if (isSumDisplayed || CurrentEntry == "0")
                    CurrentEntry = parameter;
                else CurrentEntry += parameter;

                    isSumDisplayed = false;
                    RefreshCanExecutes();
            }, 
            canExecute: (string parameter) => 
            {
                // 未加總或目前長度小於16
                return isSumDisplayed || CurrentEntry.Length < 16;
            });

Backspace 按鍵:
        BackspaceCommand = new Command(
            execute: () =>
            {
                CurrentEntry = CurrentEntry.Substring(0, CurrentEntry.Length - 1);

                if (CurrentEntry.Length == 0)
                {
                    CurrentEntry = "0";
                }

                RefreshCanExecutes();
            },
            canExecute: () =>
            {
                //還未加總且畫面上有值時,回傳 true,這 Command 可按!!!
                return !isSumDisplayed && (CurrentEntry.Length > 1 || CurrentEntry[0] != '0');
            });


可以注意一下 Execute 最後有一個 RefreshCanExecutes() ,內容如下:
當 Command 呼叫 ChangeCanExecute 時,就會執行此 Command 的 canExecute 方法。
    void RefreshCanExecutes()
    {
        ((Command)BackspaceCommand).ChangeCanExecute();
        ((Command)NumericCommand).ChangeCanExecute();
        ((Command)DecimalPointCommand).ChangeCanExecute();
        ((Command)AddCommand).ChangeCanExecute();
    }


補一下 XAML:
            
    <Button Text="&#x21E6;" Command="{Binding BackspaceCommand}" />
    <Button Text="7" Command="{Binding NumericCommand}" CommandParameter="7" />
    <Button Text="8" Command="{Binding NumericCommand}" CommandParameter="8" />















沒有留言:

張貼留言