2012年6月18日月曜日

InitWindow (ウインドウの作成)

InitWinodw()の中でウインドウの作成をしています。つまりこの関数の中にウインドウの作成に不可欠な処理が書かれています。他の2つの関数は、関数自体がエントリーポイントとウインドウプロシージャという独立した不可欠なモジュールでしたが、このInitWindowは関数自体はユーザーの定義次第であって、例えば中身さえ丸々wWinMain()の中に書いてしまえばInitWindowという関数自体は不要となります。

InitWindow関数の始めに//Register class とコメントがあります。
「クラスを登録しろ」という意味です。
 クラスといってもC++のクラスを作れという意味ではありません。しかし概念的には鋳型を作るという意味で似ています。
Windowsでウインドウを作る時にはまずウインドウクラスというものを作る事でウインドウの性質を決定します。 ウインドウズではボタンの様なコントロールもウインドウと呼ばれる部品です。ですからウインドウクラスの設定次第ではボタンを作る事も可能です。しかしボタンなどはwindowsAPIに存在するあらかじめ定義されたウインドウクラスを使えばより簡単に作る事ができます。

ここでは普通のウインドウを作ります。
ウインドウクラスを作るには WNDCLASSEX という構造体にどの様なウインドウを作りたいか設定をして、その構造体をRegisterClassEx()関数でシステム側へ登録すればOKです。WNDCLASSEX構造体の中にウインドウクラスを表す名前もあるので、一旦ウインドウクラスを登録すれば後からウインドウを作る時にその名前によってウインドウクラスを指定出来ます。一つのウインドウクラスから複数のウインドウを作る事も出来ます。

さて、ウインドウクラスを作るための情報、WNDCLASSEX構造体のメンバを解説します。

UINT  cbSiz

この構造体のバイトサイズをsizeof(WNDCLASSEX)でセットします。

UINT style 

Window Class Stylesというものをビットフラグで指定します。


 設定次第で性質が違うウインドウを作れるっぽいです。日本語の説明としてはパッとググった感じではここ(の下の方にある解説)がナイスな印象でした。

チュートリアルでは
CS_HREDRAW | CS_VREDRAW ;
となっています。その意味は

CS_HREDRAW
ウインドウの移動やサイズ調節時にクライアントエリアの幅が変われば全体を再描画する。

CS_VREDRAW
ウインドウの移動やサイズ調節時にクライアントエリアの高さが変われば 全体を再描画する。

・・・となっています。普通のウインドウを作る場合はだいたいこれでいいらしいです。

個人的に注目しているのはCS_DBLCLKSとうフラグです。これを入れておけばウインドウをダブルクリックした時にウインドウプロシージャにメッセージが通知されます。するとウインドウプロシージャのスイッチの中で

case WM_LBUTTONDBLCLK;
 マウス左ボタン ダブルクリック時の処理

case WM_RBUTTONDBLCLK:
マウス右ボタン ダブルクリック時の処理を既述

と書くことが出来ます。ダブルクリックが必要なアプリケーションを作る人は重宝するでしょう。

WNDPROC  lpfnWndPro

ウインドウプロシージャへのポインタです。 ウインドウプロシージャとは最後に説明する関数WindProc()の事です。チュートリアルでは
wcex.lpfnWndProc = WndProc;
となっている部分です。関数の名前を書く事でその関数のポインタを指定しています。もし別の名前のウインドウプロシージャを使う場合はその名前を書く必要があります。

int  sExtra

ウインドウクラス構造体の末尾に余分なメモリを確保させる場合、そのバイト数を指定します。そんなの不要という人は0を入れて下さい。余分なメモリを確保させた場合、プログラマが目的に応じてその領域を使う事が出来ます。その際に使用する関数は Set(あるいはGet)ClassWord(あるいはLong)とかです。

int  cbWndExtra 

作成した個々のウインドウインスタンスの末尾にも余分なメモリを確保させせる事が出来ます。使わない場合は0を入れておきます。使う時はバイト数を指定します。確保したメモリを使う関数は Set(あるいはGet)WindowLong とかです。

