はじめに
WPF では Model-View-ViewModel (以下 MVVM) パターンが主流になりつつあるみたい。
MVVM パターンには Command が不可欠ですが、Silverlight にはありません。でも SLExtensions ライブラリを使えば問題無し。
今更ながら挑戦してみました。
Model を作成
商品情報を格納するデータクラスにしてみました。
Product.cs
public class Product { public int Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } }
WCF のデータコントラクトを Model にしてもいいですね。
ViewModel を作成
これが MVVM パターンのキモです。
ProductViewModel.cs
public class ProductViewModel : NotifyingObject { private Command _findCommand; private ObservableCollection<Product> _model; private int _beginId; private int _endId; public ProductViewModel() { _model = new ObservableCollection<Product>(); _beginId = 1; _endId = 9999; _findCommand = new Command("Find"); _findCommand.Executed += new EventHandler<ExecutedEventArgs>(findCommand_Executed); } /// <summary> /// 検索コマンドを取得します。 /// </summary> public Command FindCommand { get { return _findCommand; } } /// <summary> /// モデルを取得または設定します。 /// </summary> public ObservableCollection<Product> Model { get { return _model; } set { if (_model == value) { return; } _model = value; OnPropertyChanged("Model"); } } /// <summary> /// 検索する製品 ID の範囲の開始値を取得または設定します。 /// </summary> public int BeginId { get { return _beginId; } set { if (_beginId == value) { return; } _beginId = value; OnPropertyChanged("BeginId"); } } /// <summary> /// 検索する製品 ID の範囲の終了値を取得または設定します。 /// </summary> public int EndId { get { return _endId; } set { if (_endId == value) { return; } _endId = value; OnPropertyChanged("EndId"); } } /// <summary> /// データを取得して Model にセットします。 /// </summary> private void findCommand_Executed(object sender, ExecutedEventArgs e) { // Model に適当なデータをセットする ObservableCollection<Product> products = new ObservableCollection<Product>(); int min = Math.Min(BeginId, EndId); int max = Math.Max(BeginId, EndId); for (int i = min; i <= max; i++) { products.Add(new Product() { Id = i, Name = string.Format("製品{0}", i), Price = 1000, }); } Model = products; } }
SLExtensions に用意されている NotifyingObject クラスを継承しています。このクラスは INotifyPropertyChanged インタフェースを実装しただけのクラス。既にあるものは利用しちゃいますw
ViewModel は UI から入力された日付や検索結果を保持しています。検索を行う FindCommand も公開しています。この Command は View から呼び出されます。
FindCommand に割り当てている処理自体は適当なデータを生成して Model にセットする簡易的なものです。本来なら WCF サービスを呼び出したりするでしょう。
View を作成
Page.xaml
<UserControl x:Class="MVVMSample.Page" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data" xmlns:mscontrols="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls" xmlns:msinput="clr-namespace:Microsoft.Windows.Controls;assembly=Microsoft.Windows.Controls.Input" xmlns:theming="clr-namespace:Microsoft.Windows.Controls.Theming;assembly=Microsoft.Windows.Controls.Theming" xmlns:slinput="clr-namespace:SLExtensions.Input;assembly=SLExtensions"> <mscontrols:DockPanel x:Name="LayoutRoot" Background="White" LastChildFill="True"> <StackPanel Orientation="Horizontal" mscontrols:DockPanel.Dock="Top" theming:ImplicitStyleManager.ApplyMode="Auto"> <StackPanel.Resources> <Style TargetType="msinput:NumericUpDown"> <Setter Property="Width" Value="120"/> <Setter Property="Minimum" Value="1"/> <Setter Property="Maximum" Value="9999"/> </Style> <Style TargetType="mscontrols:Label"> <Setter Property="VerticalContentAlignment" Value="Center"/> </Style> </StackPanel.Resources> <mscontrols:Label Content="検索範囲:"/> <mscontrols:Label Content="開始ID"/> <msinput:NumericUpDown Value="{Binding Path=BeginId, Mode=TwoWay}"/> <mscontrols:Label Content=" 〜 "/> <mscontrols:Label Content="終了ID"/> <msinput:NumericUpDown Value="{Binding Path=EndId, Mode=TwoWay}"/> <Button Content="検索" slinput:CommandService.Command="Find"/> </StackPanel> <data:DataGrid ItemsSource="{Binding Path=Model}" AutoGenerateColumns="False"> <data:DataGrid.Columns> <data:DataGridTextColumn Binding="{Binding Path=Id}" Header="商品ID"/> <data:DataGridTextColumn Binding="{Binding Path=Name}" Header="商品名"/> <data:DataGridTextColumn Binding="{Binding Path=Price}" Header="価格"/> </data:DataGrid.Columns> </data:DataGrid> </mscontrols:DockPanel> </UserControl>
Silverlight Toolkit にある NumericUpDown や Label を使っています。
注目すべきは Button の部分。CommandService クラスの Command 添付プロパティを使って、 "Find" という名前の Command を設定しています。これが ViewModel で登録した FineCommand です。
Page.xaml.cs
そうそう。
public partial class Page : UserControl { public Page() { var viewModel = new ProductViewModel(); DataContext = viewModel; InitializeComponent(); } }
View の DataContext に ViewModel を設定するのを忘れてはいけません。WPF では DataTemplate を使うみたいですが、Silverlight では出来ないので、コードで設定します。
まとめ
MVVM パターンを使えば、画面とロジックを上手く分離できそうです。View のコードがすっきりするし、ViewModel の単体テストも楽になりそうだ。