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

Windowsでウインドウを一つ開いて何か処理を行うコードを簡単に書くにはどうすればよいのか実験してみました。
完成させたいコードのイメージ。↓OpenFrameworksっぽくしてみたい
WindowBaseがAppBaseを持っていて、マウスに反応してAppBaseのイベント処理を呼び出すようなものを想定しています。

main.cpp
#include "myFrameworks.h"

int main(){

	WindowBase window(20,20,200,200);
	window.show();

	runSystem();//loop処理部分
	return 0;
}
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();
};
AppBase.cpp

ここに各イベントが発生したときの処理を書く。

#include "AppBase.h"
#include <stdio.h>
AppBase::AppBase(void)
{
}
AppBase::~AppBase(void)
{
}
void AppBase::setup(){
}
void AppBase::MouseMove(){
	printf("MouseMove\n");
}
void AppBase::MouseDragged(){
	printf("MouseDragged\n");
}
void AppBase::MouseReleased(){
	printf("MouseReleased\n");
}
void AppBase::MousePressed(){
	printf("MousePressed\n");
}
void AppBase::FrameEvent(){
}

最初に、べた書きでウインドウを表示させるコードを書いてみます。

ウインドウを表示させるコード

main.cpp
#include <windows.h>
#include <windows.h>

//initial position and size of main woindow
#define WIN_INIT_X 20 
#define WIN_INIT_Y 20
#define WIN_WIDTH 200
#define WIN_HEIGHT 200

#define DEBUG 1

#if DEBUG
#include <stdio.h>
//--------check memory leak--------------//
#include <stdlib.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new  ::new( _NORMAL_BLOCK, __FILE__, __LINE__ )
//--------check memory leak--------------//
#endif

LRESULT CALLBACK WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		return 0;
	case WM_TIMER:
		return 0;
	case WM_LBUTTONDOWN:
		return 0;
	case WM_LBUTTONUP:
		return 0;
	case WM_MOUSEMOVE:
		if (wp==MK_LBUTTON) {	//on mouse dragged

		}
		else{					//on mouse move

		}
		return 0;			
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,PSTR lpCmdLine , int nCmdShow ) {
	HWND hwnd;

#if DEBUG
	//メモリリークの検出
	::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );
	//----consoleの割り当て
	::AllocConsole();
	::FILE* out = 0; ::freopen_s( &out, "CON", "w", stdout );
	::FILE* in = 0; ::freopen_s( &in, "CON", "r", stdin );
	//-----console---------//
#endif

	WNDCLASS wndClass;
	wndClass.style			= CS_HREDRAW | CS_VREDRAW| CS_DBLCLKS;//CS_DBLCLKS-> listen double click event
	wndClass.lpfnWndProc	= WindowProcedure;
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance		= hInstance;
	wndClass.hIcon			= LoadIcon(NULL , IDI_APPLICATION);
	wndClass.hCursor		= LoadCursor(NULL , IDC_CROSS);
	wndClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndClass.lpszMenuName	= NULL;
	wndClass.lpszClassName	= TEXT("WindowTest");
	if (!RegisterClass(&wndClass)) return -1;


	//指定したClient sizeに合わせたwindowサイズを計算(そのままではメニューバーなどのサイズが考慮されていないので)
	RECT Client_Rect;
	Client_Rect.left=0;
	Client_Rect.top=0;
	Client_Rect.right=WIN_WIDTH;
	Client_Rect.bottom=WIN_HEIGHT;
 	AdjustWindowRect(&Client_Rect,WS_OVERLAPPEDWINDOW,FALSE);
	//幅Client_Rect.right-Client_Rect.left高さClient_Rect.bottom-Client_Rect.topのwindowを作るとよい

	hwnd = CreateWindow(
			TEXT("WindowTest") , TEXT("WindowTest") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			WIN_INIT_X , WIN_INIT_Y ,
			Client_Rect.right-Client_Rect.left ,Client_Rect.bottom-Client_Rect.top ,
			NULL , NULL ,
			hInstance , NULL
	);
	if (hwnd == NULL) return -1;

	MSG msg;
	while(GetMessage(&msg , NULL , 0 , 0)) {
		DispatchMessage(&msg);
	}

#if DEBUG
    //----consoleの解放----
    fclose( out );
    fclose( in );
    ::FreeConsole();
	//-----console---------//
