WPF は .NET Framework アプリですし、Managed Extensibility Framework (MEF) を活用したプラグインに対応したアプリを作れるのも強みのひとつです。
@takeshik 氏が KanColleViewer に MEF によるプラグイン機構を実装してくださいました。
で、WPF アプリでプラグインとなると、プラグイン側の設定画面等々の画面をプラグイン側で用意し、かつその画面にテーマを反映させたいものですね。
MEF のサンプルそのものは MSDN 等でよく見かけるものの、WPF アプリで、かつ拡張側 (プラグイン側) で画面を用意した軽量なサンプルはなかなか見つけられませんでした (節穴 eye という可能性)。
ということで、自分で作りました。
ダウンロードはこちらから。
http://code.msdn.microsoft.com/Creating-a-WPF-Visual-3eb4e5dc
/* 英語化は @guitarrapc_tech 先生がやってくださいました。超かっこいい。ありがとうございました。 */
概要
簡単な WPF アプリと 2 つのプラグインを用意し、各プラグインには設定画面を実装させます。
WPF アプリから各プラグインの設定画面を呼び出せるようにし、その設定によってプラグインの動作が変わるようにしました。
プロジェクト構成
VisualPlugin.UI
WPF アプリ本体。プラグインをロードし、各プラグインの情報の表示と設定画面の呼び出しをサポートします。
VisualPlugin.Interfaces
プラグインのインターフェイス。
サンプルとして、以下のメンバーを持つインターフェイスを公開しています。
1 2 3 4 5 6 7 8 9 10 11 |
public interface IVisualPlugin : INotifyPropertyChanged { // プラグインの名称。 string Name { get; } // プラグイン固有の処理。 void Proc(); // プラグインの設定画面を返すメソッド。 object GetSettingsView(); } |
VisualPlugin.Visuals
UI テーマ (共通で使用する色やフォント等) を定義するためのプロジェクトです。
WPF アプリ本体と各プラグインはこれを参照し、統一された外観を作れるようにします。
MetroRadiance や MahApps.Metro 等の UI ライブラリを使ったアプリ開発の場合は、このプロジェクトは必要ないです。
VisualPlugin.Sample1 / Sample2
プラグインのサンプル。
Sample 1 は、Proc() でメッセージボックスを表示します。
Sample 2 は、Proc() で別ウィンドウを表示し、設定されたパスの画像を表示します。
解説
MEF そのものについては他の資料をご覧いただくとして、プラグイン側で画面を返す部分について。
プラグイン側
設定画面そのものは、Sample1 / Sample2 プロジェクト内でユーザー コントロールとして定義しています。
ユーザーコントロールでは、アプリ本体 (VisualPlugin.UI) と同様の外観とするため、VisualPlugin.Visuals にある各リソースをマージしています。
1 2 3 4 5 6 7 8 9 |
そして、プラグイン本体の実装は以下。
1 2 3 4 5 6 7 |
public object GetSettingsView() { return new Settings { DataContext = this }; } |
設定画面のユーザー コントロールを生成し、返します。
呼び出し側 (つまり、WPF アプリ本体) の任意のタイミング作らせた方がスレッド等々の都合がよいので、このような形にしました。
そして、生成するユーザー コントロールの DataContext に自身のインスタンス (もしくは、ViewModel を噛まして) 与えてやりましょう。
WPF アプリ本体にロードされているインスタンスの動作をリアルタイムに変更することが可能です。
WPF アプリ本体側
まず、ViewModel でプロパティとして設定画面を公開しました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
public class PluginViewModel : ViewModel { private object _settingsView; public IVisualPlugin Plugin { get; private set; } public object SettingsView { get { return this._settingsView ?? (this._settingsView = this.Plugin.GetSettingsView()); } } public PluginViewModel(IVisualPlugin plugin) { this.Plugin = plugin; } } |
SettingsView がそのプロパティ。
余談ですが、null 合体演算子を使ったこの書き方、割と好きです。
画面では ContentPresenter を使用して、SettingsView の内容を表示します。
1 |
ContentPresenter は、Content プロパティや ContentTemplate プロパティの内容によって、表示する内容を決定します。
(そのロジックは MSDN の解説を参照)
プラグイン側の GetSettingsView() がユーザー コントロール (UIElement) を返せば、それがそのまま表示されます。
実行結果
まず、WPF 本体がロードしたプラグインを一覧で出します。
(クリックで拡大)
そして、[Settings] ボタンで設定画面を表示。
たとえば、次のような UserControl は…
こんな感じで表示されます。Style もばっちり。
また、プラグインの設定画面で変更した内容は、そのプラグインをロードしている WPF アプリ側に即時に反映されます。
サンプルのダウンロード
冒頭にも示しましたが、こちらからどうぞ。
http://code.msdn.microsoft.com/Creating-a-WPF-Visual-3eb4e5dc
KanColleViewer にも早いところ乗せてみたい。