C#でプラグイン機能を開発に挑戦してみた(5)
メニューへのプラグインの表示処理
どもです。
…スミマセンデシタ!
…いや、謝ることではないですね。
0. いきなりどうした!?
前回のエントリと前々回のエントリで、あまり適当ではないコードを紹介してしまいました!
具体的には、プラグインの一覧をメニューに表示するためのコードです。
プラグインの一覧をメニューに表示するために、ViewModelからViewに対してイベントを発行し、イベント引数でメニューで表示したいプラグインの名前をコードビハインドで設定する、という実装を紹介しました。
しかしこの機能は、バインディングを適用することで、もっと簡単に実装ができます。
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. メニューへのデータバインディング
MenuItemには、「ItemsSource」プロパティがあります。
このプロパティの詳細はMSDNに譲り、ここでは目的に対する実装に着目します。
このプロパティに対して、プラグインの一覧を設定します。
具体的には、以下のようなコードになります。
まずはC#のViewModelのプロパティの実装です。
public class ViewModel
{
protected ObservableCollection<PluginInfo> _defaultPlugins;
public ObservableCollection<PluginInfo> DefaultPlugins
{
get => this._defaultPlugins;
protected set
{
this._defaultPlugins = value;
this.RaisePropertyChanged(nameof(DefaultPlugins));
}
}
}
ウィンドウのXAML、メニューアイテムは次のように実装します。
<MenuItem Header="生成"
x:Name="RunMenuRoot"
Style="{StaticResource MenuItem_MenuBar}"
>
<MenuItem Header="スタブ・ドライバ"
IsTabStop="True"
ItemsSource="{Binding DefaultPlugins}"
/>
/>
しかしながらこの実装では、プラグインの名前がメニューに表示されません。
また、メニューのアイテムが選択された際に実行する処理と、その処理に渡す引数情報が設定できていません。
2.1. プラグイン名の表示
ItemsSourceを設定したメニューに「名前」を表示するためには、「ItemContainerStyle」を使用します。
具体的には、XAMLを以下のように実装します。
<MenuItem Header="生成"
x:Name="RunMenuRoot"
Style="{StaticResource MenuItem_MenuBar}"
>
<MenuItem Header="スタブ・ドライバ"
IsTabStop="True"
ItemsSource="{Binding DefaultPlugins}"
>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}"/>
</Style>
</MenuItem.ItemContainerStyle>l
<MenuItem>
/>
このコードでは、「Header」プロパティに対して「Name」をバインドするように設定しています。
「Name」プロパティには、メニューに表示するプラグインの名前が格納されています。
これにより、「MenuItem」にバインディングされたリスト内の要素の「PluginInfo」オブジェクトの「Name」プロパティに設定された値がメニューに表示されます。
結果として、プラグインの名前がメニューに表示されるようになります。
2.2. プラグインの実行処理
メニューのアイテムが選択された際に実行される処理は、「ItemContainerStyle」で「Command」プロパティに値を設定します。
また「CommandProperty」プロパティに、処理に対して渡す引数を設定します。
具体的なコードは、次になります。
<MenuItem Header="生成"
x:Name="RunMenuRoot"
Style="{StaticResource MenuItem_MenuBar}"
>
<MenuItem Header="スタブ・ドライバ"
IsTabStop="True"
ItemsSource="{Binding DefaultPlugins}"
>
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Path=Name}"/>
<Setter Property="Command" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=MenuItem}, Path=DataContext.DefaultPluginCommand}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>l
<MenuItem>
/>
「Command」プロパティについては、DataContext即ちViewModelのCommandプロパティを設定します。
「CommandProperty」には、単純に「{Binding}」とだけ設定します。
これにより、メニューに対応する「PluginInfo」オブジェクトをコマンドの引数に設定ができます。
前回のエントリ紹介したコードでは、選択されたメニューのPluginInfoのIDを渡していました。
これに対して、上述のコードではPluginInfoオブジェクトそのものを渡すことができます。
結果としてViewModel側での「ID」からプラグインの情報を取得して…という処理を省略できます。
3. まとめ
今回は、MenuItemのItemsSourceにBindingを設定することで、動的にメニューの表示を追加する処理について、「よりよい実装」を提案してみました。(言い訳)
前回のエントリと前々回のエントリで紹介したコードは、勿論「間違いではない」と考えています。(言い訳)
しかしながら今回紹介した方法は、それよりもよい方法であると考えています。(言い訳ではない!)
実装する内容などによって「適当な実装」は変化すると思いますので、適宜選択することが肝要です。
一つ事に囚われすぎず、常に「最適」「適当」を目指すようにしたいです。
これまでのエントリが、そのための選択肢の一助になれば幸いです。
ではっ!
ディスカッション
コメント一覧
まだ、コメントがありません