簡単にウインドウを作成するコード その2

ウインドウを簡単に作成するコード その1 - white wheelsのメモのつづき
Windowクラスを作成しましたが、残念ながらstaticなWindowProcedureの中では、静的でない変数やメソッドを利用することができません...
WindowProcedureの中でWindowBaseインスタンスのメンバにアクセスするには、そのインスタンスのアドレスを何とかして渡す必要があります。

LRESULT CALLBACK WindowBase::WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	//ここでwindowインスタンスのアドレスを知る必要がある
	switch (msg) {
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		return 0;
	case WM_TIMER:
		this->myProcedure();	//compile error :静的な関数の中で、静的でない関数を呼び出すことはできない

上のような仕組みを実現するには、Win32APIで定義されている関数を利用する方法があります。

BOOL SetProp(
  HWND hWnd,         // ウィンドウハンドル
  LPCTSTR lpString,  // ウインドウを特定する文字列
  HANDLE hData       // データのハンドル
);
HANDLE GetProp(
  HWND hWnd,         // ウィンドウのハンドル
  LPCTSTR lpString   // ウインドウを特定する文字列
);
HANDLE RemoveProp(
  HWND hWnd,         // ウィンドウのハンドル
  LPCTSTR lpString   // ウインドウを特定する文字列
);

SetPropはWindowを生成すると同時にシステムにウインドウハンドルを登録する関数です。引数としてインスタンスを特定する文字列を定めます。

WindowBase.cpp
	m_hwnd = CreateWindow(
			TEXT("WindowTest") , TEXT("WindowTest") ,
			//...略
			)
	::SetProp(m_hwnd, "THIS_INSTANCE", this);

ウインドウプロシージャ内でWindowBaseインスタンスのアドレスを取り出すには、GetPropを使います。SetPropで定めた文字列を利用してウインドウを知ることができます。

LRESULT CALLBACK WindowBase::WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	WindowBase* win = (WindowBase*)::GetProp(hwnd, "THIS_INSTANCE");

	switch (msg) {
	case WM_DESTROY:
		::PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		return 0;
	case WM_MOUSEMOVE:
		//略
	}
	return ::DefWindowProc(hwnd , msg , wp , lp);
}

システムに登録したハンドルの情報を削除するにはRemovePropを呼び出します。

WindowBase::~WindowBase(void)
{
	//..略
	::RemoveProp(m_hwnd, "THIS_INSTANCE");
}

これで静的でないメンバをウインドウプロシージャ内で呼び出すことができるようになりました。
そこで各インスタンスごとのウインドウプロシージャを定義し、タイマーイベントや、マウスイベントなどを取り出すことにします。取り出した先で、AppBaseのメンバ関数を呼び出すことにしましょう。
静的でないプロシージャmyProcedureをクラスのメンバ関数に加えます。

WindowBase.cpp
class WindowBase
{
public:
	int myProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp);
//略
int WindowBase::myProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch (msg) {
	case WM_TIMER:
		return 0;
	case WM_LBUTTONDOWN:
		m_appBase->MousePressed();//ここでAppBaseを呼び出す。
		return 0;
	case WM_LBUTTONUP:
		m_appBase->MouseReleased();
		return 0;
	case WM_MOUSEMOVE:
		if (wp==MK_LBUTTON) {	//on mouse dragged
		m_appBase->MouseDragged();
		}
		else{					//on mouse move
		m_appBase->MouseMove();
		}
		return 0;			
	}
	return ::DefWindowProc(hwnd , msg , wp , lp);
}

WindowProcedureが呼び出されるタイミングは、windowsからメッセージを受けとった時であって、必ずしもWindowBaseクラスのインスタンスによるものであありません。そこで、WindowBaseインスタンスのアドレスを取ることができたときのみ処理を行うことにします。
WindowBaseを取得できるのはウインドウが生成された後の話なので、少なくともWM_CREATEの処理はここに残しておきます。