#endif
	return 0;
}

まずは全体の処理の流れを眺めてみます。

Entry Point

エントリーポイントはWinMainです。プログラムを実行するとまずはここから処理が始まります。

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,PSTR lpCmdLine , int nCmdShow ) {

Window Classの登録

作成するウインドウの情報をシステムに登録する部分です。ウインドウで発生したイベントを処理する関数、アイコン、カーソル、背景などを設定します。

	WNDCLASS wndClass;
	wndClass.style			= CS_HREDRAW | CS_VREDRAW| CS_DBLCLKS;//CS_DBLCLKS-> listen double click events
	wndClass.lpfnWndProc	= WindowProcedure;
	wndClass.cbClsExtra		= 0;
	wndClass.cbWndExtra		= 0;
	wndClass.hInstance		= GetModuleHandle(NULL);
	wndClass.hIcon			= LoadIcon(NULL , IDI_APPLICATION);
	wndClass.hCursor		= LoadCursor(NULL , IDC_CROSS);
	wndClass.hbrBackground	= (HBRUSH)GetStockObject(BLACK_BRUSH);
	wndClass.lpszMenuName	= NULL;
	wndClass.lpszClassName	= TEXT("WindowTest");

	if (!RegisterClass(&wndClass)) return -1;

Windowの作成

ここで初めてWindowを作成します。ウインドウの初期位置、サイズなどを設定します。

	hwnd = CreateWindow(
			TEXT("WindowTest") , TEXT("WindowTest") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			WIN_INIT_X , WIN_INIT_Y ,
			WIN_WIDTH ,WIN_HEIGHT ,
			NULL , NULL ,
			GetModuleHandle(NULL) , NULL
	);
	if (hwnd == NULL) return -1;

Messege Loop

ここのループ処理によってWindowで発生したイベントを待ち続ける状態になります。

	while(GetMessage(&msg , NULL , 0 , 0)) {
		DispatchMessage(&msg);
	}

Window Procudure

イベントを受け取った時に呼ばれる関数です。ここに各イベントに応じた処理を書きます。

LRESULT CALLBACK WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
				return 0;
	case WM_PAINT:
		return 0;
	case WM_TIMER:
		return 0;
	case WM_LBUTTONDOWN:
		return 0;
	case WM_LBUTTONUP:
		return 0;
	case WM_MOUSEMOVE:
		if (wp==MK_LBUTTON) {	//on mouse dragged

		}
		else{					//on mouse move

		}
		return 0;			
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}

以上でベースとなるプログラムを用意できました。このプログラムを組み替えて最初のコードに変形してみます。

mainとrunSystem追加

mainやrunSystem関数を利用しているのでmain.cppに追加します。mainにウインドウ関連の処理を、runSystemの中にループ処理を書いてそれぞれ分離します。

main.cpp
int main();
int runSystem();

int main(){
	//ウインドウ生成に関する処理
	//省略

	runSystem();
	return 0;
}

void runSystem(){
	while(GetMessage(&msg , NULL , 0 , 0)) {
		DispatchMessage(&msg);
	}
}

メッセージループを含むので、runSystemはmainの最後に、mainはWinMainの最後に呼び出すことにします。

main文とその他のコードを分離する

main文にウインドウ関連の処理を残して、その他をmyFrameworks.hに分けてみます。ただ単に分けただけです。

main.cpp
#include "myFrameworks.h"
#include <windows.h>

//initial position and size of main woindow
#define WIN_INIT_X 20 
#define WIN_INIT_Y 20
#define WIN_WIDTH 200
#define WIN_HEIGHT 200

LRESULT CALLBACK WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp);

int main(){
	HWND hwnd;

	WNDCLASS wndClass;
	wndClass.style			= CS_HREDRAW | CS_VREDRAW| CS_DBLCLKS;//CS_DBLCLKS-> listen double click event
	wndClass.lpfnWndProc	= WindowProcedure;
	//・・・省略
	wndClass.lpszClassName	= TEXT("WindowTest");
	if (!RegisterClass(&wndClass)) return -1;

	//・・・省略

	hwnd = CreateWindow(
		//・・・省略
	);
	if (hwnd == NULL) return -1;

	runSystem();
	return 0;
}

