はじめに
C# + WPF + MEFを使ってアプリにプラグインを実装してみる その1 の続きです。
今回は、UserControlをプラグインとして分離したいと思います。
GitHub : https://github.com/isuzu-shiranui/CalculationApplication2
読み込み側
View (MainWindow)
外観
コード
Views/MainWindow.xaml
<Window x:Class="CalculationApplication2.Views.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CalculationApplication2.Views"
xmlns:vm="clr-namespace:CalculationApplication2.ViewModels"
mc:Ignorable="d"
Title="MainWindow" Height="326" Width="591">
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
<Grid>
<UserControl Content="{Binding Plugin}" />
</Grid>
</Window>
Views/MainWindow.xaml.cs
using System.Windows;
namespace CalculationApplication2.Views
{
/// <summary>
/// MainWindow.xaml の相互作用ロジック
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
}
}
ViewModel (MainWindowViewModel)
ViewModels/MainWindowViewModel
using Plugin;
using System;
using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.IO;
namespace CalculationApplication2.ViewModels
{
public class MainWindowViewModel
{
[Import(typeof(IPlugin))]
public IPlugin Plugin { get; set; }
public MainWindowViewModel()
{
LoadPlugins();
}
/// <summary>
/// プラグインを全て読みこんで、[Import]がついているプロパティに格納する。
/// </summary>
private void LoadPlugins()
{
//フォルダがなければ作る。
string pluginsPath = Directory.GetCurrentDirectory() + @"\plugins";
if (!Directory.Exists(pluginsPath)) Directory.CreateDirectory(pluginsPath);
//プラグイン読み込み
using (var catalog = new DirectoryCatalog(pluginsPath, "SummationPlugin.dll"))
using (var container = new CompositionContainer(catalog))
{
if (catalog.LoadedFiles.Count > 0) container.SatisfyImportsOnce(this);
}
}
}
}
##App.xaml
StartupUri="MainWindow.xaml"
→ StartupUri="Views/MainWindow.xaml"
に変更
プラグイン用インターフェース作成
IPlugin
namespace Plugin
{
public interface IPlugin
{
}
}
UserControlプラグイン作成
View (SummationView)
外観
前回の読み込み側のViewをそのままUserControlに移しただけです(;・∀・)
コード
Views/SummationView.xaml
<UserControl x:Class="SummationPlugin.Views.SummationView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:vm="clr-namespace:SummationPlugin.ViewModels"
xmlns:local="clr-namespace:SummationPlugin"
mc:Ignorable="d"
d:DesignHeight="300" Width="522">
<UserControl.DataContext>
<vm:SummationViewModel />
</UserControl.DataContext>
<Grid Background="White">
<StackPanel HorizontalAlignment="Left" Height="24" Margin="10,113,0,0" VerticalAlignment="Top" Width="499" Orientation="Horizontal">
<Label Content="左辺" />
<TextBox Width="212" Text="{Binding LeftValue, Mode=TwoWay}"/>
<Label Content="左辺" />
<TextBox Width="212" Text="{Binding RightValue, Mode=TwoWay}"/>
</StackPanel>
<Button Content="実行" HorizontalAlignment="Left" Margin="425,239,0,0" VerticalAlignment="Top" Width="75" Command="{Binding CalculationCommand, UpdateSourceTrigger=PropertyChanged}"/>
<TextBox x:Name="textBox" HorizontalAlignment="Left" Height="23" Margin="44,172,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="211" Text="{Binding ResultValue}"/>
<Label x:Name="label" Content="結果" HorizontalAlignment="Left" Margin="10,172,0,0" VerticalAlignment="Top"/>
</Grid>
</UserControl>
Views/SummationView.xaml.cs
using Plugin;
using System.ComponentModel.Composition;
using System.Windows.Controls;
namespace SummationPlugin.Views
{
/// <summary>
/// SummationView.xaml の相互作用ロジック
/// </summary>
[Export(typeof(IPlugin))]
public partial class SummationView : UserControl, IPlugin
{
public SummationView()
{
InitializeComponent();
}
}
}
ViewModel (SummationViewModel)
ViewModels/SummationViewModel.cs
using Prism.Commands;
using System.ComponentModel.DataAnnotations;
namespace SummationPlugin.ViewModels
{
class SummationViewModel : Commons.ViewModelBase
{
private double leftValue;
[Required]
[Range(0, double.MaxValue)]
public double LeftValue
{
get { return leftValue; }
set { SetProperty(ref leftValue, value); CalculationCommand.RaiseCanExecuteChanged(); }
}
private double rightValue;
[Required]
[Range(0, double.MaxValue)]
public double RightValue
{
get { return rightValue; }
set { SetProperty(ref rightValue, value); CalculationCommand.RaiseCanExecuteChanged(); }
}
private double resultValue;
public double ResultValue
{
get { return resultValue; }
set { SetProperty(ref resultValue, value); }
}
private DelegateCommand calculationCommand;
public DelegateCommand CalculationCommand => calculationCommand ?? (new DelegateCommand(CalculationExecute, () => !HasErrors));
private void CalculationExecute()
{
ResultValue = LeftValue + RightValue;
}
}
}
ViewModelBase
前回と一緒...のはず
using Prism.Mvvm;
using System;
using System.Runtime.CompilerServices;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace SummationPlugin.Commons
{
public class ViewModelBase : BindableBase, INotifyDataErrorInfo
{
private ErrorsContainer<string> errorsContainer;
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public ViewModelBase()
{
errorsContainer = new ErrorsContainer<string>(propertyName => ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName)));
}
public bool HasErrors => errorsContainer.HasErrors;
public IEnumerable GetErrors(string propertyName)
{
return errorsContainer.GetErrors(propertyName);
}
protected override bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
{
if(!base.SetProperty(ref storage, value, propertyName)) return false;
var context = new ValidationContext(this) { MemberName = propertyName };
var errors = new List<ValidationResult>();
if(!Validator.TryValidateProperty(value, context, errors))
{
errorsContainer.SetErrors(propertyName, errors.Select(error => error.ErrorMessage).ToArray());
}
else
{
errorsContainer.ClearErrors(propertyName);
}
return true;
}
}
}
動かしてみる
プラグインを配置する
まずは作ったプラグインをビルドします。
すると、プラグインのDebugまたはReleaseフォルダに、(プラグイン名).dllができていると思います。
これを、読み込みアプリのDebugまたはReleaseフォルダにpluginフォルダがなければ作って貼り付けます。
今回は、プラグイン側にもPrismを使っているので、Prism.dllとPrism.Wpf.dllも追加します。
読み込み側アプリの動作確認
実際にアプリを起動して、計算が行われれば完成です。見た目上は前回と同じ動きになります。
さいごに
見てもらって分かったと思いますが、基本的には前回と変わりません。
次は複数プラグインへの対応をやっていこうかなと思ってます。
冒頭にも乗っけてますが、GitHubにありますので試してみてください。
GitHub : https://github.com/isuzu-shiranui/CalculationApplication2