HINSTANCE  hInstance

エントリーポイント(最初の関数 wWinMain)で渡されたインスタンスハンドル(hInstance)を指定します。このハンドルを元にシステムはどのアプリケーションやDLLのインスタンスがどのウインドウクラスやウインドウを作ったのかを知る事が出来ます。

HICON  hIcon

アプリケーションのアイコンを設定します。アイコンは実行ファイルの絵は勿論、ウインドウの左上やタスクバーにも表示されている奴です。
アイコンはwindowsプログラミングにおいてリソースとして扱われます。(Direct3Dでもリソースという用語がありますが、ここで述べるリソースとは違うものです。)一般的なウィンドウズプログラミングにおけるリソースは実行ファイルに組み込まれるデータのことです。アイコンの他にもメニューの情報など、GUI系のデータがリソースとして扱われたりします。アイコンの様な画像ファイル(つまりデータ)でも、実行ファイル.exeに組み込んでしまう事でプログラムを一つのファイルとして結実させる事が出来る訳です。
チュートリアルではこのメンバは
wcex.hIcon = LoadIcon( hInstance, ( LPCTSTR )IDI_TUTORIAL1 );
となっています。これでDirectXのアイコンが表示される仕組みなっています。

これを自分で描いた絵に差し替える手順は、次の様になります。
1 アイコン画像の用意
アイコンの画像の拡張子はicoです。このフォーマットの画像を作るにはそれが可能なドット絵ツールなどの画像編集系ソフトを使えば良いでしょう。私はEDGEを使いました。作った画像は拡張子が.icoとなるアイコン形式として保存します。
保存場所は VC++のプロジェクトファイルやらソースファイルが置かれているフォルダです。(設定次第なのかな?) DirectXサンプルブラウザにチュートリアル1を作らせた場合は「Tutorial01」という名前のフォルダの中にアイコンを置けば良いです。(ちなみに元からあるdirectx.icoというアイコンファイルもそこにあると思います。)

2 リソーススクリプト(リソース定義ファイル)の改造
1でアイコンを保存した場所に拡張子が.rcとなっているファイルがあると思います。チュートリアルではTutorial01.rcという名前のファイルです。これがリソーススクリプトです。これをメモ帳で開いて下さい。その中に

/////////////////////////////////////////
// Icon
//

// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.

IDI_TUTORIAL1 ICON "directx.ico"

//////////////////////////////////////
という箇所があると思います。
directx.icoの部分を自分で作ったアイコン名に差し替えてから保存します。

これでプロジェクトを改めてビルドすると、自分で作ったアイコンが使われているはずです。

ところでチュートリアルのヘッダに
#include "resource.h"
というものがあります。
このヘッダファイルの中にもリソースのIDとかが定義されていて私はよく分からんのですがVC++はこのヘッダファイルとリソーススクリプトの2つを使ってリソースを実行ファイルに組み込んでいるっぽいですね。

HCURSOR  hCursor

カーソルのリソースを指定します。 チュートリアルでは
wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
となっています。これでウインドウズで標準的に使用されている定義済みのカーソル (矢印) を利用できます。

HBRUSH  hbrBackground

ウインドウの背景色を指定します。GDIのブラシを使うかシステムカラーから指定します。チュートリアルでは
wcex.hbrBackground = ( HBRUSH )( COLOR_WINDOW + 1 );
となっています。これによってシステムカラーの中のウインドウ色を利用しています。
チュートリアルでは背景もDirect3Dで描画するので気にする必要がないでしょう。

LPCTSTR  lpszMenuName

アプリケーションに搭載させるメニューのリーソスを表す名前を指定します。 チュートリアルではメニューを使わないのでNULLを指定しています。

LPCTSTR  lpszClassName

