step3:Button-clickをバインドする
つぎに「ふやす/へらす」Buttonのクリックに反応せにゃなりません。各ButtonはそれぞれIncrement/Decrementプロパティにバインドしてあるので、この2つをCounterViewModelに用意します。Command="{Binding ~}"でバインドされるプロパティの型:Windows::UI::Xaml::Input::ICommandを実装した、小さいながらとっても便利なクラス:RellayCommandがCommonディレクトリに作られているので有り難く使わせていただきましょう。
#pragma once
#include "Common/RelayCommand.h"
#include "CounterLib/CounterLib.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 do_inc();
void do_dec();
void execute(std::function<void(void)> action);
void RaisePropertyChanged(Platform::String^ propertyName);
CounterLib::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() { }
// property Count
int CounterViewModel::Count::get() { return counter_.get(); }
// property Increment/Decrement
ICommand^ CounterViewModel::Increment::get() {
return ref new Common::RelayCommand(returnsTrue, [this](Object^) { do_inc(); });
}
ICommand^ CounterViewModel::Decrement::get() {
return ref new Common::RelayCommand(returnsTrue, [this](Object^) { do_dec(); });
}
// increment/decrement counter
void CounterViewModel::do_inc() { execute([this]() { counter_.increment(); }); }
void CounterViewModel::do_dec() { execute([this]() { counter_.decrement(); }); }
// actionを実行し、カウント値に変化があれば PropertyChanged する
void CounterViewModel::execute(std::function<void(void)> action) {
int prev = counter_.get();
action();
if ( prev != counter_.get() ) {
RaisePropertyChanged(L"Count");
}
}
void CounterViewModel::RaisePropertyChanged(String^ propertyName) {
PropertyChanged(this, ref new PropertyChangedEventArgs(propertyName));
}
}
Common::RellayCommandのコンストラクタに与える引数は2つ。
1つめはそのコマンドが実行可ならtrueを返す関数オブジェクト、ここでは常にtrueを返すラムダ式を使っています。これがfalseを返すとコマンドの発行元(ここではButton)がDisableになります。
2つめがコマンドに応じた処理を行う関数オブジェクトです。この中でCounterのincrement()/decrement()を行い、カウント値に変化があったならRaisePropertyChanged(L"Count")して「プロパティ:Countに変化があった」ことをViewに知らせます。Viewはその知らせを受けてCountプロパティの値を読み取り、画面に反映させます。これでCounterAppの完成です。
step4:おまけ:定周期動作
ついでにもう一つ、ゲームなどで使われることの多いタイマーによる定周期動作の実装。コレ、そんなに難しくありません。DispatcherTimerを1つ作り、インターバル(周期)とタイムアウト時の処理を与えておけば、あとはStart()/Stop()するだけです。おためしにPainPageに開始/停止Buttonを追加しDispatcherTimerを動かしてみました。開始/停止ButtonのCommandはどちらもプロパティ:Timerにバインドし、CommandParameterでどちらが押されたのかを判別しています。
<!-- このGridの中にコントロールを配置します -->
<TextBlock Text="{Binding Count}" .../>
<Button Command="{Binding Increment}" Content="ふやす" .../>
<Button Command="{Binding Decrement}" Content="へらす" .../>
<!-- Timerの開始/停止Buttonを追加 -->
<Button Command="{Binding Timer}" CommandParameter="Start" Content="かってにふやす" .../>
<Button Command="{Binding Timer}" CommandParameter="Stop" Content="ふやすのやめ" .../>
#pragma once
#include "Common/RelayCommand.h"
#include "CounterLib/CounterLib.h"
namespace CounterApp {
...
[Windows::UI::Xaml::Data::Bindable]
public ref class CounterViewModel sealed : cx::data::INotifyPropertyChanged{
public:
...
property cx::input::ICommand^ Timer { cx::input::ICommand^ get(); }
private:
...
cx::xaml::DispatcherTimer^ timer_;
Common::RelayCommand^ cmdtimer_;
CounterLib::Counter counter_;
};
}
...
CounterViewModel::CounterViewModel() {
// タイマー初期化
timer_ = ref new DispatcherTimer();
TimeSpan span;
span.Duration = 100 * 1000 * 10; // 10 nano-sec / unit
timer_->Interval = span;
timer_->Tick += ref new EventHandler<Object^>([this](Object^,Object^){ do_inc(); });
// タイマー開始/停止コマンド
cmdtimer_ = ref new Common::RelayCommand(
[this](Object^ param) {
String^ cmd = safe_cast<String^>(param);
if ( cmd == L"Start" ) return !timer_->IsEnabled;
else if ( cmd == L"Stop" ) return timer_->IsEnabled;
else return false;
},
[this](Object^ param) {
String^ cmd = safe_cast<String^>(param);
if ( cmd == L"Start" ) timer_->Start();
else if (cmd == L"Stop") timer_->Stop();
else return;
cmdtimer_->RaiseCanExecuteChanged();
});
}
ICommand^ CounterViewModel::Timer::get() { return cmdtimer_; }
...
以上、『C++/CXでのWindows 8.1ストアアプリのつくりかた』でした。本格的なアプリにはまだ遠くおよびませんけども、はじめの一歩半は踏み出せたんじゃないでしょか。

