かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

Expression BlendのMVVMサポート機能とMEFの連携

Expression blendを見てると、MVVMをサポートした形のUserControlのテンプレートがあります。こいつは以下のようなコードを作成してくれます。

<UserControl
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	xmlns:local="clr-namespace:SilverlightApplication1"
	x:Class="SilverlightApplication1.View1"
	d:DesignWidth="640" d:DesignHeight="480">
	<UserControl.Resources>
		<local:View1Model x:Key="View1ModelDataSource" />
	</UserControl.Resources>

	<Grid x:Name="LayoutRoot" DataContext="{Binding Source={StaticResource View1ModelDataSource}}"/>
</UserControl>
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Data;
using System.ComponentModel;

namespace SilverlightApplication1
{
	public class View1Model : INotifyPropertyChanged
	{
		public View1Model()
		{
			
		}

		#region INotifyPropertyChanged
		public event PropertyChangedEventHandler PropertyChanged;

		private void NotifyPropertyChanged(String info)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(info));
			}
		}
		#endregion
	}
}

ViewModelは、INotifyPropertyChangedインターフェースを実装しただけのシンプルなクラスで、そのクラスをUserControlのResourcesに登録してLayoutRootのDataContextにバインドしています。この形だと、Blendの支援機能がフルに受けることが出来るのでBlendを使う場合は、基本的にこのテンプレートを使うと楽そうです。


このテンプレートを使うとなると、MEF(Managed Extensibility Framework)とどう連携させたものか悩みものです。ViewはMEFで管理しても、あまり意味は無さそうです。(View自体をアドインするようなシステム作るなら意味あるかも)なんたってViewのインスタンスがつくられると自動的にViewModelのインスタンスがひもづいてきます。これを分離しようとするとBlendの支援機能をフルに使って開発することは難しくなるような気がします。


よく考えると、Viewは実行して見てみないことには確認出来ないのでViewModelを差し替え可能にしても単体テスト的には、あまりうまいことにはならないような気がします。ということなので、ViewとViewModelはがっちり結合してもらったままにしよう。

次にViewModelですが、こいつは単体テストしたいです。なのでViewModelが依存するクラスはMEFにインジェクションしてもらうほうが楽そうです。でも、Viewのインスタンス化と同時にViewModelもインスタンス化されてしまうため、ViewModelもMEFで管理することは難しそうです。


そんな時のために、SilverlightのMEFにはSystem.ComponentModel.Composition.ComositionInitializerクラスのSatisfyImportsメソッドがあります。こいつは、引数で渡されたオブジェクトにMEFで管理しているオブジェクトを注入してくれる動きをします。この時に使われるMEFのコンテナは、どこかでstaticに管理されていてSystem.ComponentModel.Composition.Hosting.CompositionHostクラスのInitializeメソッドを使ってコンテナの初期化をすることが出来ます。この機能WPFにもほしかった・・・。ということでSilverlightでは、この機能を使うのが良さげです。


SatisfyImportsをするタイミングですが、UserControlのLoadedイベントかコンストラクタあたりでやっておけば問題ないと思われます。コードとしては以下のような一行になると思います。

CompositionInitializer.SatisfyImports(LayoutRoot.DataContext);


大した手間ではないような気もしますが、Behavior化してBlendからポトペタでできるようにします。

namespace SilverlightApplication1
{
    using System;
    using System.ComponentModel.Composition;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;

    public class MEFBehavior : Behavior<UserControl>
    {
        public MEFBehavior()
        {
        }

        protected override void OnAttached()
        {
            base.OnAttached();
            this.AssociatedObject.Loaded += this.LayoutRootLoaded;
        }

        protected override void OnDetaching()
        {
            base.OnDetaching();
            this.AssociatedObject.Loaded -= this.LayoutRootLoaded;
        }

        private void LayoutRootLoaded(object sender, EventArgs e)
        {
            var root = this.AssociatedObject.Content as FrameworkElement;
            if (root == null)
            {
                return;
            }

            CompositionInitializer.SatisfyImports(root.DataContext);
        }
    }
}

こういうのを作っておけば、Blendで、このビヘイビアをUserControlにぽとっとするだけで対応するViewModelにMEFで管理するオブジェクトが注入されます。


小道具として加えておこう。