かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

MEF(Managed Extensibility Framework) & WPFでHello world

注意:MEFは、正式リリース前のbeta版を使ってるので、正式版とは違うかもしれません。
次期.NET Framework4に入るDIコンテナのMEFとWPFを組み合わせて、簡単なHello worldを作ってみようと思います。

下準備

まず、最初にWPFアプリケーションを作成します。名前は「MefWpfHelloWorld」にしました。
参照設定で、MEFのアセンブリであるSystem.ComponentModel.Composition.dllを追加します。次に、Window1.xamlの名前が気に入らないので、MainWindow.xamlに名前変更をします。クラス名と、XAML内にあるx:Class属性のクラス名もMainWindowに変更しておきます。
以上で下準備完了です。

Appクラスの変更

では、Appクラスを書き換えてみようと思います。
App.xamlには、StartupUri属性が指定されていると思います。さっき名前を変えたWindow1.xamlが書かれていると思うので、さくっと消してStartupイベントを登録します。

<Application x:Class="MefWpfHelloWorld.App"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Startup="Application_Startup">
    <Application.Resources>
         
    </Application.Resources>
</Application>

次にApp.xaml.csでMainWindowプロパティをnewで上書きしてMEFのImport属性をつけます。MainWindowという名前でExportされているWindowクラスを受け取るようにしています。そして、Startupイベントハンドラ内で、CompositionContainerを初期化します。初期化後に、自分自身をコンテナに登録して、MainWindowを表示しています。
このMainWindowはMEFで自動的に登録されたものになります。

// App.xaml.cs
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Diagnostics;
using System.Windows;

namespace MefWpfHelloWorld
{
    public partial class App : Application
    {
        // MainWindowという名前でExportされているものを設定する
        [Import("MainWindow")]
        public new Window MainWindow
        {
            get { return base.MainWindow; }
            set { base.MainWindow = value; }
        }

        private CompositionContainer _container;
        private void Application_Startup(object sender, StartupEventArgs e)
        {
            if (!ComposeContainer() || this.MainWindow == null)
            {
                // 失敗したら終わる
                this.Shutdown();
                return;
            }

            // 後始末処理を追加
            this.Exit += (s, evt) =>
                {
                    if (_container != null)
                    {
                        _container.Dispose();
                    }
                };
            this.MainWindow.Show();
        }

        private bool ComposeContainer()
        {
            try
            {
                // 今のアセンブリと、カレントディレクトリ上の
                // アセンブリをコンテナに読み込ませる
                var agg = new AggregateCatalog(
                    new AssemblyCatalog(typeof(App).Assembly),
                    new DirectoryCatalog("."));
                _container = new CompositionContainer(agg);
                // 自分自身を登録
                _container.ComposeParts(this);
                return true;
            }
            catch (CompositionException ex)
            {
                Debug.WriteLine(ex);
                // とりあえず失敗したらfalseで。
                // 実際はちゃんとエラーログでも吐こう。
                return false;
            }
        }
    }
}

MainWindowクラスの変更

最初に下準備で、名前変更をしたMainWindowクラスにも手をいれます。こいつはMainWindowという名前でWindow型としてExportしてやるだけでOKです。

using System.ComponentModel.Composition;
using System.Windows;

namespace MefWpfHelloWorld
{
    [Export("MainWindow", typeof(Window))]
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

とりあえず実行

実行してみると、恐らく真っ白な画面が実行されます。

ViewModelの作成

これでは、真っ白なだけなので、ViewModelクラスを作成して、Hello worldを画面に表示してみようと思います。MainWindowViweModelという名前でクラスを作成して、以下のように記述します。Exportしてる以外は、何の変哲もないクラスです。今回は、ViewModelからViewへのデータの変更の反映とかも無いので、INotifyPropertyChangedも実装していません。

using System.ComponentModel.Composition;

namespace MefWpfHelloWorld
{
    [Export]
    public class MainWindowViewModel
    {
        public string Message
        {
            get { return "Hello world"; }
        }
    }
}

ViewとViewModelをくっつける

今回は、MEFを使うと言うことなので、DataTemplateでViewModelとViewを繋ぐのではなく、MEFでViewにViewModelを注入します。

using System.ComponentModel.Composition;
using System.Windows;

namespace MefWpfHelloWorld
{
    [Export("MainWindow", typeof(Window))]
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        // DataContextをラップする感じでModelプロパティを定義する
        [Import]
        public MainWindowViewModel Model
        {
            get { return DataContext as MainWindowViewModel; }
            set { DataContext = value; }
        }
    }
}

そして、XAML側では、ViweModelのMessageプロパティをバインドします。

<Window x:Class="MefWpfHelloWorld.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="300" Width="300">
    <Grid>
        <TextBlock Text="{Binding Message}" />
    </Grid>
</Window>

実行

これで、実行すると以下のようにHello worldが表示されます。

以上っ!!Hello worldでした。