nuits.jp blog

C#, Xamarin, WPFを中心に書いています。Microsoft MVP for Development Technologies。

Prism for Xamarin.Forms入門 Command

さて、今回はCommand編です。
WPFやXamarin.Formsを利用されている方にとっては既に当たり前の機能かもしれません。
Commandって何よ?って方は、以前に概略をまとめたエントリーがありますので、まずはこちらからご覧ください。

www.nuits.jp

さてPrismにも、他のMVVMフレームワークと同様に、ICommandの実装クラスがあります。
いくつか特徴的な機能もありますので、今回はそれらを紹介していきます。

ぶっちゃけReactiveProperty使えよって気もしないでもないですけど、「知っていて使わない」という選択と、「知らない」というのは別でしょうから、良かったらお付き合いください。

なお、Prism for Xamarin.Forms入門、以下に目次がありますので、他のエントリーもご覧いただけると嬉しいです。
【Xamarin】Prism.Forms入門 目次 - nuits.jp blog

それでは早速進めましょう。

Overview

まず、以下のクラス図をご覧ください。

f:id:nuitsjp:20160919145531p:plain

Prismでは大きく分けて二種類のICommandの実装クラスが提供されています。

  • DelegateCommand
  • CompositeCommand

DelegateCommandは、Xamarin.Formsの提供するCommandクラスや、MVVM Light ToolkitでRelayCommadに類似した、ICommandの実装クラスです。
実行時の引数がGenerics対応されているものと、されていないものの二種類が存在します。
通常はこのクラスを利用するケースが多いでしょう。
CanExecuteの制御や、非同期メソッドのCommand化を容易に実現するための仕組みが用意されている点が、他のフレームワークで提供されるICommand実装とは異なる特色です。
なお今回は非同期周りについての説明は省略させていただきます。(私もまだ整理中でして。。。)

CompositeCommandは、クラス図や名前から推測できる通り、複数のCommandを束ねる事が可能な複合コマンドクラスになります。

それでは具体的な実装を交えて、そのあたりを説明していきたいと思います。
まずは、Prism Template Packを利用して、Prism.Formsのプロジェクトを作成してください。
プロジェクトの作成方法が分からない方は、以下を参考にしてください。

www.nuits.jp

また今回作成したコードは以下のリポジトリの06.Commandingに公開していますので良かったら参考にどうぞ。

github.com

DelegateCommand

ここでは、Commandの実行可能状態を制御する方法を説明します。
ICommandや、ActionやFuncを利用したコマンドの実装方法の説明はここでは割愛します。
分からない方は、こちらを参照してください。

単一項目によるCanExecute制御

まずは、単一のテキストボックスに、値が入力されていた場合に押下が可能となるボタンを作成します。 Prism Template Packで作成したプロジェクトを開き、MainPageViewModel.csクラスを開いてください。

その上で以下を追加します。

  • FirstNameプロパティ
  • NameCommandプロパティ
  • NameCommandプロパティの実行可否を判定するCanExecuteNameCommandメソッド
  • コンストラクタでのNameCommandの初期化処理

これらを実装した具体的なコードが以下のとおりです。

private string _firstName;
public string FirstName
{
    get { return _firstName; }
    set { SetProperty(ref _firstName, value); }
}

public ICommand NameCommand { get; }

public MainPageViewModel()
{
    NameCommand = 
        new DelegateCommand(() => { }, CanExecuteNameCommand)
            .ObservesProperty(() => FirstName);
}

private bool CanExecuteNameCommand()
{
    return !string.IsNullOrWhiteSpace(FirstName);
}

ポイントは、DelegateCommandをnewした後のObservesPropertyメソッドです。
この引数で、変更を監視するプロパティを式木(Expression Tree)で渡してあげます。
こうする事でプロパティの変更通知を監視し、プロパティが変更された際に、再度CanExecuteNameCommanを評価して、Commandの実行可能状態を制御します。

続いて、Viewを修正しましょう。
MainPage.xamlを開いてください。

以下の修正を実施します。

  • StackLayoutのHorizontalOptionsをFillに変更
  • Entryを追加し、TextにFirstNameプロパティをバインドする
  • Buttonを追加し、CommandにNameCommandプロパティをバインドする

具体的なコードは以下のとおりです。

<StackLayout HorizontalOptions="Fill" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
    <Entry Text="{Binding FirstName}" />
    <Button Text="Name Command" Command="{Binding NameCommand}"/>
</StackLayout>

では実行してみましょう。

f:id:nuitsjp:20160919150342g:plain

テキストボックス(Entry)に値が入力されるとボタンが活性化し、Entryが空になると非活性化されるのが見てとれるかと思います。

複数項目によるCanExecute制御

さて、実際のケースでは前述のように単純なケースはほとんどなく、複数の項目によって実行可能状態は制御されることになると思います。
というわけで、新たにFamilyNameを項目として追加し、双方が入力された時にボタンが活性化されるよう改修してみましょう。