作るクラスの名前をプログラマが任意で命名出来る文字列です。あとで説明するCreateWindow関数を使ってウインドウを作成する時、ここで登録した名前が必要になります。
一つのプロセスで何度も同じ名前のクラスを登録する事はできませんが、システムが元から定義しているボタンなどのクラス(いわゆるシステムクラス)と同じ名前を付けても構いません。その場合、システムクラスはオーバーライドされ、あなたが定義したクラスが優先的に使われる事になります。勿論、その場合でも他のアプリケーションにおいては通常のシステムクラスを使う事は阻害されません。

HICON  hIconSm

小さいアイコン用のリソースを指定します。(アイコンの大きさは、使われ方によって2種類あるのでプログラマも2種類の大きさのアイコン画像を用意する必要があります。 上で説明したhIconメンバは実は、大きいサイズのアイコンの設定だったと考えられます。 )これをNULLにしとくとシステムが勝手に大きいアイコン画像を利用したりして小さいアイコンを作るっぽいです。
ただし上のhIcomメンバで説明した様な手順でちゃんとしたアイコンファイルを一つ作っておけば、ここのメンバでも同じアイコンを指定してやればOKです。つまりhIconメンバと同じ値にしておけば良いです。ちゃんとしたアイコンファイルの中には各サイズに対応する画像が収められているからです。

--------------------

ふぅ・・・・・

構造体の情報を全部埋めたら
RegisterClassEx関数によって実際にシステムに対してクラスを登録します。 チュートリアルには
 if( !RegisterClassEx( &wcex ) )
    return E_FAIL;
とあります。万が一エラーが戻った場合はプログラムを終了させる流れとなります。

CreateWindow関数でウインドウの作成


登録したウインドウクラスの情報を元にいよいよ実際のウインドウの作成に入ります。
同じ名前のウインドウクラスは一つのプリケーションで一つしか作れません。しかし実際に作るウインドウはその一つのクラスを鋳型として複数作る事が出来ます。これはC++で言うクラスの型とそこから作るインスタンス(オブジェクト)の関係と似ています。 もちろん性質の違うウインドウを作りたい時は別の名前の、つまり別のウインドウクラスも作る必要がありますが、似た性質のウインドウ(例えば同じ様なボタンを並べる時は)、一つのウインドウクラスを複数回使い回す事になます。(・・・まぁチュートリアルでは一つのウインドウしか作りませんが。)
ウインドウを作成する時は CreateWindow()関数を使い、この引数の中で事前に登録した中で使いたいウインドウクラスの名前を指定します。その他の引数でウインドウをどこに表示させるか等のウインドウ個別の設定を付与します。

さて、チュートリアルのコードには
// Create window
RECT rc = { 0, 0, 680, 480 };
AdjustWindowRect( &rc, WS_OVERLAPPEDWINDOW, FALSE );
g_hWnd = CreateWindow(
L"TutorialWindowClass", // ウインドウクラス名
L"Direct3D 11 Tutorial 1: Direct3D 11 Basics", // ウインドウタイトル
WS_OVERLAPPEDWINDOW, // ウインドウスタイル
CW_USEDEFAULT, // X座標の初期値
CW_USEDEFAULT, // Y座標の初期値
rc.right - rc.left, // 幅の初期値
rc.bottom - rc.top, // 高さの初期値
NULL, // 親ウインドウのハンドル
NULL, // ウインドウメニューのハンドル
hInstance, // インスタンスハンドル
NULL ); // 作成パラメータ
if( !g_hWnd )
return E_FAIL;

ShowWindow( g_hWnd, nCmdShow ); //ウインドウを表示する

とあります。
コメントは私が付与したもんです。
説明はMSDNのCreateWindow()の解説にありますが、ここでも解説を加えます。

ウインドウクラス名
どのウインドウクラスに基づくウインドウを作るか指定します。
上で解説した RegisterClassEx()関数で登録した WNDCLASSEX構造体中のlpszClassNameと同じ名前を入れればOKです。

ウインドウタイトル
ウインドウ上部に表示される文字列を入れます。システムクラスのボタンなどを作る場合は、そのキャプションになります。

