[c++] __event と __hook を使って mvvm を実装してみる

__eventを使ったイベントハンドリング – CREST’S WEBLOG (」・ω・)」うー!(/・ω・)/にゃー!
http://d.hatena.ne.jp/Crest/20100418/1271603367
__event
http://msdn.microsoft.com/en-us/library/cb1dzt8t(v=vs.100).aspx

MS-C++ では __declspec(property()) でプロパティを作れるよ | Moonmile Solutions Blog
http://www.moonmile.net/blog/archives/3557

を書いてみて、ひょっとすると event/delegate 系も使えるようになっているのでは?と思ったらありました。
__event/__hook の組み合わせでいけます。C# の event/delegate, Action<>と機能は同じ。

#include "stdafx.h"
#include <afxwin.h>
#include <iostream>
#include <string>
using namespace std;

// Model側のインターフェース(INotify)
class INotifyProperyChange
{
public:
	__event void PropertyChange( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};

// View側のインターフェース(ITarget)
class ITargetPropertyChanged
{
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info );
};

class Model : public INotifyProperyChange
{
private:
	int m_num;
	string m_name;
public:

	__declspec(property(get=getNum, put=setNum)) int Num;
	int getNum() { return m_num ; }
	void setNum( int value ) {
		m_num = value;
		PropertyChange( this, "Num", &value, typeid(value) );
	}

	__declspec(property(get=getName,put=setName)) string Name;
	string &getName() { return m_name; }
	void setName( string &value ) {
		m_name = value;
		PropertyChange( this, "Name", &value, typeid(value) );
	}

};

// View は Model から独立している
class View : public ITargetPropertyChanged
{
private:
	int m_num;
	string m_name;
public:
	__declspec(property(get=getNum, put=setNum)) int Num;
	int getNum() { return m_num; }
	void setNum( int value ) {
		m_num = value;
		cout << "View::Num: " << value << endl;
	}
	__declspec(property(get=getName,put=setName)) string Name;
	string &getName() { return m_name; }
	void setName( string &value ) {
		m_name = value;
		cout << "View::Name: " << value << endl;
	}
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
	{
		// ここを map にする?
		string propName((const char*)name);
		if ( propName == string("Num")) {
			this->Num = *(int*)value;
		} else if ( propName == string("Name")) {
			this->Name = *(string*)value;
		}
	}
};

// ViewModel は View と Model の両方に依存しているが、
// View のほうへの依存度が高い
class ViewModel
{
private:
	View *m_view;
	Model *m_model;
public:
	void OnProperyChanged( INotifyProperyChange *model, void *name, void *value, const type_info &info )
	{
		m_view->OnProperyChanged( model, name, value, info );
	}
	void Bind( Model *model, View *view )
	{
		this->m_model = model;
		this->m_view = view;
		__hook( &Model::PropertyChange, model, &ViewModel::OnProperyChanged, view );
	}
};

int _tmain(int argc, _TCHAR* argv[])
{
	Model model;
	View view;
	ViewModel vm;

	vm.Bind( &model, &view );
	model.Num = 10;
	model.Name = string("masuda tomoaki");
}
D:\work\blog\src\CppProperty\Debug>CppProperty.exe
View::Num: 10
View::Name: masuda tomoaki

ざっと書くとこんな感じ。C# の一般的な mvvm よりも、Model と View の独立性を強くしてあります。__hook の利用法が面白いところで、__hook( ソースの関数ポインタ、ソースオブジェクト、ターゲットの関数ポインタ) を設定します。ここで仕様バグなのかどうかは不明ですが、ターゲットの関数ポインタは、何も自分のクラスのメソッドである必要はありません。ここでは &ViewModel::OnProperyChanged と自分自身を示していますが、&View::OnProperyChanged のほうに他クラスの関数ポインタも示せるという優れもの…と言いますかいい加減な実装がw。ソースの関数メソッドとターゲットの関数メソッドの「型があっていればOK」といういい加減な実装(意図的?)なために、かなり自由度が高いのです。

これを試しに interface だけ(class継承だけ)を使うと、どうしてもうまく行きません。関数ポインタの型がきつくて、一度 static 関数を通さないと実装ができないという状態なので、__hook は重宝します。
まあ、考えてみれば、c/c++ なのだからインラインアセンブラを作れば良いのでした。なるほど。

さて、これでコマンドラインでは、c++ で(おそらく ms-c 限定ですが) mvvm の INotiry, ITarget が動くことが分かりました。今度は windows form(内部的には *.rc)を使って実装してみましょうか。DDX_Control マクロと UpdateData 関数を使わずに実装できるかも。

そうそう View::OnProperyChanged の実装がいまいちなので、View のプロパティ名=バインド名の部分をなんとかしたいですね。C# ならばリフレクションを使うところですが、C++ の場合はどうするか思案中。

カテゴリー: C++ パーマリンク