C++ Windows プログラミング② 書いて覚えるための初心者自己中記事
ウィンドウを作成する
ウィンドウクラスを作成する。
C++のクラスとは違う。
ウィンドウクラスには複数のウィンドウが共通して実行する一連の動作が定義される。
共通しない部分(各ウィンドウ固有のデータ)はインスタンスデータと呼ばれる。
ウィンドウクラスは実行時にシステムに登録される。
登録するにはまず、WNDCLASSという構造体に情報を入れていくらしい。
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
const wchar_t CLASS_NAME[] = L"Sample Window Class"; WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = hInstance; wc.lpszClassName = CLASS_NAME;
3つのメンバにいれてる。
lpfnWndProc
ウィンドウプロシージャと呼ばれるアプリケーション定義関数へのポインタ。
ウィンドウ動作の大部分がこのプロシージャで定義されるらしい。
ウィンドウプロシージャそのものは後で解説入るそう。
とりあえず、ウィンドウプロシージャへのポインタ。
hInstance
アプリケーションインスタンスへのハンドル。wWinMainのhInstance関数?から取得。
lpszClassName
ウィンドウクラスを識別する文字列。
情報をいれたWNDCLASSオブジェクトを
RegisterClass(&wc);
RegisterClass関数に渡す(アドレスを)。
これでウィンドウクラスがOSに登録される。
つぎに、ウィンドウの新しいインスタンスをCreateWindowEX関数で作成する。
HWND hwnd = CreateWindowEx( 0, // オプションのウィンドウ スタイル CLASS_NAME, // ウィンドウ クラス L"Learn to Program Windows", // ウィンドウ テキスト WS_OVERLAPPEDWINDOW, // ウィンドウ スタイル // サイズと位置 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // 親ウィンドウ NULL, // メニュー hInstance, // インスタンス ハンドル NULL // 追加のアプリケーション データ );
オプションのウィンドウスタイル 既定は0
ウィンドウクラス さっき登録したクラス名
ウィンドウテキスト タイトルバーがあると表示される
ウィンドウスタイル ウィンドウの外観を定義するフラグセット
(タイトルバー・境界・システムメニュー・最小化・最大化など)
サイズと位置 定数がある?
親ウィンドウ 最上位の場合はNULL
メニュー アプリケーションウィンドウのメニューを定義する
インスタンスハンドル ここでもhInstance
追加のアプリケーションデータ 任意データへのvoid型ポインタ(ウィンドウプロシージャに渡す。あとで解説有るらしい)
このCreateWindowEX関数は作成したウィンドウのハンドルを返す。
なので受け取る。作っても表示させなければ表示されない。
if (hwnd == NULL) { return 0; } ShowWindow(hwnd, nCmdShow);
CreateWindowEX関数は失敗時にNULLを返すのでチェック。
ShowWindow関数に作成したウィンドウハンドルを、
nCmdShowはwWinMain関数で引数になってたやつ、ウィンドウの最小化・最大化に関して使用。OSからwWinMain関数経由でここに来る。
よしよし、Windowsデベロッパーセンター分かりやすいかも。
つぎ、ウィンドウメッセージ
ユーザーによるイベント
マウスクリック・キー操作・タッチスクリーン・ジェスチャ
OSによるイベント
プログラムの外部から行われプログラムの動作に影響を与えるすべてのイベント
これらのイベントはプログラム実行中に発生する。
windowsはメッセージパッシングモデルというのを使用してアプリケーションにメッセージを渡すことで対話を実現している。
メッセージは特定のイベントを指定したシンプルな数値コード。
ユーザーがマウスの左ボタンをクリックした場合
#define WM_LBUTTONDOWN 0x0201
このメッセージコードを含むメッセージがウィンドウに渡される。
メッセージにはメッセージコードのほかにデータが渡る場合もあり、このWM_LBUTTONDOWN メッセージにはマウスカーソルのX座標Y座標が格納されている。
OSはメッセージを送らなければならないウィンドウのウィンドウプロシージャを呼び出す。
ウィンドウプロシージャはあれか、中継係か。
OSはメッセージ用のキューを作る。キューってデータ構造のキュー?
各スレッド(CPUの話?)ごとの全てのウィンドウに向けられるメッセージを保持する。キューがね。
GetMessage関数でキューからメッセージを取得できる。
MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0))
これによってキューの最初のメッセージが削除される。FIFOっぽいしやっぱりキューはキューか。
キューが空の場合は関数によってブロックされる。??
ブロックが原因でプログラムが非応答状態になるわけじゃない。
メッセージを格納するのはMSG構造体。
GetMessage関数の第一引数はこれ。
第二~第四までの引数はキューから取得するメッセージを絞り込むためだそう。基本0かNULL。
受け取ったメッセージが格納されているMSG構造体の中身は確認不可。
この構造体オブジェクトは2つの関数に渡すだけ。
TranslateMessage(&msg); DispatchMessage(&msg);
TranslateMessage関数はキーボード入力に関連付けられていて、入力されたキーを文字に変換する。次のDispatchMessage関数の直前に呼び出すルール。
DisoatchMessage関数はメッセージのターゲットウィンドウのウィンドウプロシージャを呼び出すようにOSに通知する。
なるほど、さっき言ってたOSがウィンドウプロシージャを呼び出すってのはDispatchMessage関数がそう通知してくれるからなのか。
MSG msg = {}; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); }
まとめてこうなる。通常GetMessage関数は0以外の数値を返している。
プログラムを終了するときはPostQuitMessage関数を呼び出す。(誰が?)
このPostQuitMessage関数はWM_QUITメッセージをキューに配置する。
このメッセージでGetMessage関数は0を返す。
ループが終了出来る。
メッセージの用語で区別に気を付けるものがある。
- メッセージの "ポスト": メッセージがメッセージ キューに入り、メッセージ ループを使ってディスパッチされます (GetMessage および DispatchMessage)。
- メッセージの "送信": メッセージがキューをスキップし、オペレーティング システムがウィンドウ プロシージャを直接呼び出します。
つぎ、ようやく、ウィンドウプロシージャ。
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
第一引数
HWND hwnd ウィンドウへのハンドル
第二引数
UNIT uMsg メッセージコード
第三・第四引数
WPARAM / LPARAM メッセージの追加情報
通常のウィンドウプロシージャはメッセージごとにswitchで切り替えるつくり
switch (uMsg) { case WM_SIZE: // ウィンドウのサイズ変更を処理 // およびその他 }
WPARAM / LPARAM はuMsgごとに異なる。
MSDN(?)でメッセージコードを確認して正しいデータ型にキャストする必要がある。らしい。え?
どういうことだろう?
普通のメッセージじゃない場合ってことかな。
チェックボックスにチェックをしてボタン押したとか?
まだよくわからないな。
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch (uMsg) { case WM_SIZE: { int width = LOWORD(lParam); // 下位ワードを取得するマクロ int height = HIWORD(lParam); // 上位ワードを取得するマクロ // メッセージに応答: OnSize(hwnd, (UINT)wParam, width, height); } break; } } void OnSize(HWND hwnd, UINT flag, int width, int height); { // サイズ変更を処理 }
例としてWM_SIZEというメッセージではこんな風に値を取り出してキャストしてると。
既定のメッセージ処理
ん?書いてることが理解できない。
ウィンドウ プロシージャで特定のメッセージを処理しない場合は、メッセージ パラメーターを直接 DefWindowProc 関数に渡し、この関数がメッセージに対して既定の動作を実行します (内容はメッセージの種類ごとに異なります)。
return DefWindowProc(hwnd, uMsg, wParam, lParam);
ウィンドウプロシージャの実行中は同スレッドで作成されたウィンドウに対するメッセージがブロックされる。
このためウィンドウプロシージャの処理が長時間になることを避けなければならない。閉じるとかもできないらしい。
なのでこれらの処理は別のスレッドにする方法があるらしい。
- 新しいスレッドを作成する。
- スレッド プールを使用する。
- 非同期 I/O 呼び出しを使用する。
- 非同期プロシージャ コールを使用する。
まぁ、おいおいだな。これは。
来た、ウィンドウに描画する。
ウィンドウの描画は
プログラムが描画の操作を開始してウィンドウの外観を更新する場合。
OSがウィンドウの一部を再描画するように通知する場合。
があるらしい。
後者の場合、OSはWM_PAINT というメッセージをウィンドウに送信する。
あ、送信だからキューをスキップするやつか。
ウィンドウの描画が必要とされる部分は更新領域というらしい。
ウィンドウを最初に表示する場合はクライアント領域全体に描画が必要。
なので?ウィンドウを表示する際は少なくとも1回はWM_PAINTを受け取ることになる。
クライアント領域の描画が完了したら更新領域(描画が必要な部分)はクリアされる。
これにより変更が発生しない限りWM_PAINTの送信は必要ないということがOSに通知される。
変更とは、ウィンドウの形を変えたり別のウィンドウが前面にかかってきて見えない部分が発生して、その後また見えたときなど。
switch (uMsg) { case WM_PAINT: { PAINTSTRUCT ps;//描画要求に関する情報を格納する構造体 HDC hdc = BeginPaint(hwnd, &ps);//BeginPaint 描画操作開始 FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1)); //最新の更新領域はrcPaintで指定される EndPaint(hwnd, &ps); } return 0; }
HDCってなんだ・・・?
説明なしか。
つぎ。ウィンドウを閉じる。
ウィンドウを閉じる。閉じるボタンを押したりCtrl + F4 を押したり。
これらの動作でウィンドウはWM_CLOSEメッセージを受け取る。
ウィンドウを閉じる前にプロンプト?あの「閉じていいですか?」的なやつか。
最終的に閉じるときはDestroyWindow関数を呼ぶ。
case WM_CLOSE: if (MessageBox(hwnd, L"Really quit?", L"My application", MB_OKCANCEL) == IDOK) { DestroyWindow(hwnd); } // Else: ユーザーがキャンセルし、何もしない return 0;
つぎ、アプリケーションの状態を管理する。
CreateWindowEX関数を使用すると、あらゆるデータ構造をウィンドウに渡せるようになる。この関数を呼び出すとウィンドウプロシージャに2つのメッセージが送られる。
この順番で送信される。
このメッセージはウィンドウが表示される前に送信されるのでUIの初期化に使用できる。
CreateWindowEXの引数の末尾はvoid*
このポインタにあらゆるポインター値を渡せる。
このvoid* が上記のWM_NCCREATE と WM_CREATEに入っているらしい。
なので状態情報を保持するクラス、または構造体を用意して
StateInfo *pState = new (std::nothrow) StateInfo; if (pState == NULL) { return 0; } // 構造体メンバーを初期化する (省略) HWND hwnd = CreateWindowEx( 0, // オプションのウィンドウ スタイル CLASS_NAME, // ウィンドウ クラス L"Learn to Program Windows", // ウィンドウ テキスト WS_OVERLAPPEDWINDOW, // ウィンドウ スタイル // サイズと位置 CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, // 親ウィンドウ NULL, // メニュー hInstance, // インスタンス ハンドル pState // 追加のアプリケーション データ );
ポインタを最後につける。
lParamはCREATESTRUCT構造体?へのポインタになる。
CREATESTRUCT構造体はCreateWindowEXに渡されたvoid*を保持する。
??
ウィンドウプロシージャでlParamをCREATESTRUCTにキャストする。
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
そしたら今度は、CREATESTRUCT構造体のメンバでlpCreateParamsはCreateWindowEXにのせたvoid* つまり最初に用意した構造体を指している。
のでまたキャスト
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
そしたらSerWindowLongPtr関数を呼び出す。ポインターに渡す。?
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
これでポインターをウィンドウのインスタンスデータに格納したことになるらしい。Setなので。
GetWindowLongPtr関数でこのポインターをウィンドウから取得できるようになる。
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA); StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
だんだん説明が乗ってきてるな。ついていけなくなってきた。
状態情報の構造体って具体的になんなのかな。
ウィンドウ側でその構造体へのポインタが格納されたとして、なんなのかな?
レベルに追いつけない。明日もう一回トライしよう。
ここまで。