WindowBase.cpp
LRESULT CALLBACK WindowBase::WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	WindowBase* win = (WindowBase*)::GetProp(hwnd, "THIS_INSTANCE");
	switch (msg) {
		case WM_DESTROY:
			::KillTimer(hwnd, 1);
			::PostQuitMessage( 0 );
			return 0;
		case WM_CREATE:
			::SetTimer(hwnd,1,100,NULL);
			return 0;
	}

	if(win == 0){
		return ::DefWindowProc(hwnd , msg , wp , lp);
	}
	return win->myProcedure(hwnd , msg , wp , lp);
}

最後に、AppBaseをWindowBaseのメンバに追加して作業終了です。AppBaseはコンストラクタでnew,デストラクタでdeleteしておきます。
これでAppBaseのメンバ関数を呼ぶことができるようになりました。

WindowBase.h
class WindowBase
{
//...
private:
	AppBase* m_appBase;
};
WindowBase.cpp
WindowBase::WindowBase(int posX,int posY,int width,int height):
//...
	m_appBase=new AppBase();
}

WindowBase::~WindowBase(void){
	delete m_appBase;
//...

AppBaseの実装

AppBaseはメッセージに対応した処理を行うクラスで、その処理自体はウインドウクラスとは分離しています。
メンバとイベントごとの処理だけAppBaseに書き加えることで、様々な機能を追加することができるようになります。
例えば、マウスの座標を取得できるようにするには、マウス座標をAppBaseのメンバに追加します。

AppBase.h
class AppBase
{
public:
//....略
	void setMouse(int x,int y){
		mouseX = x; mouseY = y;
	}
private:
	int mouseX;
	int mouseY;
};

さらにイベントを受け取ったときの処理を書きます。

WindowBase.cpp
	case WM_LBUTTONDOWN:
		m_appBase->setMouse(LOWORD(lp),HIWORD(lp));
		m_appBase->MousePressed();
		return 0;
	case WM_LBUTTONUP:
		m_appBase->setMouse(LOWORD(lp),HIWORD(lp));
		m_appBase->MouseReleased();
		return 0;
	case WM_MOUSEMOVE:
		m_appBase->setMouse(LOWORD(lp),HIWORD(lp));
		if (wp==MK_LBUTTON) {	//on mouse dragged
		m_appBase->MouseDragged();
		}
		else{			//on mouse move
		m_appBase->MouseMove();
		}
		return 0;	

そして各イベントで呼び出されるAppBaseのメソッドを実装すると、windowの中のマウスの位置を常に取得することができるようになります。

AppBase.cpp
void AppBase::MouseMove(){
	printf("MouseMove:(%d,%d)\n",mouseX,mouseY);
}
void AppBase::MouseDragged(){
	printf("MouseDragged:(%d,%d)\n",mouseX,mouseY);
}
void AppBase::MouseReleased(){
	printf("MouseReleased:(%d,%d)\n",mouseX,mouseY);
}
void AppBase::MousePressed(){
	printf("MousePressed:(%d,%d)\n",mouseX,mouseY);
}

タイマーイベントを利用すると、フレームごとに呼び出されれる処理を実装することもできます。
この場合も基本的にはAppBaseを書き換えることが主な作業になります。

AppBase.h
class AppBase
{
public:
	AppBase(void);
	virtual ~AppBase(void);
	virtual void setup();
	virtual void MouseMove();
	virtual void MouseDragged();
	virtual void MouseReleased();
	virtual void MousePressed();
	virtual void FrameEvent();

	void setMouse(int x,int y){
		mouseX = x; mouseY = y;
	}
private:
	int mouseX;
	int mouseY;
	int count;
};
AppBase.cpp
AppBase::AppBase(void)
{
	setup();
}
void AppBase::setup(){
	count=0;
}
//...
void AppBase::FrameEvent(){
	printf("FrameEvent:count = %d\n",count);
	count++;
}