ViewMakerにみるMVVMの実装(2)

ViewModelに引き続き、ViewModelから送出されるイベントをハンドリングするView側のヘルパー処理について説明します。

MEFによる入れ替え可能なヘルパー処理の実装

ヘルパー処理のインタフェースはIViewCommandHelperで定義されており、MEFを使って実装を切り替えるようにしています。これによってアプリケーション固有の処理を含めやすくしています。たとえば、標準ではViewModelで発生した例外をメッセージボックスで表示していますが、画面下部に表示するなどの仕組みに切り替えることも簡単にできます。

    /// ViewCommandを処理する既定のヘルパー
    public interface IViewCommandHelper
    {
        /// ViewCommandのハンドリングが必要なViewModelを指定する
        IViewCommand ViewModel { get; set; }

        /// 指定したコントロールにヘルパーを割り当てる
        void Attach(FrameworkElement control);

        /// 指定したコントロールの割り当てたヘルパーを解除する
        void Detach();
    }

IViewCommandHelperの実装

WPF側の実装としては以下のような実装になっており、MEFの属性が付与されています。Silverlight版も同じような処理になっています。

    [Export(typeof(IViewCommandHelper))]
    [PartCreationPolicy(CreationPolicy.NonShared)] 
    public class WpfViewCommandHelper : IViewCommandHelper
    {
        ...

        void viewModel_ViewCommandNotified(object sender, ViewCommandEventArgs e)
        {
            var win = Window.GetWindow(content);
            if (win != null)
            {
                if (e.Command == ViewCommands.Show) win.Show();
                if (e.Command == ViewCommands.ShowDialog) win.ShowDialog();
                if (e.Command == ViewCommands.Close) win.Close();
            }
            if (content == null) return;
            if (e.Command == ViewCommands.EndCommand)
            {
                content.Cursor = oldCursor;
                var ex = e.Parameter as Exception;
                if (ex != null) MessageBox.Show(ex.Message, "Error");
            }

            if (e.Command == ViewCommands.ShowOpenFile)
            {
                var info = (SelectFileViewCommandInfo)e.Parameter;
                var dialog = new OpenFileDialog();
                dialog.Filter = info.Filter;
                if (dialog.ShowDialog() == true)
                {
                    info.FileStream = new FileStream(dialog.FileName, FileMode.Open, FileAccess.Read);
                }
                else
                {
                    info.FileStream = null;
                }
                return;
            }
            ...

ヘルパーのビヘイビア

作成したヘルパーはビヘイビアで設定できるようになっています。以下はSilverlight版のビヘイビアですが、IViewCommandHelperをWindowやContentControlに添付プロパティとして設定できます。

    public class ViewCommandBehavior : Behavior<FrameworkElement>
    {
        public static readonly DependencyProperty HelperProperty = DependencyProperty.RegisterAttached("Helper", typeof(IViewCommandHelper), typeof(ViewCommandBehavior), new PropertyMetadata(HelperCallback));

        public IViewCommandHelper Helper
        {
            get { return (IViewCommandHelper)this.GetValue(HelperProperty); }
            set { this.SetValue(HelperProperty, value); }
        }

        private static void HelperCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            if (Helper != null) Helper.Attach(AssociatedObject);
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            if (Helper != null) Helper.Detach();
        }
    }

XAML上でMEFで生成したオブジェクトを利用する

インタフェースを添付プロパティにしているのはヘルパーをMEFで任意のものに入れ変えるためです。
以下のようなstaticプロパティを作ると簡単にXAML上でもMEFで生成したオブジェクトを代入できます。

    public static class ViewCommandHelperLocator
    {
        public static IViewCommandHelper Helper
        {
            get { return ServiceLocator.GetService<IViewCommandHelper>(); }
        }
    }

XAML上は以下のようになります。

<Window x:Class="WpfSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:vg="clr-namespace:ViewMaker.Core.Wpf;assembly=ViewMaker.Core"
        xmlns:local="clr-namespace:WpfSample"
        Title="MainWindow" Height="350" Width="525"
        vg:ViewCommandBehavior.Helper="{x:Static local:ViewCommandHelperLocator.Helper}"
        >