LRESULT CALLBACK WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {

	switch (msg) {
	case WM_DESTROY:
		//・・・省略	
	}
	return DefWindowProc(hwnd , msg , wp , lp);
}
myFrameworks.h
#if DEBUG
#include <stdio.h>
//--------check memory leak--------------//
#include <stdlib.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#define new  ::new( _NORMAL_BLOCK, __FILE__, __LINE__ )
//--------check memory leak--------------//
#endif

int main();
void runSystem();
int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,	PSTR lpCmdLine , int nCmdShow );
myFrameworks.cpp
#include "myFrameworks.h"

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,PSTR lpCmdLine , int nCmdShow ) {

#if DEBUG
    //メモリリークの検出
	::_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

	//----consoleの割り当て
	::AllocConsole();
 	::FILE* out = 0; ::freopen_s( &out, "CON", "w", stdout );
	::FILE* in = 0; ::freopen_s( &in, "CON", "r", stdin );
	//-----console---------//
#endif
	main();

#if DEBUG
	//----consoleの解放----
	fclose( out );
	fclose( in );
	::FreeConsole();
	//-----console---------//
#endif
	return 0;
}

void runSystem(){
	::MSG msg;
	while(::GetMessage(&msg , NULL , 0 , 0)) {
		::DispatchMessage(&msg);
	}
}

ウインドウクラスを作る

ウインドウクラスのメソッドとして、ウインドウプロシージャ、ウインドウクラスの登録を行うメソッドの他に、main文から呼び出すshow()メソッドを追加します。ウインドウを生成するとウインドウハンドルが返ってきますので、メンバとしてウインドウハンドル(m_hwnd)を持つことにします。また、初期位置とサイズをメンバとして持たせます。
まずは何も考えずに、registWindowClass()にウインドウクラスの登録を行う処理を、
show()にウインドウ生成処理を、WindowProcedure()にウインドウプロシージャの中で行われている処理をそのまま書いてみます。

WindowBase.h
#include <windows.h>
class WindowBase
{
public:
	WindowBase(int posX,int posY,int width,int height);
	~WindowBase(void);
	void show();
private:
	static LRESULT CALLBACK WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp);
	int registWindowClass();
	
	int m_posX;
	int m_posY;
	int m_width;
	int m_height;

	HWND m_hwnd;
};
WindowBase.cpp

コンストラク

#include "WindowBase.h"

WindowBase::WindowBase(int posX,int posY,int width,int height):
m_posX(posX),
m_posY(posY),
m_width(width),
m_height(height),
m_hwnd(NULL)
{
}

ウインドウクラスを登録するメソッドの追加

int WindowBase::registWindowClass(){

	WNDCLASS wndClass;
	wndClass.style		= CS_HREDRAW | CS_VREDRAW| CS_DBLCLKS;
	wndClass.lpfnWndProc	= WindowProcedure;
		//・・・・・・省略
	if (!RegisterClass(&wndClass)) return -1;
	return 0;
}

ウインドウを作成するメソッドの追加

void WindowBase::show(){
	RECT Client_Rect;
	Client_Rect.left=0;
	Client_Rect.left=0;
	Client_Rect.top=0;
	Client_Rect.right=m_width;
	Client_Rect.bottom=m_height;
	AdjustWindowRect(&Client_Rect,WS_OVERLAPPEDWINDOW,FALSE);

	m_hwnd = CreateWindow(
			TEXT("WindowTest") , TEXT("WindowTest") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			m_posX , m_posY ,
			Client_Rect.right-Client_Rect.left ,Client_Rect.bottom-Client_Rect.top ,
			NULL , NULL ,
			GetModuleHandle(NULL) , NULL
	);
	if (m_hwnd == NULL) return;
}

ウインドウプロシージャの追加

int WindowBase::WindowProcedure(HWND hwnd , UINT msg , WPARAM wp , LPARAM lp) {
	switch (msg) {
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		return 0;
	case WM_CREATE:
		return 0;
		//・・・・・・省略
	}
	return ::DefWindowProc(hwnd , msg , wp , lp);
}

以上でウインドウをクラスっぽく書き換えることはできましたが、実際にうまく動かせるようにするにはもう少し工夫が要るようです。→次回簡単にウインドウを作成するコード その2 - white wheelsのメモ