ウィンドウのあるプログラム作成 ~ Hello, world

それではいよいよ、Hello, world! プログラムを作りましょう。

出来上がりのスクリーンショットは次のようになります。

前回のプログラムから変ったところは、画面中央に "Hello, world!" という文字が書いてあるところだけです。

ソースコードは次の通りです。赤字でマークしたところだけが前回と変っています。


#include <windows.h>

#define WND_CLASS_NAME TEXT("My_Window")


LRESULT CALLBACK WindowProc( HWND hwnd, UINT Msg, WPARAM wParam, LPARAM lParam);


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

	HWND hWnd;
	WNDCLASSEX wcl;
	
	wcl.cbSize = sizeof(WNDCLASSEX);
	wcl.hInstance = hInstance;
	wcl.lpszClassName = WND_CLASS_NAME;
	wcl.lpfnWndProc = WindowProc;
	wcl.style = NULL;
	wcl.hIcon = NULL;
	wcl.hIconSm = NULL;
	wcl.hCursor = LoadCursor(NULL, IDC_ARROW);
	wcl.lpszMenuName = NULL;
	wcl.cbClsExtra = 0;
	wcl.cbWndExtra = 0;
	wcl.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
	
	if(!RegisterClassEx(&wcl)) {
		return FALSE;
	}
	
	hWnd = CreateWindowEx(
		NULL, 
		WND_CLASS_NAME,
		TEXT("Windows Programming Primer - Simple"),
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT, 
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		NULL,
		NULL,
		hInstance,
		NULL);
	
	if(!hWnd) {
		return FALSE;
	}
	
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	
	MSG msg;
	BOOL bRet;
	
	while( ( bRet = GetMessage( &msg, hWnd, 0, 0 ) ) != 0) { 

		if (bRet == -1) {
			break;
		}
		else {
			DispatchMessage( &msg ); 
		}
	}

	return msg.wParam;
	
}


LRESULT CALLBACK WindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam) {

	
	PAINTSTRUCT ps;
	HDC hdc;
	RECT rect;

	switch( uMsg ) {
	case WM_PAINT:
		hdc = BeginPaint ( hwnd, &ps );
		GetClientRect ( hwnd, &rect );
		DrawText( 
			hdc, 
			TEXT("Hello, world!"), 
			-1, 
			&rect, 
			DT_CENTER | DT_VCENTER | DT_SINGLELINE
		);
		EndPaint( hwnd, &ps );
		break;
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		break;
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}

赤でマークしたところが、前回と異なるところです。

一つ目のポイントは UpdateWindow です。


	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);

UpdateWindow によって、特定のウィンドウのクライアントエリアを再描画する要求を出すことが出来ます。

クライアントエリアというのは、ウィンドウの中身の部分です。


図2. クライアントエリア

UpdateWindow を呼び出すことで、WM_PAINT メッセージがウィンドウプロシージャに直接送られます。 後述するように WM_PAINT は画面描画用の特別なメッセージです。クライアントエリアの一部または全体を再描画する場合は WM_PAINT で行ってください。

