step6:ちょっと気の利いたコンポーネント
さてこのViewModel、退屈な仕事をしています。カウンタをIncrement/Decrementし、カウント値に変化があったらその旨をViewに通知しているところ:do_inc/do_decです。この仕事はコンポーネント側に任せ、カウント値の変化はイベントで受け取ることにしましょう。ついでにカウント値の取得もGet()メソッドからCountプロパティに改めます。
#pragma once
#include "CounterLib/CounterLib.h"
namespace CounterComponent {
namespace cx {
namespace data = Windows::UI::Xaml::Data;
}
[Windows::Foundation::Metadata::WebHostHidden]
public ref class Counter sealed : cx::data::INotifyPropertyChanged {
public:
Counter();
virtual event cx::data::PropertyChangedEventHandler^ PropertyChanged;
void Increment();
void Decrement();
property int Count { int get(); }
private:
void RaisePropertyChanged(Platform::String^ propertyName);
CounterLib::Counter counter_;
};
}
#include "pch.h"
#include "Counter.h"
using namespace Platform;
namespace CounterComponent {
Counter::Counter() : counter_() {}
void Counter::Increment() {
int curr = counter_.get();
counter_.increment();
if ( curr != counter_.get() ) {
RaisePropertyChanged(L"Count");
}
}
void Counter::Decrement() {
int curr = counter_.get();
counter_.decrement();
if ( curr != counter_.get() ) {
RaisePropertyChanged(L"Count");
}
}
int Counter::Count::get() { return counter_.get(); }
void Counter::RaisePropertyChanged(Platform::String^ propertyName) {
PropertyChanged(this, ref new Windows::UI::Xaml::Data::PropertyChangedEventArgs(propertyName));
}
}
ViewModel側ではカウント値が変化したこと、つまりPropertyChangedイベントを受け取ればよく、イベント・ハンドラで自身のPropertChangedイベントを発行することでその旨をViewに"横流し"します。
#pragma once
#include "Common/RelayCommand.h"
namespace CounterApp {
namespace cx {
namespace xaml = Windows::UI::Xaml;
namespace data = Windows::UI::Xaml::Data;
namespace input = Windows::UI::Xaml::Input;
}
[Windows::UI::Xaml::Data::Bindable]
public ref class CounterViewModel sealed : cx::data::INotifyPropertyChanged{
public:
CounterViewModel();
virtual event cx::data::PropertyChangedEventHandler^ PropertyChanged;
property int Count { int get(); }
property cx::input::ICommand^ Increment { cx::input::ICommand^ get(); }
property cx::input::ICommand^ Decrement { cx::input::ICommand^ get(); }
private:
void RaisePropertyChanged(Platform::String^ propertyName);
CounterComponent::Counter counter_;
};
}
#include "pch.h"
#include "CounterViewModel.h"
using namespace Platform;
using namespace Windows::Foundation;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Data;
namespace CounterApp {
auto returnsTrue = [](Object^) { return true; };
CounterViewModel::CounterViewModel() : counter_() {
// Counterからのイベントを横流しする。
counter_.PropertyChanged += ref new PropertyChangedEventHandler(
[this](Object^ sender, PropertyChangedEventArgs^ e) {
if ( e->PropertyName == L"Count" ) RaisePropertyChanged(L"Count");
});
}
// property Count
int CounterViewModel::Count::get() { return counter_.Count; }
// property Increment/Decrement
ICommand^ CounterViewModel::Increment::get() {
return ref new Common::RelayCommand(returnsTrue, [this](Object^) { counter_.Increment(); });
}
ICommand^ CounterViewModel::Decrement::get() {
return ref new Common::RelayCommand(returnsTrue, [this](Object^) { counter_.Decrement(); });
}
void CounterViewModel::RaisePropertyChanged(String^ propertyName) {
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
}
C#/VB.NETでも同様に:
using System.Windows.Input;
using System.ComponentModel;
namespace CounterApp_CS {
sealed class CounterViewModel : INotifyPropertyChanged {
public CounterViewModel() {
counter_ = new CounterComponent.Counter();
counter_.PropertyChanged += (sender,e) => {
if ( e.PropertyName == "Count" ) {
RaisePropertyChanged("Count");
}
};
}
public event PropertyChangedEventHandler PropertyChanged;
public int Count { get { return counter_.Count; } }
public ICommand Increment { get { return new Common.RelayCommand( () => { counter_.Increment(); }); } }
public ICommand Decrement { get { return new Common.RelayCommand( () => { counter_.Decrement(); }); } }
private void RaisePropertyChanged(string propertyName) {
if ( PropertyChanged != null ) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
private CounterComponent.Counter counter_;
};
}
Imports System.Windows.Input
Imports System.ComponentModel
NotInheritable Class CounterViewModel
Implements INotifyPropertyChanged
Public Sub New()
counter_ = New CounterComponent.Counter()
AddHandler counter_.PropertyChanged,
Sub(sender, e)
If e.PropertyName = "Count" Then
RaisePropertyChanged("Count")
End If
End Sub
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Public ReadOnly Property Count() As Integer
Get
Return counter_.Count
End Get
End Property
Public ReadOnly Property Increment() As ICommand
Get
Return New Common.RelayCommand(Sub()
counter_.Increment()
End Sub)
End Get
End Property
Public ReadOnly Property Decrement() As ICommand
Get
Return New Common.RelayCommand(Sub()
counter_.Decrement()
End Sub)
End Get
End Property
Private Sub RaisePropertyChanged(propertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
End Sub
Private counter_ As CounterComponent.Counter
End Class
ずいぶんスッキリしました。ViewModelはあくまでViewとModelとを繋ぐのが本来のお役目ですから、ここにロジックを盛り込むのは宜しくなさそうです。"何をやるか"だけでなく"誰がやるか"を適切に振り分けるのも設計のキモですね。
