C#でプラグイン機能を開発に挑戦してみた(4)
クリックしたメニューに対応するプラグインを実行してみる

どもです。

前回のエントリで、データベースに登録されたプラグインの名前を、メニューに追加/表示してみました。
今回は、次のステップとして、メニューをクリックして実際にデータベースに登録されたプラグインを実行する方法について検討したので、その結果について書きます。

1. 開発環境

毎度ですが、開発環境です。

開発/実行環境
項目 内容
OS Windows10 Pro(1909)
CPU i7-8700
メモリ 16GB
IDE Visual Studio Community 2019
Version 16.11.2
言語 C#/WPF
.NET .NET Framework 4.7
データベース LiteDB Ver.5.0.11

2. メニューへのプラグインの表示

前回のエントリでは、画面上のボタンを押下した際に、プラグインの名前を表示しました。
今回は、実際の動作に近づけるために、アプリケーション起動時にプラグインを読み込み、名前をメニューに追加します。

2.1. シーケンス

具体的には、以下のような手順(シーケンス)で実行します。



上図のシーケンスの中の「NotifyPluginItem」で、データベースから取得したプラグイン情報をウィンドウ(View)に通知しています。
あとは、通知を受け取ったView側で、前回のエントリで紹介した方法(実装)でメニューにプラグインを追加します。

2.2. コマンドの追加

メニューにプラグインを追加しましたが、コレだけではプラグインは実行ができません。
プラグインが選択(クリック)された際に実行するコマンドの設定が必要です。
通常のWPF/XAMLで設定する場合には、以下のようなコードでバインディングを設定します。

<MenuItem Command="{Binding MenuExecuteCommand}"/>

しかし今は、動的にメニューを生成しています。
そのため、XAML形式での記述ができません。
ではどうするか?
とても簡単です。
MenuItemオブジェクトを(動的に)追加する処理の中で、「Command」属性も一緒に設定します。
具体的なコードは、以下の通りです。

public partial class MainWindow : Window
{
	private void LoadMenuItem(IEnumerable plugins)
	{
		MenuItem rootMenuItem = (MenuItem)this.FindName("PluginRoot");
		rootMenuItem.Items.Clear();
		foreach (var pluginItem in plugins)
		{
			var menuItem = new MenuItem()
			{
				Header = pluginItem.Title,
				Command = ((PluginSampleGuiViewModel)this.DataContext).MenuExecuteCommand,
				CommandParameter = pluginItem.Id
			};
			rootMenuItem.Items.Add(menuItem);
		}
	}
}

コードの基本的な部分は、前回のエントリで紹介した内容と同じです。
そのため、その部分については説明を省略します。
「Command」プロパティに値を設定する処理ですが、DataContextをViewModelにキャスト、そこからメニューで実行すべき関数(コマンド)を取得して設定します。
当然ですが、ViewのDataContextに、ViewModelオブジェクトが設定されていることが大前提です。
また上述のコードでは、CommandParameterプロパティに値を設定しています。
これは、View上で選択されたプラグインの情報をViewModel側で判定するための値です。
今回は「Id」を設定していますが、別の値でも「選択されたプラグインを特定できれば」どのような値でも問題ないと考えています。

3. メニューに表示されたプラグインの実行処理

プラグインを実行する準備ができたので、次のステップとして、プラグインを実行してみます。
プラグインの選択から実行までは、以下のようなシーケンスで実現できます。



…前出のシーケンス図と比較して、かなり細かく書いていますが…ご容赦ください。
このシーケンスの通りに実装したコードは、以下になります。

public class PluginSampleGuiViewModel : ViewModelBase
{
	public void MenuExecuteCommandExecute(int id)
	{
		PluginManager pluginManager = new PluginManager();
		IPlugin plugin = pluginManager.Load(id);
		PluginOutput output = plugin.PluginFunction("message");

		EventArgs eventArgs = new NotifyMessageEventArgs(output.message);
		this.NotifyMessageEvent?.Invoke(this, eventArgs);
	}
}

4. 実行してみよう

プラグインの表示から実行までの処理ができたところで、実際に動作させてみます。
起動直後に、メニューを選択すると、「PluginA」と「PluginB」が表示されています。



これにより、正常にデータベースからプラグイン情報を取得、表示できていることが確認できます。
次に、実際に「PluginA」を実行します。
結果として、期待するメッセージを示すダイアログが表示されます。



同様にメニューから「PluginB」を選択すると、PluginBが実行されたことを示す文字列が表示されます。



このように、データベースからプラグイン情報を取得、メニューに表示できること、およびメニューを選択することで対応するプラグインが実行できています!

5. まとめ

今回は、メニューからデータベースに登録されたプラグインを実行する方法について検討してみました。
画面ロード時に、メニューに対して、プラグインの名前に加えてプラグインを特定する情報(今回は、ID)とプラグインを実行するコマンドをバインディングする方法を試しました。
その結果、メニューを選択すると、対応するプラグインの処理を実行できることが分かりました。

今回のエントリでは、メニュー(MenuItem)のCommand/CommandParameterプロパティに対してコマンドと引数(パラメータ)を設定しています。
MenuItemに対しては、色々な値をバインディングを設定できます。
工夫次第では、もっと違うことができるかと思います。
でも。
ここまでで、エントリの目的は達成ができています。
そのため、今回はここまでにしておきます。

ではっ!

ex. 公開しています

今回のエントリで紹介したアプリケーションのコードは、全てGitHubにて公開しています。
エントリ中で紹介したのは、全体のほんの一部です。
コードの全体については、コチラを参考にしてください。