次のコードは WM_PAINT のメッセージハンドラです。

	
	PAINTSTRUCT ps;
	HDC hdc;
	RECT rect;

	switch( uMsg ) {
	case WM_PAINT:
		GetClientRect ( hwnd, &rect );
		
		hdc = BeginPaint ( hwnd, &ps );
		DrawText( 
			hdc, 
			TEXT("Hello, world!"), 
			-1, 
			&rect, 
			DT_CENTER | DT_VCENTER | DT_SINGLELINE  
		);
		EndPaint( hwnd, &ps );
		
		break;
		
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		...

GetClientRect でクライアントエリアのサイズ (矩形) を取得しています。

BeginPaint 関数にウィンドウハンドルを渡すと、デバイスコンテキスト (device context) のハンドルと PAINTSTRUCT が取得できます。 今回、ここでは PAINTSTRUCT は使いません。問題はデバイスコンテキストです。

デバイスコンテキスト (device context) とはなんでしょうか

ウィンドウズはさまざまなデバイスに描画を行います。 スクリーンに文字を書いたり、プリンターに絵を出力したり、状況によってさまざまな 「デバイス(機器)」に出力します。

出力先はいろいろ変れど、書き出すものは同じ。それなら、出力(描画)手順は同じで、 「出力先スイッチ」のようなものがあったら大変便利です。スイッチを「プリンター」に切り替えて、 ある文書を出力。スイッチを「スクリーン」に切り替えて、ある文書を出力。 デバイスコンテキストはそのような「スイッチ」のようなものだと、考えておいて良いと思います。

デバイスコンテキストのタイプは4種類あります。スクリーン、プリンタ、メモリ、そしてインフォメーションです。 インフォメーションは他の三つと異なり、既定のデバイスデータ取得用に使われます。スクリーン、プリンタはその名の通りで、 メモリはビットマップへの出力になります。

さて、BeginPaint 関数に戻りましょう。

BeginPaint 関数を呼ぶと、引数で指定したウィンドウのスクリーン・デバイスコンテキストのハンドルを取得できます。 スクリーン・デバイスコンテキストのハンドルが返る、ということは BeginPaint を呼ぶとシステム内部にデバイスコンテキストが用意され、 それの識別子(それを特定できる値)が返る、ということです。

EndPaint を呼ぶとシステムに描画を終了したことを知らせることが出来ます。これを呼ぶことで、 システム内部のデバイスコンテキストが解放されます。逆に言うと BeginPaint を呼んだ後、EndPaint を呼ばないと デバイスコンテキストが解放されません。必ず BeginPaint と EndPaint をペアで呼び出してください

DrawText は指定したデバイスコンテキストを使って、テキストを出力します。

パラメータの意味は下記、コメントとして書いたとおりです。


	DrawText( 
		hdc,                   // デバイスコンテキストのハンドル
		TEXT("Hello, world!"), // 出力する文字列
		-1,                    // 文字列長。-1 を指定すると文字列から数える
		&rect,                 // 描画する領域
		DT_CENTER | DT_VCENTER | DT_SINGLELINE
		                       // 縦横中央を指定  
	);

DrawText でスクリーンに文字を書いて終了です。

いかがでしょうか。プログラムは動きましたか?

さて、動かしてみてお気づきと思いますが、実はこれだけではウィンドウのリサイズ等を行ったときに対応できていません。 例えば、ウィンドウのサイズを小さく変えてみると次のようになってしまいます。


図3. リサイズに対応できていない

文字がクライアント領域の中央に来ていません。これは意図した動作と異なります。

リサイズに対応するために、ウィンドウプロシージャを下のように変更しましょう。


LRESULT CALLBACK WindowProc(
	HWND hwnd,
	UINT uMsg,
	WPARAM wParam,
	LPARAM lParam) {


	PAINTSTRUCT ps;
	HDC hdc;
	RECT rect;

	switch( uMsg ) {
	case WM_PAINT:
		hdc = BeginPaint ( hwnd, &ps );
		GetClientRect ( hwnd, &rect );
		DrawText( 
			hdc, 
			TEXT("Hello, world!"), 
			-1, 
			&rect, 
			DT_CENTER | DT_VCENTER | DT_SINGLELINE
		);
		EndPaint( hwnd, &ps );
		break;
	case WM_SIZE:
		InvalidateRect( hwnd, NULL, TRUE );
		break;
	case WM_DESTROY:
		::PostQuitMessage( 0 );
		break;
	default:
		return DefWindowProc(hwnd, uMsg, wParam, lParam);
	}

	return 0;
}

赤でマークした部分が、追加されています。

WM_SIZE メッセージはウィンドウサイズが変更されたときに送られてきます。このイベントを受け取ったときに、 クライアント領域の表示を無効にします。これによって、システムが無効になったクライアント領域を再描画するために、 WM_PAINT メッセージを送ります。そして WM_PAINT イベントハンドラで "Hello, world!" というテキストが書かれます。

このように、「再描画をするために、意図的にクライアントエリア全体または一部を無効にする」ということがしばしば行われますので、 覚えておいて下さい。

以上で、下図のようにリサイズに対応することが出来ました。

お疲れ様でした!


図4. リサイズに対応して文字が中央に表示されている

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説