まずはMainPageViewModelに以下の変更を施します。

  • FamilyNameプロパティの追加
  • NameCommandの初期化処理の修正
  • CanExecuteNameCommandの判定条件にFamilyNameプロパティを追加

具体的なコードは以下のとおりです。

private string _familyName;
public string FamilyName
{
    get { return _familyName; }
    set { SetProperty(ref _familyName, value); }
}

public MainPageViewModel()
{
    NameCommand =
        new DelegateCommand(() => { }, CanExecuteNameCommand)
            .ObservesProperty(() => FirstName)
            .ObservesProperty(() => FamilyName);
}

private bool CanExecuteNameCommand()
{
    return !string.IsNullOrWhiteSpace(FirstName)
            && !string.IsNullOrWhiteSpace(FamilyName);
}

ポイントは、ObservesPropertyメソッドを連続的に呼び出し、判定対象のプロパティを全て指定している点にあります。
ObservesPropertyメソッドの戻り値は、大元のDelegateCommandのインスタンスそのものなので、幾つでも繰り返し指定可能です。

XAML側にも修正して、FamilyNameをバインドするEntryを追加しましょう。

<StackLayout HorizontalOptions="Fill" VerticalOptions="Center">
    <Label Text="{Binding Title}" />
    <Entry Text="{Binding FirstName}" />
    <Entry Text="{Binding FamilyName}" />
    <Button Text="Name Command" Command="{Binding NameCommand}"/>
</StackLayout>

そして実行結果が以下のとおりです。

f:id:nuitsjp:20160919150434g:plain

boolプロパティによる実行制御

さて、Commandの実行可能状態が、bool型のプロパティとして存在する場合、もう少し簡素な記述が可能です。
Switchを追加して、SwitchがONの場合にのみ活性化されるButtonを作成してみます。
MainPageViewModelに以下を追加します。

  • IsToggledプロパティ
  • ToggleCommandプロパティ
  • コンストラクタでのToggleCommandの初期化処理
private bool _isToggled;
public bool IsToggled
{
    get { return _isToggled; }
    set { SetProperty(ref _isToggled, value); }
}

public ICommand ToggleCommand { get; }

public MainPageViewModel()
{
    ・・・省略
    ToggleCommand = new DelegateCommand(() => { }).ObservesCanExecute(_=> IsToggled);
}

ポイントは2点あります。

  • DelegateCommandのコンストラクタにcanExecuteを指定していない
  • ObservesCanExecuteで実行可否を判定するプロパティを指定している

ObservesCanExecuteメソッドでは、内部で指定されたプロパティによって判定するcanExecuteを式木から生成して置き換えています。
このため、DelegateCommandのコンストラクタでcanExecuteを指定していても置き換えられるため意味がありません。
と言うよりも
「ObservesCanExecuteでのみ指定すれば良い」
といった方が適切でしょうか?

続いてMainPage.xamlを開き、以下を修正します。

  • Switchを追加し、TextにIsToggledプロパティをバインドする
  • Buttonを追加し、CommandにToggleCommandプロパティをバインドする

修正結果は以下のとおりです。

<StackLayout HorizontalOptions="Fill" VerticalOptions="Center">
    ・・・省略
    <Switch IsToggled="{Binding IsToggled}" HorizontalOptions="Center"/>
    <Button Text="Toggle Command" Command="{Binding ToggleCommand}"/>
</StackLayout>

そして実行結果が以下のとおりです。

f:id:nuitsjp:20160919150435g:plain

CompositeCommand

PrismではCompositeパターンを適用した、そのものズバリなCompositeCommandクラスが提供されています。
利用方法は簡単です。
以下のように、CompositeCommandプロパティを作成し、そこに別途作成したCommandを登録することで利用します。

public CompositeCommand CompositeCommand { get; }

public MainPageViewModel()
{
    ・・・省略
    CompositeCommand = new CompositeCommand();
    CompositeCommand.RegisterCommand(NameCommand);
    CompositeCommand.RegisterCommand(ToggleCommand);
}

CompositeCommandでは、内包されるすべてのCommandが実行可能な場合のみ、自信も実行可能な状態となります。
実行順は登録順になります。

XAMLに上記のコマンドをバインドしたButtonを追加して動作を見てみましょう。

<StackLayout HorizontalOptions="Fill" VerticalOptions="Center">
    ・・・省略
    <Button Text="Composite Command" Command="{Binding CompositeCommand}"/>
</StackLayout>

f:id:nuitsjp:20160919150436g:plain

以上です。

総括

今回はPrismの提供するICommandの実装について見てきました。
なお、非同期メソッドの取り扱いについては、今回の中に含まれていません。
いくつか検証してみないと私自身理解しきれていないところがありますので、またの機会を見て整理したいと思います。

というわけで、今回はここまで。
次回は多分、EventAggregatorについて書くと思います。(多分ね
それではまた!