今度は、MVVM Basicな構成のプロジェクトを見ていきます。
この雛形は、シンプルなMVVM構成でコードを書く時には、割と使い勝手がいいかなと思います。
準備
まずは、以下の設定でプロジェクトを作成します。
- ProjectType: Blank
- Framework: MVVM Basic
- Pages, Features: デフォルトのまま
プロジェクトの全体構成はこんな感じです。
Code BehindとMVVM Basicのプロジェクトの違い
アプリの起動処理や、一部の各種ヘルパー関数などは、前回のCode Behindなプロジェクトと共通です。
違いは以下のような点です。
- Helpersフォルダに、以下のようなMVVM設計のための基底クラスが用意されている
- Observableクラス
- RelayCommandクラス
- Views/ViewModelsフォルダ
- Observableクラスを継承した、MainViewModelクラス
- MainPage.xaml.csは、↑をプロパティとして持つだけのシンプルな構成
MVVMな設計のためのヘルパークラス類
MVVM Basicでは、Helpersフォルダに以下のようなObservable/RelayCommandといったクラスが追加されています。
これらは、MVVMな設計をする上での必需品となる、INotifyPropertyChangedとICommandインターフェースの実装クラスです。
C#7.0な書き方が多用されていますが、↓に書いたようなコードとだいたい同じことが書かれています。
http://sourcechord.hatenablog.com/entry/20130303/1362315081
http://sourcechord.hatenablog.com/entry/2014/01/13/200039
Observable.cs
publicclass Observable : INotifyPropertyChanged { publicevent PropertyChangedEventHandler PropertyChanged; protectedvoid Set<T>(ref T storage, T value, [CallerMemberName]string propertyName = null) { if (Equals(storage, value)) { return; } storage = value; OnPropertyChanged(propertyName); } protectedvoid OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); }
RelayCommand.cs
publicclass RelayCommand : ICommand { privatereadonly Action _execute; privatereadonly Func<bool> _canExecute; publicevent EventHandler CanExecuteChanged; public RelayCommand(Action execute) : this(execute, null) { } public RelayCommand(Action execute, Func<bool> canExecute) { this._execute = execute ?? thrownew ArgumentNullException("execute"); this._canExecute = canExecute; } publicbool CanExecute(object parameter) => _canExecute == null || _canExecute(); publicvoid Execute(object parameter) => _execute(); publicvoid OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); } publicclass RelayCommand<T> : ICommand { privatereadonly Action<T> _execute; privatereadonly Func<T, bool> _canExecute; publicevent EventHandler CanExecuteChanged; public RelayCommand(Action<T> execute) : this(execute, null) { } public RelayCommand(Action<T> execute, Func<T, bool> canExecute) { this._execute = execute ?? thrownew ArgumentNullException("execute"); this._canExecute = canExecute; } publicbool CanExecute(object parameter) => _canExecute == null || _canExecute((T)parameter); publicvoid Execute(object parameter) => _execute((T)parameter); publicvoid OnCanExecuteChanged() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); }
ViewsとViewModelsフォルダ
MainPage.xaml
MainPageのxamlは、前回のコードビハインドを用いたプロジェクトとほぼ同様のものです。
ということで、ここではコードは省略します。
MainPage.xaml.cs
MainPageのコードビハインドは、ViewModelのプロパティを持つだけのシンプルなものになっています。
前回と違い、MVVMなスタイルでViewとViewModelを分離しているので、コードビハインドはINotifyPropertyChangedの実装などはせず、シンプルな形になっています。
publicsealedpartialclass MainPage : Page { public MainViewModel ViewModel { get; } = new MainViewModel(); public MainPage() { InitializeComponent(); } }
MainViewModel.cs
ViewModelは、Observableを継承しただけのシンプルなものが用意されています。
publicclass MainViewModel : Observable { public MainViewModel() { } }
サンプル
ObservableやRelayCommandどのクラスを利用し、ちょっとしたサンプルコード書いてみます。
テキストボックスの文字列をViewModelのNameプロパティとバインドしておき、
Clearボタンが押されたら、RelayCommandを用いて上記Nameプロパティをクリアします。
View/ViewModelのコードは、それぞれ以下のような感じ。
MainPage.xaml
<Pagex:Class="Blank_MvvmBasic.Views.MainPage"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"mc:Ignorable="d"><Gridx:Name="ContentArea"Margin="12,0,12,0"><Grid.RowDefinitions><RowDefinition x:Name="TitleRow"Height="48"/><RowDefinition Height="*"/></Grid.RowDefinitions><TextBlockx:Name="TitlePage"x:Uid="Main_Title"FontSize="28"FontWeight="SemiLight"TextTrimming="CharacterEllipsis"TextWrapping="NoWrap"VerticalAlignment="Center"Margin="0,0,12,7"/><Grid Grid.Row="1"Background="{ThemeResource SystemControlPageBackgroundChromeLowBrush}"><StackPanel><TextBox Margin="5"Width="200"Text="{x:Bind ViewModel.Name, Mode=TwoWay}"HorizontalAlignment="Left" /><Button Content="Clear"HorizontalAlignment="Left"Margin="5"Command="{x:Bind ViewModel.ClearCommand}" /></StackPanel></Grid></Grid></Page>
MainPage.xaml.cs
雛形コードのまま変更なし。
MainViewModel.cs
publicclass MainViewModel : Observable { public MainViewModel() { } // Observableクラスで定義されているSetメソッドを使うことで、// プロパティの値が更新されたら、INotifyPropertyChangedでの更新通知イベントが呼び出されます。privatestring name; publicstring Name { get { return name; } set { this.Set(refthis.name, value); } } // 以下、RelayCommandクラスを用いたコマンドの実装private RelayCommand clearCommand; public RelayCommand ClearCommand { get { return clearCommand = clearCommand ?? new RelayCommand(Clear); } } privatevoid Clear() { this.Name = string.Empty; } }
コマンド実行のEnable/Disable制御
RelayCommandのCanExecuteを利用して、コマンド実行可否の制御をしてみます。
UWPではx:Bindによってイベントから関数へと直接バインドすることができます。
なので、RelayCommandクラスを用いずとも、前述のサンプルと同等の事は実装できます。
しかし、RelayCommandではコマンドが実行可能か否かをコマンド自身が示すことができるようになっています。
コマンドが実行可能かどうかを判断するチェック処理を定義しておくと、コマンドをボタンにバインドしたときに、自動でEnable/Disable制御をしてくれるようになります。
ということで、コマンドの実行可否の制御をしてみます。
修正点は以下の二つ。
- RelayCommand作成時に、第二引数でコマンド実行できるかどうかのチェック処理を渡す
- NameプロパティのSetterで、ClearCommandのOnCanExecuteChangedを呼び出す
- Nameプロパティが変わる = Clearボタンの実行可否が変わる可能性があるため
こうすると、ClearCommandをボタンのCommandプロパティにバインドしておくだけで、
CanExecuteがfalseの時には、ボタンが勝手にDisable状態へと変化します。
publicclass MainViewModel : Observable { public MainViewModel() { } // Observableクラスで定義されているSetメソッドを使うことで、// プロパティの値が更新されたら、INotifyPropertyChangedでの更新通知イベントが呼び出されます。privatestring name; publicstring Name { get { return name; } set { this.Set(refthis.name, value); this.ClearCommand.OnCanExecuteChanged(); } } // 以下、RelayCommandクラスを用いたコマンドの実装private RelayCommand clearCommand; public RelayCommand ClearCommand { get { return clearCommand = clearCommand ?? new RelayCommand(Clear, () => !string.IsNullOrWhiteSpace(this.Name)); } } privatevoid Clear() { this.Name = string.Empty; } }