Silverlight Prism フレームワークを利用してMVVMアプリケーションを作成してみる 第2回 MVVMパターンの作成
MVVM QuickStart を参考にしつつ、アプリケーションに、Prism フレームワークを適用させていく。
Silverlight Prism フレームワークを利用してMVVMアプリケーションを作成してみる 第1回 で、モジュールの分割と、UI合成の動作を確認するところまでできたので、今回は、MVVMパターンと各クラスの責務を再確認したのち、データの参照をするところまでを実装してみる。
クラスの責任と特徴
Model-View-ViewModel(MVVM) パターンは、ビジネスおよびプレゼンテーションロジックをユーザーインターフェースから明確に分離する助けになる。
MVVMパターンでは、3つのクラスに責務を分離する。
- View : UIおよびUIロジックをカプセル化
- View Model : プレゼンテーションロジックと状態をカプセル化
- Model : ビジネスロジックとデータをカプセル化
View は View Model と データバインディング、コマンド、通知イベントを通して影響し合う。View Modelは、問い合わせ、監視し、調整、更新をモデルに行う。変換、妥当性検査、データの凝集は必要に応じ、Viewで行う。
MVVM クラスと相互作用
View クラス
Viewの責務は、ユーザーへスクリーン上の構造と見え方を定義すること。理想的には、コードビハインドでは、コンストラクタでInitializeComponent メソッドのみを呼び出すようにする。いくつかの場合、コードビハインドは、XAMLでは表現が困難な振る舞いをUIロジックとして含む。
Viewのデータバインディング式は、データコンテキストについて評価される。MVVMでは、Viewのデータコンテキストには、View Modelが設定される。View Model は、Viewがバインドや通知できるプロパティとコマンドが実装される。通常、一対一の関連がViewとView Modelに設定される。
通常、View は、Control もしくは UserControl の派生クラス。しかしながら、いくつかの場合、View は、データテンプレートとして表現される。
View Model クラス
MVVMにて、View Model は、Viewのためのプレゼンテーションロジックおよびデータをカプセル化する。View Model は、直接 Viewへの参照は持たないし、View の実装や型についても何も知らない。View Model は、プロパティやコマンドを、Viewとのデータバインドやイベントを通じた状態の変更通知を可能にするために実装する。
View Model は、View と Model との相互作用の調整に責任を持つ。一般的にこれは、一対多の関係となる。
Model クラス
MVVMにて、Model は、ビジネスロジックとデータをカプセル化する。通常、Modelは、アプリケーションのクライアント側ドメインモデルを表現する。Model は、データアクセスとキャッシングのサポートコードを含む。
通常、Model は、View とのバインドが容易になる機能を実装する。これは、通常、プロパティやコレクションの変更が、INotifyPropertyChanged、INotifyCollectionChanged インターフェースを通して通知されることを意味する。Model クラスは、オブジェクトのコレクションを、ObservableCollection<T> で表現する。これは、INotifyCollectionChanged インターフェースを実装している。
Model は、データの妥当性検査やエラー報告を、IDataErrorInfo( もしくは INotifyDataErrorInfo) インターフェースでサポートする。
Model-View-ViewModel の作成
と、MVVMにおける、各クラスの責務を概観したところで、今回、プロジェクト工数見積のクライアントをSilverlightで作成することを題材としている、対象「プロジェクト」の部分を参照する部分を実装していきたいと思う。
題材は、システム開発工数をファンクションポイント法 を使って見積もるというもので、GAEを利用して作ってある
Model の作成
まずは、工数算出対象である、Project モデルを作成。
namespace ProjectModule.Models { public class Project { public string Key { get; set; } public string Owner { get; set; } public string SystemName { get; set; } public string ApplicationName { get; set; } public string MesurementType { get; set; } } }
そして、Project のコレクションを表現するために、Projecs クラスを作成する。
using System.Collections.ObjectModel; namespace ProjectModule.Models { public class Projects : ObservableCollection{ } }
データサービスの作成
Project データを取得するデータサービスを作成。現時点では、ダミーデータを返すようにしておく。
インターフェース
using ProjectModule.Models; namespace ProjectModule.Services { public interface IProjectDataService { Projects GetProjects(); } }
ダミーデータを返す実装
DIコンテナに認識させるために、Export アトリビュートを設定しておく。ここでは、まだ、ダミーデータを返すようにしておく。Webサービスとの連携は別途実装する。
using System.ComponentModel.Composition; using ProjectModule.Models; namespace ProjectModule.Services { [Export(typeof(IProjectDataService))] public class ProjectDataService : IProjectDataService { private Projects projects; public Projects GetProjects() { if (this.projects == null) { this.projects = new Projects { new Project(){ Key = "001", SystemName = "Sys1", ApplicationName="Test App1" }, : }; } return this.projects; } } }
ViewModelの作成
INotifyChanged を実装し、ImportingConstructor アトリビュートを指定することで、コンストラクタインジェクションを行い、上記で作成した、データサービスをインジェクションする。
また、ViewModel 自体も、View にインジェクションするために、Export アトリビュートを付与し、DIコンテナに登録されるようにしておく。
using System; using System.ComponentModel; using System.ComponentModel.Composition; using System.Windows.Data; using Microsoft.Practices.Prism.Events; using ProjectModule.Events; using ProjectModule.Models; using ProjectModule.Services; namespace ProjectModule.ViewModels { [Export] public class ProjectListViewModel : INotifyPropertyChanged { private readonly IEventAggregator eventAggregator; public ICollectionView Projects { get; private set; } [ImportingConstructor] public ProjectListViewModel(IProjectDataService dataService, IEventAggregator eventAggregator) { if (dataService == null) throw new ArgumentNullException("dataService"); if (eventAggregator == null) throw new ArgumentNullException("eventAggregator"); this.eventAggregator = eventAggregator; this.Projects = new PagedCollectionView(dataService.GetProjects()); this.Projects.CurrentChanged += new EventHandler(this.SelectedProjectChanged); } private void SelectedProjectChanged(object sender, EventArgs e) { Project project = this.Projects.CurrentItem as Project; if (project != null) { this.eventAggregator.GetEvent<ProjectSelectedEvent>().Publish(project.Key); } } #region INotifyPropertyChanged Members public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } }
Viewの作成
前回 Hello Word としていた、ProjectList を ProjectListView に リネームして、コントロールを配置。
データバインドを利用して、Project クラス のプロパティとバインドする。
<UserControl x:Class="ProjectModule.Views.ProjectListView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:prism="http://www.codeplex.com/prism"> <Grid x:Name="LayoutRoot" Background="White"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <TextBlock Grid.Row="0" Text="Projects List View" TextWrapping="Wrap" Grid.RowSpan="1" Grid.ColumnSpan="2" FontSize="18" Foreground="#FF2F3806" Margin="8,8,8,8" /> <Controls:DataGrid x:Name="ProjectsList" Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" SelectionMode="Single" ItemsSource="{Binding Path=Projects}" AutoGenerateColumns="False" Margin="8" AutomationProperties.AutomationId="ProjectsListGrid"> <Controls:DataGrid.Columns> <Controls:DataGridTextColumn Header="System Name" Binding="{Binding Path=SystemName}" IsReadOnly="True" Width="*" /> <Controls:DataGridTextColumn Header="App Name" Binding="{Binding Path=ApplicationName}" IsReadOnly="True" Width="*" /> </Controls:DataGrid.Columns> </Controls:DataGrid> </Grid> </UserControl>
あとは、コードビハインドにて、コンストラクタに、ImportingConstructor アトリビュートを設定し、上記で作成した、ViewModel を インジェクションし、データコンテキストに設定するコードを記述。
using System.ComponentModel.Composition; using System.Windows.Controls; using ProjectModule.ViewModels; namespace ProjectModule.Views { [Export(typeof(ProjectListView))] public partial class ProjectListView : UserControl { [ImportingConstructor] public ProjectListView(ProjectListViewModel viewModel) { InitializeComponent(); this.DataContext = viewModel; } } }
実行
まずは、想定どおりに画面表示できた。