過去の関連記事
[C#][MEF]Managed Extensibility Framework入門 その1
[C#][MEF]Managed Extensibility Framework入門 その2
[C#][MEF]Managed Extensibility Framework入門 その3
[C#][MEF]Managed Extensibility Framework入門 その4
[C#][MEF]Managed Extensibility Framework入門 その5
[C#][MEF]Managed Extensibility Framework入門 その6
[C#][MEF]Managed Extensibility Framework入門 その7
前回に引き続き、Catalog関連の部分をやってみようと思います。
今回のターゲットはDirectoryCatalogです。
こいつは、Directory内にあるアセンブリを自動で読み取って登録してくれるものです。
ということで、いつもどおりのコンソールアプリケーションにも飽きてきたので、WPFアプリケーションで実験してみようと思います。
WpfMEFという名前でWPFアプリケーションを作成します。
そして、Window1にExport属性をつけてコンテナにExportします。
[Export]
public partial class Window1 : Window
{
// 省略
}
そして、App.xamlのStartupUri属性を消してStartupイベントを作り、そこに以下のようなコンテナの初期化コードと、Window1を表示する処理を書きます。
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Windows;
namespace WpfMEF
{
public partial class App : Application
{
private void Application_Startup(object sender, StartupEventArgs e)
{
// カレントディレクトリにあるアセンブリを対象にするDirectoryCatalogを作成
var dirCatalog = new DirectoryCatalog(".");
// 自アセンブリと↑をまとめたカタログを作成
var catalog = new AggregateCatalog(
new AssemblyCatalog(typeof(App).Assembly),
dirCatalog);
// コンテナを作成してDirectoryCatalogを明示的にコンテナに追加
var con = new CompositionContainer(catalog);
var batch = new CompositionBatch();
batch.AddExportedObject<DirectoryCatalog>(dirCatalog);
con.Compose(batch);
// Window1を表示
var window = con.GetExportedObject<Window1>();
window.Show();
}
}
}
今回のキモは、DirectoryCatalogをあえてコンテナに登録しているところです。
こうすることで、DirectoryCatalogをImportできるようになります。
次に、Window1.xamlを編集していきます。Window1.xamlは、プラグインをホストするためのItemsControlと、プラグインの再読み込みを行うRefreshボタンがあるだけのシンプル構造にしました。
<Window x:Class="WpfMEF.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Loaded="Window_Loaded"
Title="Window1">
<DockPanel>
<Button DockPanel.Dock="Top" Content="Refresh" Click="Button_Click" />
<ItemsControl ItemsSource="{Binding Plugins}"/>
</DockPanel>
</Window>
Window1のイベントとしては、Loadedイベントと、Refreshボタンのクリックイベントを登録しています。Loadedイベントとクリックイベントでは、プラグインの再読み込みを行っています。
コードとしては、こんな感じです。
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.Windows;
namespace WpfMEF
{
[Export]
public partial class Window1 : Window
{
// プラグイン
[Import(AllowRecomposition=true)]
public ExportCollection<FrameworkElement> ExportedFrameworkElements { get; set; }
// カタログ
[Import]
public DirectoryCatalog Catalog { get; set; }
// ここにプラグインに定義されたFrameworkElementが入る.
// XAML側のItemsControlとバインドされているので、ここに入れたFrameworkElementが画面に
// 表示される。
public ObservableCollection<FrameworkElement> Plugins { get; private set; }
// プラグインの初期化・再読み込みを行う
private void LoadPlugins()
{
// 初回のときはコレクションを作成する
if (Plugins == null)
{
this.Plugins = new ObservableCollection<FrameworkElement>();
}
// 再読み込み開始
Catalog.Refresh();
Plugins.Clear();
// 新たなプラグインをコレクションに追加
foreach(var plugin in ExportedFrameworkElements)
{
Plugins.Add(plugin.GetExportedObject());
}
}
public Window1()
{
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e)
{
LoadPlugins();
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
LoadPlugins();
// とりあえずコードビハインドがViewModel役
DataContext = this;
}
}
}
これで、プラグインをホストする側は完成です。
実行すると、ボタンがあるだけの悲しい画面が起動するのが確認できます。
プラグインを作成
プラグインのプロジェクトを2つ作成します。WpfPlugin1とWpfPlugin2という名前でWPFのユーザコントロールライブラリを作成します。System.ComponentModel.Composition.dllへの参照を各々追加しておきます。
それぞれ、非常にシンプルなユーザコントロールを1つずつ定義しておきます。
Plugin1のほうのユーザコントロール
<UserControl x:Class="WpfPlugin1.Plugin1Control"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="Plugin1" />
</Grid>
</UserControl>
Plugin2のほうのユーザコントロール
<UserControl x:Class="WpfPlugin2.UserControl1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock Text="Plugin2" />
</Grid>
</UserControl>
各々のコードビハインドで、クラスにExport(typeof(FrameworkElement))属性をつけます。
こうすることで、プラグインをホストする側のExportCollection<FrameworkElement>にImportされるようになります。
Plugin1のほうのコードビハインド
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
namespace WpfPlugin1
{
[Export(typeof(FrameworkElement))]
public partial class Plugin1Control : UserControl
{
public Plugin1Control()
{
InitializeComponent();
}
}
}
Plugin2のほうのコードビハインド
using System.ComponentModel.Composition;
using System.Windows;
using System.Windows.Controls;
namespace WpfPlugin2
{
[Export(typeof(FrameworkElement))]
public partial class UserControl1 : UserControl
{
public UserControl1()
{
InitializeComponent();
}
}
}
それでは、実行して動きを見ていきます。
WpfMEFを起動してボタン1つだけの画面が起動することを確認します。
アプリケーションをいったん終了して、WpfMEFのbin\Debugフォルダに、WpfPlugin1.dllをコピーして起動します。
すると、画面にPlugin1が表示されます。
続いて、起動したままの状態でWpfPlugin2.dllをWpfEMFのbin\DebugフォルダにコピーしてRefreshボタンを押すと…
Plugin2が追加されました!
ということで、DirectoryCatalogを使うと、プラグインみたいな機能が割りと簡単に作れるということでした。