第二回目はウィンドウの作成方法について説明します
次のような至ってシンプルなウィンドウを作ってみましょう
&ref(windowsample.GIF);

まず、以下の用語は
ウィンドウ作成において
非常に重要ですので覚えてください
SIZE(30){ COLOR(#ffaa00){・ウィンドウクラス}}
SIZE(30){ COLOR(#ffaa00){・ウィンドウスタイル}}
SIZE(30){ COLOR(#ffaa00){・ウィンドウプロシージャ}}

ウィンドウクラスとウィンドウスタイルは
ウィンドウの性質を決めます
この2つを変更することで普通のウィンドウから、ボタン、エディットコントロールなど
さまざまな種類のウィンドウを作成することができます

ウィンドウプロシージャとは
ウィンドウに送られてくるメッセージを処理する関数のことです
ウィンドウズプロシージャでの処理を記述することで
さまざまなメッセージに対応することができます

では、早速作ってみましょう
-----------------------------------
Win32プロジェクトを選択します
(実はWin32コンソールでも作成できます、このやり方については後述します)

&ref(project.GIF);

「次へ」を押して進んでください(完了を押してはいけません)

&ref(project2.GIF);

・Windowsアプリケーションにチェックがついているか確認して
・空のプロジェクトを選んでください
(空のプロジェクト以外を選ぶと余計なヘッダーファイルが作成されてしまいます)

&ref(project3.GIF);

次に、main.cppを作ります
コンソールの時と同じように作ります
&ref(project4.GIF);
&ref(project5.GIF);

main.cppができたら
まず、次のコードを記述してください

	#include <tchar.h>
	#include <windows.h>
	#include <windowsx.h>
    
	int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
	{
		MessageBox(NULL,_T("Hello World"),_T("Msg Box"),MB_OK);
		return 0;
	}

次のような、メッセージボックスがでれば成功です
&ref(msgbox.GIF);

ここで気づいてほしいことがあります
main関数がありません
その代わりにWinMain関数が使われています
WindowsアプリケーションではWinMain関数がアプリケーションの開始となる関数です
戻り値、引数も必ず上記のように書かなければなりません

また、起動してみるとコンソール画面(黒画面)は表示されません
WinMain関数から開始するとコンソール画面は隠蔽されてしまいます
(コンソール画面を出す方法は後述)

この関数でまず、いきなり変だと感じると思うのは__stdcallと書かれている部分です
これは呼び出し規約と言われて
関数を呼び出すときの仕組みです
普段コンソールで作っている関数は暗黙のうちに
__cdeclという呼び出し規約になっていますが
ウィンドウズの関数は基本的に__stdcallという呼び出し規約に従います
それ以上のことはあまり考えなくてもよいと思うので詳しい説明は割愛させてもらいます

ではWinMain関数の引数について説明します
HINSTANCEはインスタンスハンドルを指します
SIZE(30){ COLOR(#ffaa00){インスタンスハンドルはウィンドウを作るときに必要です}}
インスタンスハンドルというのは
OSがアプリケーションを識別するためのハンドル(ポインタ)です
アプリケーションが開始するとOSからインスタンスハンドルが引数として送られてきます。
プロセス毎に違うハンドル割り当てられるので
同じアプリケーションでも二つ同時に起動するとそれぞれ違うインスタンスハンドルが割り当てられます。
インスタンスハンドルは第1引数と第2引数の二つありますが使うのは
最初の引数のインスタンスハンドル(hInstance)のみです
第2引数のインスタンスハンドル(hPrevInstance)は使いません(これはWin16時代の名残です)

第3引数(lpCmdLine)はコマンドライン引数を格納してる文字列です
第4引数(nShowCmd)はコマンドライン引数の個数です
コンソールの表示はされませんが
コンソールから直接起動したりバッチファイルを使うことで
コンソールアプリケーション同様、引数を受け取ることは可能です

では、
シンプルなウィンドウを作ってみましょう
コード量がかなりあるので最初は戸惑うかも知れません
ただ、やっている処理の内容は次の3つだけです

SIZE(20){ COLOR(#00aaff){・ウィンドウクラスを定義し、登録}}
SIZE(20){ COLOR(#00aaff){・ウィンドウを作成}}
SIZE(20){ COLOR(#00aaff){・メッセージループを回す}}

ゆっくり順番に見ていけば大丈夫です

	// ウィンドウズバージョン指定
	#define _WIN32_WINNT 0x0500
 
	#include <stdio.h>
	#include <stdlib.h>
	#include <tchar.h>
	#include <windows.h>
	#include <windowsx.h>
	#include <commctrl.h>
	#pragma comment(lib,"comctl32.lib")
	#pragma comment(lib,"winmm.lib") 
   
	// ウィンドウプロシージャ
	LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
 
	int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)
	{
		const TCHAR* szClassName = _T("Basic");	// ウィンドウクラス名
 
		/********************************************************/
		/*		ウィンドウクラスの定義と作成		*/
		/********************************************************/
 
		// 新規ウィンドウクラスの定義
		WNDCLASSEX wc;
		wc.cbSize = sizeof(WNDCLASSEX);				// ウィンドウクラスのサイズ
		wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;	// ウィンドウクラスのスタイル	
		wc.lpfnWndProc = WndProc;				// ウィンドウプロシージャへの関数ポインタ
		wc.cbClsExtra = 0;					// 0(使わない)
		wc.cbWndExtra = 0;					// 0(使わない)
		wc.hInstance = hInstance;				// インスタンスハンドル
		wc.hIcon = (HICON)LoadImage(NULL,
			MAKEINTRESOURCE(IDI_APPLICATION),
			IMAGE_ICON,
			0,
			0,
			LR_DEFAULTSIZE | LR_SHARED);			// ウィンドウのアイコン
		wc.hCursor = (HCURSOR)LoadImage(NULL,
			MAKEINTRESOURCE(IDC_ARROW),
			IMAGE_CURSOR,
			0,
			0,
			LR_DEFAULTSIZE | LR_SHARED);			// ウィンドウのカーソル
		wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);	// ウィンドウ背景色(再描画のとき、この設定ブラシで塗りつぶしが行われる)
		wc.lpszMenuName = NULL;					// メニュー名
		wc.lpszClassName = (LPCTSTR)szClassName;		// ウィンドウクラス名
		wc.hIconSm = (HICON)LoadImage(NULL,
			MAKEINTRESOURCE(IDI_APPLICATION),
			IMAGE_ICON,
			0,
			0,
 			LR_DEFAULTSIZE | LR_SHARED);// 小さいアイコンが設定された場合の情報を記述
 
		// ウィンドウクラスの登録
		if(!RegisterClassEx(&wc))
			return -1;	// 登録失敗
 
		/********************************************************/
		/*		ウィンドウの作成			*/
		/********************************************************/
 
		HWND hWnd = CreateWindowEx(
        		 	0,			//拡張ウィンドウスタイル
				szClassName,		//ウィンドウクラス名
				_T("タイトル"),		//タイトルバーにこの名前が表示されます
				WS_OVERLAPPEDWINDOW,	//ウィンドウスタイル
				CW_USEDEFAULT,		//X座標
				CW_USEDEFAULT,		//Y座標
				CW_USEDEFAULT,		//幅
				CW_USEDEFAULT,		//高さ
				NULL,			//親ウィンドウのハンドル、親を作るときはNULL
				NULL,			//メニューハンドルorリソースID
				hInstance,		//インスタンスハンドル
				NULL);
 		
		// ウィンドウ作成失敗
		if (hWnd == NULL)
			return -1;
 
		// ウィンドウを可視状態にする
		ShowWindow(hWnd, SW_SHOW);
		UpdateWindow(hWnd);
 
	/********************************************************/
	/*		メッセージループ			*/
	/********************************************************/
		MSG msg = {}; 
		while(msg.message != WM_QUIT) {
			if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
				TranslateMessage(&msg);
				DispatchMessage(&msg);
			}
			else{
				// メッセージ処理をしてないとき
			}
		}
		return 0;
	}
  
	// ウィンドウプロシージャ
	LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		switch (msg) {
			case WM_DESTROY:
				PostQuitMessage(0);
				return 0;
		}
		return (DefWindowProc(hWnd, msg, wParam, lParam));
	}

次のように出てくれば成功です
&ref(window.GIF);

正直、冗長な部分も多いと思いますので
特に大事な部分に絞って説明します
それ以外の設定はこのサンプル通りに使ってくれればよいと思います
上から見ていきましょう

 // ウィンドウプロシージャ
 LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
これはウィンドウに送られてきたメッセージを処理するための関数です
処理の中身は後述。

メイン関数です
 int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nShowCmd)

WINAPI、CALLBACKは__stdcallが#defineで置き換えられているだけです(ウィンドウズの関数なので__stdcall)

WNDCLASSEX構造体の変数に色々代入し、
RegisterClassEx関数でWNCLASSEX構造体を登録してます
WNDCLASSEX構造体のいくつか重要な変数を説明しておきます

CS_HREDRAW、CS_VREDRAWは
ウィンドウのサイズが変わった時に再描画するかのフラグです
CS_DBLCLKSはダブルクリックしたときのメッセージを受け付けるかの設定です
 wc.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;		// ウィンドウクラスのスタイル	
SIZE(30){ COLOR(#ffaa00){ウィンドウへのメッセージを処理するプロシージャをセットします}}
関数ポインタの代入なのでグローバル関数もしくはクラスの静的関数でなければなりません
 wc.lpfnWndProc = WndProc;					// ウィンドウプロシージャへの関数ポインタ
インスタンスハンドルをセットします
 wc.hInstance = hInstance;					// インスタンスハンドル

アイコンとカーソルをセットします
LoadImage関数はビットマップ(.bmp)、アイコン(.ico)、カーソル(.cur)の読み込みができます 
 wc.hIcon = (HICON)LoadImage(NULL,
	    MAKEINTRESOURCE(IDI_APPLICATION),
			IMAGE_ICON,
			0,
			0,
			LR_DEFAULTSIZE | LR_SHARED);		// ウィンドウのアイコン
 wc.hCursor = (HCURSOR)LoadImage(NULL,
			MAKEINTRESOURCE(IDC_ARROW),
			IMAGE_CURSOR,
			0,
			0,
			LR_DEFAULTSIZE | LR_SHARED);		// ウィンドウのカーソル
ウィンドウの背景色をセットします
今回は白色ブラシで塗りつぶし
ここをGRAY_BRUSHにするとグレー色で塗りつぶしになります
 wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);	// ウィンドウ背景色(再描画のとき、この設定ブラシで塗りつぶしが行われる)
メニューをセットします(ウィンドウを作成するときにもセットできるのでNULLで構いません)
 wc.lpszMenuName = NULL;					// メニュー名
SIZE(30){ COLOR(#ffaa00){ウィンドウクラス名をセットします}}
"BUTTON"(後述)など予め定義されたウィンドウクラス名は使ってはいけません
 wc.lpszClassName = (LPCTSTR)szClassName;			// ウィンドウクラス名

ウィンドウクラスを登録します
 // ウィンドウクラスの登録
 if(!RegisterClassEx(&wc))
   return -1;	// 登録失敗

ウィンドウクラスに基づいて
ウィンドウを作成します
 HWND hWnd = CreateWindowEx(
        			0,			//拡張ウィンドウスタイル
				szClassName,		//ウィンドウクラス名
				_T("タイトル"),		//タイトルバーにこの名前が表示されます
				WS_OVERLAPPEDWINDOW,	//ウィンドウスタイル
				CW_USEDEFAULT,		//X座標
				CW_USEDEFAULT,		//Y座標
				CW_USEDEFAULT,		//幅
				CW_USEDEFAULT,		//高さ
				NULL,			//親ウィンドウのハンドル、親を作るときはNULL
				NULL,			//メニューハンドルorリソースID
				hInstance,		//インスタンスハンドル
				NULL);

ここで重要なのは
ウィンドウクラス名とウィンドウスタイルです
ウィンドウクラス名で登録されたウィンドウクラスを指定します
ボタンなどを作る場合は"BUTTON"などの予めデフォルトで登録済みのウィンドウクラス名を使います
ウィンドウスタイルはWS_OVERLAPPEDWINDOWとしていますが
これは最小化ボタン、最大化ボタン、閉じるボタンが付いていて、ウィンドウのサイズも
ウィンドウの枠をドラッグすることで変えられます
ほかにも色々なスタイルが存在します
ウィンドウズスタイルのフラグは
「WS_なんとか」の形で全て書かれています
(追々に説明していきます)

第1引数の拡張ウィンドウスタイルはウィンドウスタイルの拡張です
使わないときは0を指定します
(半透明ウィンドウを作成するときに設定したりします)

X座標、Y座標、幅、高さに
CW_USEDEFAULTを入れると
適当な位置に適当な大きさでウィンドウを作成してくれます
指定したい場合はここに整数値をいれます

第9引数は
親ウィンドウがある場合は親ウィンドウのハンドルを指定します
親がいない場合はNULLを指定します
(コントロールなどは指定)

第10引数は
メニューハンドルまたはリソースIDを指定します
リソースIDはコントロールなどの作成時に指定して
コントロールの識別に使います
(コントロール、メニューに関してはまた今度)

第11引数はインスタンスハンドルを指定します
最後の引数はNULLです

ウィンドウを作成したら
メッセージループを作ります
メッセージを取得し、ウィンドウプロシージャに処理させます

	MSG msg = {}; 
	while(msg.message != WM_QUIT) {
		if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)){
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else{
			// メッセージ処理をしてないとき
		}
	}

ウィンドウズのメッセージは
「WM_なんとか」の形で全て書かれています
WM_QUITというメッセージが送られていなければループを続行します
 while(msg.message != WM_QUIT) 

PeekMessage関数でメッセージキューからメッセージを取得します
TranslateMessage関数はアクセラレータキーなどを変換します(ここでは使っていませんが)
DispatchMessage関数はウィンドウの対応するウィンドウプロシージャにメッセージを転送します

メッセージの処理はウィンドウプロシージャで書きます

	// ウィンドウプロシージャ
	LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
	{
		switch (msg) {
			case WM_DESTROY:
				PostQuitMessage(0);
				return 0;
		}
		return (DefWindowProc(hWnd, msg, wParam, lParam));
	}

msgにウィンドウに送られたメッセージが入ります
たとえばウィンドウで左マウスボタンが押されたりしたら
WM_BUTTONDOWNというメッセージが送られてきます
特にデフォルトの処理でいい場合は
DefWindowProc関数を呼び出してその戻り値を返します
LRESULT型は整数型の一種です

今回はウィンドウが破棄されたときにメッセージループを抜けるために
PostQuitMessage関数を呼び出し、WM_QUITメッセージをメッセージキューに格納します
引数には0を指定します
メッセージを自前で処理する場合、大抵の場合は0を返します
(違う場合もある)

残りのwParam,lParamにはメッセージに応じたパラメータが入っています
メッセージに応じてキャストして使います
メッセージクラッカーを使えば、パラメータを明示的に取り扱うことができます
(メッセージクラッカーについてはまた今度)
 
かなり長くなってしまいましたがウィンドウズプログラミングにおいて
基本的な重要な部分はほとんどここに詰まっています
(なのでここが理解できれば、後は楽だと思います)

あと、基本的なウィンドウを作る場合は長々と書きましたが
コントロールを作るときなどは予め用意されたウィンドウクラスが登録されているので
ウィンドウクラスの定義、登録などのめんどくさいことはしなくてよいです

また、ウィンドウは一度作ったあと(既存のウィンドウ)でも
ウィンドウハンドルさえあれば
そのウィンドウのウィンドウクラスの定義やウィンドウスタイルを変更させることが実はできます
(たとえば、スタートアップボタンの機能を変更したり、ブラウザのコントロールいじくったり)
この辺がWin32APIで直接いじれる面白いところだと思います
この方法についても後々記述したいと思います

#vote((^ω^)やったお[4],何これwww意味不すぎwww[32],。(`ω´#)。あぁん?最近、だらしねぇな[129])
|(^ω^)やったお|4|
|何これwww意味不すぎwww|32|
|。(`ω´#)。あぁん?最近、だらしねぇな|129|



トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS