しっぽを追いかけて

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

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

Prism を MVPVM 化する(2)View から対応する Presenter を取得できるようにする

はじめに前回の記事ですが、ObservableCollection のために Microsoft.BCL.Build.Tasks.dll を利用しているというところ・・・不要でした; ビルドターゲットを .NETFramework 4.5 にすればそれだけで利用できていました

気を取り直して Prism を MVPVM 化する(1)の続きです!

MVVM から MVPVM に拡張するにあたり、View と Presenter の関連付けを解決できるようにする必要があります

それはなぜかというと、少なくともストアアプリでは画面遷移の起点が Presenter ではなく View になるためです

ViewModel は View の DataContect を参照することで解決できますが、Presenter 用の仕組みはないので View の拡張クラスを作って解決できるようにします

/// <summary>
/// Presenter 属性
/// </summary>
[AttributeUsage(System.AttributeTargets.Class, AllowMultiple = false)]
public class PresenterViewAttribute : Attribute
{
    #region Privates

    /// <summary>
    /// Presenter の型
    /// </summary>
    private Type type;

    #endregion //Privates

    /// <summary>
    /// Presenter の型
    /// </summary>
    public Type Type
    {
        get { return this.type; }
        set { this.type = value; }
    }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="type">Presenter の型</param>
    public PresenterViewAttribute(Type type)
    {
        this.type = type;
    }
}

/// <summary>
/// Presenter つき FrameworkElement の拡張クラス
/// </summary>
public static class PresenterViewExtention
{
    /// <summary>
    /// Presenter の型を取得する
    /// </summary>
    /// <param name="view">View</param>
    /// <returns>Presenter の型</returns>
    public static Type GetPresenterType(this FrameworkElement view)
    {
        return PresenterViewExtention.GetAttribute(view).Type;
    }

    /// <summary>
    /// Presenter のインスタンスを取得する
    /// </summary>
    /// <typeparam name="T">Presenter の型</typeparam>
    /// <param name="view">View</param>
    /// <returns>Presenter</returns>
    public static T GetPresenter<T>(this FrameworkElement view) where T : class
    {
        return PresenterLocator.Get(view.GetPresenterType()) as T;
    }

    /// <summary>
    /// Presenter のインスタンスを取得する
    /// </summary>
    /// <param name="view">View</param>
    /// <returns>Presenter</returns>
    public static object GetPresenter(this FrameworkElement view)
    {
        return PresenterLocator.Get(view.GetPresenterType());
    }

    /// <summary>
    /// PresenterView の属性を取得する
    /// </summary>
    /// <param name="view">View</param>
    /// <returns>PresenterView の属性</returns>
    private static PresenterViewAttribute GetAttribute(FrameworkElement view)
    {
        var viewType = view.GetType();
        var attribute = viewType.GetTypeInfo().GetCustomAttribute(typeof(PresenterViewAttribute), false) as PresenterViewAttribute;
        if (attribute == null)
        {
            throw new InvalidOperationException("この Page には PresenterView 属性が指定されていません");
        }

        return attribute;
    }
}

FrameworkElement の拡張クラスを作成し、拡張メソッド GetPresenterType と GetPresenter を実装します

FrameworkElement には PresenterViewAttribute という属性を付加することで対応する Presenter クラスを関連付けるようにし、属性定義から Presenter クラスの型を取得することで Presenter のインスタンスを取得できるようにするというわけです

下記のように View から Presenter を取得することができるようになります

/// <summary>
/// トップ画面
/// </summary>
[PresenterView(typeof(TopPagePresenter))]
public sealed partial class TopPage : VisualStateAwarePage, IPresenterView<TopPagePresenter>
{
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public TopPage()
    {
        this.InitializeComponent();
    }

    #region IPresenterView<TPresenter>

    /// <summary>
    /// この画面の Presenter
    /// </summary>
    public TopPagePresenter Presenter
    {
        get { return this.GetPresenter<TopPagePresenter>(); }
    }

    #endregion //IPresenterView<TPresenter>
}

途中で PresenterLocator というコンテナを利用していますが、こちらは下記のように Unity の UnityContainer を利用しているだけです

/// <summary>
/// Presenter 配置クラス
/// </summary>
public class PresenterLocator
{
    #region Privates

    /// <summary>
    /// コンテナ
    /// </summary>
    private static readonly UnityContainer Container = new UnityContainer();

    #endregion //Privates

    /// <summary>
    /// コンストラクタ
    /// </summary>
    static PresenterLocator()
    {
        // アプリケーション内の IPresenterBase を実装するクラスをコンテナに一括登録する
        var presenterTypes =
            AllClasses.FromApplication()
            .Where(t => t.GetTypeInfo().ImplementedInterfaces.Any(i => i == typeof(IPresenterBase)));

        // コンテナ内で Presenter インスタンスをシングルトン化する
        Container.RegisterTypes(presenterTypes, getLifetimeManager: t => new ContainerControlledLifetimeManager());
    }

    /// <summary>
    /// Presenter を取得する
    /// </summary>
    /// <typeparam name="T">Presenter の型</typeparam>
    /// <returns>Presenter</returns>
    public static T Get<T>() where T : class
    {
        return Container.Resolve<T>();
    }

    /// <summary>
    /// Presenter を取得する
    /// </summary>
    /// <param name="type">Presenter の型</param>
    /// <returns>Presenter</returns>
    public static object Get(Type type)
    {
        return Container.Resolve(type);
    }
}

UnityContainer は WinRT 依存ですが、将来的には Portable Class Library 化されてほしいと思うほど便利なクラスです

とりあえず今回はここまでで MVPVM 化(3)に続きます!