ウインドウスタイル
たかがウインドウの作成一つに設定の中にさらに設定項目がぎっしり・・・。まぁそれだけ選択の自由があるという事でしょうが・・・。
ともかく普通にタイトルがあるメインウインドウみたいなのを作りたい場合はWS_OVERLAPPEDWINDOWを指定しておけばOKです。このページ(外部リンク)にウインドウスタイルの一通りの解説があります。MSDNのページもありますね。ちなみに世の中にはCreateWindowEX関数というものもあって、こちらはさらに様々なウインドウスタイルを指定できます。ところで、ボタンコントール等、システムウインドウクラスに基づいてウインドウを作る場合、この項目はそのコントロールに特有のものを指定するためにも使われます。

X、Y座標の初期値
ウインドウの座標をウインドウの左上位置で指定します。この例の様にオーバーラップウインドウを作る場合はCW_USEDEFAULTを入れておくとシステムがデフォルトの位置を選んでくれるらしいです。・・・しかしシステムのデフォルトの位置って何処っすか?まぁよく使われているっぽいのでこれを指定しておけば悪い様にはならないでしょう。逆に安易にこのぐらいでいいのでは?と思って
(x = 200, y = 200)の座標を指定した場合、仮にユーザーが使っているモニターが200ドット未満しかない様な極小モニターだったら範囲外になる訳です。また、マルチモニター環境だとまたどういう値が正しいのか簡単には分からなかったりします(私が不勉強なんですが)。

幅、高さの初期値
ウインドウの幅と高さを指定します。
AdjustWindowRect()関数でクライアントエリアを求めています。チュートリアルにおいてはビデオカードに描画させたいピクセルサイズが幅640高さ480の長方形みたいですね。この値をそのままここの引数にセットするとウインドウ全体の大きさがそれになってしまい、実際に描画できるクライアントエリア(つまりウインドウのタイトルバーや枠などを取り除いた部分)はこれよりも若干小さい範囲となってしまいます。そこでこのAdjustWindowRect()関数を使って一旦、左上端が(0, 0)と仮定した場合のクライアント座標を求め、そこから幅と高さを計算しています。
なお、 ウィンドウの作成にCreateWindowEx 関数を使って拡張ウィンドウスタイルを指定する場合はAdjustWindowRectEx 関数を使ってクライアント座標を計算するらしいです。

親ウインドウのハンドル
アプリケーションでただ一つのウインドウを作りたい場合、NULLを指定すればOKです。
ところでCreateWindow()関数の戻り値は作ったウインドウのハンドル型となります。つまり新たに子ウインドウを作りたい場合は、この引数で親ウインドウを作った時の戻り値を指定してやれば良いのです。そうすることで親ウインドウのウインドウプロシージャに対してボタン(子ウインドウ)を押した時のメッセージなどが渡されます。

ウインドウメニューのハンドル
ウインドウに与えるメニューリソース ( よくアプリケーションの上部に並んでいるコマンド一覧 )へのハンドルを指定します。
ウインドウクラスを登録した時も構造体の中にメニューの項目がありました。ここで新たに付与した場合はこちらが優先になります。チュートリアルではメニューを使わないのでNULLにしてあります。

インスタンスハンドル

アプリケーションのインスタンスハンドルを入れます。インスタンスハンドルはwWinMain関数等のエントリーポイントでもらっている奴です。ちなみにMSDNに
Windows 95/98:ウィンドウに関連付けられたモジュールのインスタンスハンドルを指定します。

Windows NT/2000:このパラメータは無視されます。

とありましたので最近のOSでも意味は持たないのかも知れませんね。

作成パラメータ
ウインドウ固有の情報を指すポインタとかを入れておけます。チュートリアルでは使わないのでNULLです。


CreateWindow関数を呼んでエラーが戻って来なければウインドウ作成は完了です。しかしこのままではウインドウを作っただけで表示はしていないので

ShowWindow( g_hWnd, nCmdShow );
によってウインドウを表示しています。

0 件のコメント: