しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Xamarin.Forms で使える ViewModelLocator

前回の投稿 で Prism の Xamarin 用 MVVM 基盤の拡張に光が見えたので、さっそく作ってみます

まずはプロジェクトの追加から

f:id:matatabi_ux:20141026210131p:plain

ViewModel のバインディングをするので Xamarin.Forms の PCL クラスライブラリプロジェクトを選んで追加します

データバインディングはおそらく Xamarin.Forms 主体になると思うので ViewModelLocator も Xamarin.Forms のクラスライブラリで記述すれば大丈夫なはずだからです

このプロジェクトに Prism.Mvvm と Prism.SharedInterfaces のプロジェクト参照も追加します

あとは Xamarin.Forms 用の ViewModelLocator を記述するだけ!

まぁでも簡単です

/// <summary>
/// ViewModel のロケータ
/// </summary>
public static class ViewModelLocator
{
    /// <summary>
    /// ViewModel を自動的にバインドするか否かを表す BindableProperty
    /// </summary>
    public static readonly BindableProperty AutoWireViewModelProperty
        = BindableProperty.Create(
        "AutoWireViewModel",
        typeof(bool),
        typeof(ViewModelLocator),
        false,
        BindingMode.TwoWay,
        null,
        AutoWireViewModelChanged);

    /// <summary>
    /// AutoWireViewModelProperty の変更イベントハンドラ
    /// </summary>
    /// <param name="bindable">BindableObject</param>
    /// <param name="oldValue">変更前の値</param>
    /// <param name="newValue">変更後の値</param>
    private static void AutoWireViewModelChanged(BindableObject bindable, object oldValue, object newValue)
    {
        var view = bindable as IView;

        if (view == null)
        {
            throw new Exception("Your views must implement IView");
        }

        ViewModelLocationProvider.AutoWireViewModelChanged(view);
    }

    /// <summary>
    /// AutoWireViewModel を取得します
    /// </summary>
    /// <param name="bindable">バインドされている BindingContext</param>
    /// <returns>自動的にバインドされた ViewModel が利用可能な場合 <c>true</c>  それ以外は <c>false</c></returns>
    public static bool GetAutoWireViewModel(BindableObject bindable)
    {
        if (bindable != null)
        {
            return (bool)bindable.GetValue(AutoWireViewModelProperty);
        }
        return false;
    }

    /// <summary>
    /// AutoWireViewModel を 設定します
    /// </summary>
    /// <param name="bindable">バインドされている BindingContext</param>
    /// <param name="value"><c>true</c> を設定した場合自動的に ViewModel がバインドされます</param>
    public static void SetAutoWireViewModel(BindableObject bindable, bool value)
    {
        if (bindable != null)
        {
            bindable.SetValue(AutoWireViewModelProperty, value);
        }
    }
}

本家の ViewModelLocator の DependencyProperty を BidableProperty に書き直すだけです

f:id:matatabi_ux:20141026210237p:plain

追加後のソリューション構成はこんな感じ

ただ実際に利用する場合にはもうひと手間かかりそうです

/// <summary>
/// Prism 用 ContentView の基底クラス
/// </summary>
public class ContentViewBase : ContentView, IView
{
    #region IView

    /// <summary>
    /// Bindable DataContext
    /// </summary>
    public object DataContext
    {
        get { return this.BindingContext; }
        set { this.BindingContext = value; }
    }

    #endregion //IView
}

ViewModelLocator の中で利用している IView のインタフェースは DataContext を参照可能にしますが、Xamarin.Forms だと BindingContext なのでその差異を吸収する必要があります

なので、View を利用する場合そのままではなく必ず IView インタフェースを実装しなければなりません

こういうところで微妙に名前の違うことの弊害を感じる・・・まぁとりあえずはこれで本家 Prism みたいに ViewModelLocator が利用できるようになるでしょうか?!