10日ほど前の2014年4月19日にPrism 5 for .NET4.5がリリースされてました。
Prismは、MSの中の人たちが作ってるOSSのWPF用(SL用やストアアプリ用などもある)のフレームワークで、複合型アプリケーション(モジュールを組み合わせて1つのアプリケーションに仕立て上げるもの)のサポートと、MVVMパターンのサポート機能が含まれています。
ライブラリの入手
サンプルプログラムなどを見るならMSのサイトからDLしてもいいですが、さくっと始めるだけならNuGetで入手するのが楽です。
Install-Package Prism
もちろんGUIからもOKです。ダウンロードする際に、結構細分化された状態でパッケージが入ってくるのがわかります。
- Prism.Composition
- 複合型アプリケーションを作成するための機能が含まれてます。一番複雑怪奇。
- Prism.Interactivity
- MVVMパターンでVMから、ユーザと対話するための機能を提供するクラス群が入ってます。簡単にいうと確認ダイアログとかが出せます。
- Prism.PubSubEvents
- モジュール間で疎結合にメッセージのやり取りを行うためのクラス群が入ってます。Medatorパターンですね。
- Prism.Mvvm
- MVVMをするための基本的なICommandの実装やINotifyPropertyChangedの実装の他にViewとViewModelを自動的に結び付ける機能を提供します。
- Prism
- 上記のNuGetパッケージをまとめるためのプレースホルダーです。
複合型アプリケーション
昔とかわりません。ムズイです。
MVVMサポート
結構シンプルに実装してます。そのぶんめんどいです。大体以下のクラスです。
- INotifyPropertyChangedの実装のBindableBaseクラス。
- ViewからViewModelの自動引き当てを行うViewModelLocatorProviderクラス。
- INotifyErrorInfoを実装するときに楽できるErrorsContainer
- ICommandの実装のDelegateCommand
遊んでみた
DataAnnotationでのエラーチェックをサポートしたViewModel作って遊んでみた。
using Microsoft.Practices.Prism.Mvvm; using Microsoft.Practices.Prism.ViewModel; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Runtime.CompilerServices; namespace WpfApplication7.ViewModels { /// <summary>/// PrismのBindableBaseを拡張してデータの検証を追加/// </summary>publicclass ViewModelBase : BindableBase, INotifyDataErrorInfo { // INotifyDataErrorInfo実装時に楽できるクラスprivatereadonly ErrorsContainer<string> Errors; publicevent EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; public ViewModelBase() { // ErrorsChangedを発行するメソッドをコンストラクタに渡してインスタンス化すると// SetErrorsなどのときに、いい感じにイベント出してくれるthis.Errors = new ErrorsContainer<string>(this.OnErrorsChanged); } // INotifyDataErrorInfoの実装。基本委譲するだけ。public IEnumerable GetErrors(string propertyName) { returnthis.Errors.GetErrors(propertyName); } publicbool HasErrors { get { returnthis.Errors.HasErrors; } } protectedvirtualvoid OnErrorsChanged([CallerMemberName]string propertyName = null) { var h = this.ErrorsChanged; if (h != null) { h(this, new DataErrorsChangedEventArgs(propertyName)); } } /// <summary>/// 値の設定と値の検証を行う/// </summary>protectedoverridebool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null) { var changed = base.SetProperty<T>(ref storage, value, propertyName); if (changed) { // DataAnnotationsでプロパティの検証 var context = new ValidationContext(this) { MemberName = propertyName }; List<ValidationResult> validationErrors = new List<ValidationResult>(); if (!Validator.TryValidateProperty(value, context, validationErrors)) { // エラーがあったらエラーを設定this.Errors.SetErrors( propertyName, validationErrors.Select(error => error.ErrorMessage).ToArray()); } else { // なければクリアthis.Errors.ClearErrors(propertyName); } } return changed; } publicbool ValidateObject() { // オブジェクト全体の検証を行う var context = new ValidationContext(this); List<ValidationResult> validationErrors = new List<ValidationResult>(); if (Validator.TryValidateObject(this, context, validationErrors)) { returntrue; } var errors = validationErrors .Where(error => error.MemberNames.Any()) .GroupBy(error => error.MemberNames.First()); foreach (var error in errors) { this.Errors.SetErrors( error.Key, error.Select(e => e.ErrorMessage).ToArray()); } returnfalse; } } }
そして、これを継承したViewModel
using Microsoft.Practices.Prism.Commands; using Microsoft.Practices.Prism.Interactivity.InteractionRequest; using System.ComponentModel.DataAnnotations; namespace WpfApplication7.ViewModels { publicclass MainWindowViewModel : ViewModelBase { privatestring input; /// <summary>/// 必須入力の項目/// </summary> [Required(ErrorMessage = "必須!")] publicstring Input { get { returnthis.input; } // 値が設定されたタイミングでコマンドの実行可否の確認set { this.SetProperty(refthis.input, value); this.AlertCommand.RaiseCanExecuteChanged(); } } private InteractionRequest<Notification> alertRequest = new InteractionRequest<Notification>(); /// <summary>/// Viewへ通知を行うためのInteractionRequest/// </summary>public InteractionRequest<Notification> AlertRequest { get { returnthis.alertRequest; } set { this.SetProperty(refthis.alertRequest, value); } } private DelegateCommand alertCommand; /// <summary>/// 適当にコマンド/// </summary>public DelegateCommand AlertCommand { get { returnthis.alertCommand ?? (this.alertCommand = new DelegateCommand(this.AlertExecute, this.CanAlertExecute)); } } publicvoid AlertExecute() { // エラーがなければif (!this.ValidateObject()) { return; } // Viewに投げるthis.AlertRequest.Raise(new Notification { Title = "お知らせ", Content = this.HasErrors ? "エラー" : "オッケー" }); } publicbool CanAlertExecute() { return !this.HasErrors; } } }
んでViewのコードビハインドです。
using Microsoft.Practices.Prism.Mvvm; using System.Windows; namespace WpfApplication7.Views { /// <summary>/// IViewを実装してViewModelLocatorProviderでViewModelを自動で設定/// </summary>publicpartialclass MainWindow : Window, IView { public MainWindow() { InitializeComponent(); ViewModelLocationProvider.AutoWireViewModelChanged(this); } } }
ViewからViewModelを設定する方法はXAMLでViewModelLocatorを使う方法があるけど、それだとXAMLエディタがエラーをはくので個人的にはコードビハインドでViewModelLocationProviderを使うほうが好み。
そしてXAML。
<Windowxmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:mvvm="clr-namespace:Microsoft.Practices.Prism.Mvvm;assembly=Microsoft.Practices.Prism.Mvvm.Desktop"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:ViewModels="clr-namespace:WpfApplication7.ViewModels"xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"xmlns:Custom="http://www.codeplex.com/prism"xmlns:DefaultPopupWindows="clr-namespace:Microsoft.Practices.Prism.Interactivity.DefaultPopupWindows;assembly=Microsoft.Practices.Prism.Interactivity"mc:Ignorable="d"x:Class="WpfApplication7.Views.MainWindow"Title="MainView"Height="300"Width="300"d:DataContext="{d:DesignInstance {x:Type ViewModels:MainWindowViewModel}, IsDesignTimeCreatable=True}"><i:Interaction.Triggers><!-- AlertRequestからの要求を受けるTrigger --><Custom:InteractionRequestTrigger SourceObject="{Binding AlertRequest}"><!-- 地味にPrism 5からWindow表示アクションがある --><Custom:PopupWindowAction /></Custom:InteractionRequestTrigger></i:Interaction.Triggers><Grid><!-- VMの入力項目にバインド --><TextBox x:Name="textBox"HorizontalAlignment="Left"Height="23"Margin="10,10,0,0"TextWrapping="Wrap"Text="{Binding Input, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"VerticalAlignment="Top"Width="120"/><!-- TextBoxのエラーの内容を表示する --><TextBlock HorizontalAlignment="Left"Margin="135,18,0,0"TextWrapping="Wrap"Text="{Binding (Validation.Errors)[0].ErrorContent, ElementName=textBox, Mode=OneWay}"VerticalAlignment="Top"/><!-- コマンドと接続するためのボタン --><Button Content="Button"HorizontalAlignment="Left"Margin="10,38,0,0"VerticalAlignment="Top"Width="75"Command="{Binding AlertCommand, Mode=OneWay}"/></Grid></Window>
実行するとこんな感じ。
何もせずボタンお押すとブロックするよ。これはCommandでValidateObjectを呼んでるおかげ。
何か入力するとボタンが押せるようになる。押したらデフォルトのウィンドウが出てくる。
所感
軽いMVVMフレームワークとして、PrismのMVVM部分だけ参照して使うのはありかなと思いました。(NuGetでもパッケージが細かくわかれてるので) 複合アプリ作りたい人はフルパッケージでどうぞ!