ウィンドウの表示
メッセージ駆動型
2日目では、いよいよ本格的にウィンドウの表示についてとりあげます。ここでとりあげるサンプルプログラムは非常に長いものですが、大事な箇所は限られています。また、ここで出てくる「型」さえ理解できれば、ウィンドウを利用したアプリの作成は基本的に難しくありません。
ウィンドウアプリの仕組み
具体的なサンプルを取り上げる前に、まずはウィンドウアプリとOS(Windows)の関係性を説明しなくてはなりません。
Windowsに限らず、現在のほとんどのコンピュータのOSは、マルチタスクOSと呼ばれるものに分類されます。マルチタスクとは、複数の仕事を同時にこなす、という意味です。例えば、ウィンドウズでは、ウェブブラウザと、ワープロソフトと、メールソフトなどといったソフトを、同時並行に使うことができます。これが、マルチタスクということです。
Windowsは、これら複数のアプリケーションを管理しているわけですが、基本的にこれらアプリケーションはそれぞれ別々に動作しています。それらに対し、OSは、メッセージと呼ばれる指令を与えるわけです。たとえば、メールソフトが、特に操作をしなくても新着メールが来ると画面に表示され効果音がなったり、ウェブブラウザで動画投稿サイトで動画が出力されるのは、すべてウィンドウがこのメッセージを送っているからなのです。
このようなスタイルのアプリケーションのことを、メッセージ駆動型と言います。メッセージ駆動型のアプリの特徴は、OSから送られるメッセージによって処理内容が変わるということです。(図2-1.)
図2-1.マルチタスクとメッセージ駆動型
Windowsアプリケーションは、基本的には次のような処理を行っています。
ウインドウは常にメッセージが発生しないかどうかを、メッセージループで監視しています。これに対し、実際に何かメッセージが発せられた時に実行される関数を、ウィンドウプロシージャと呼びます。
キー操作や、マウス操作もまた、メッセージの一つです。例えば、マウスをウインドウ上でクリックすると、左クリックされましたよ!というメッセージが、ウインドウプロシージャへ送られます。メッセージを受け取った側に、あらかじめ左クリックした場合の処理が記述してあれば、それに応じた処理を行います。なければスルーします。
サンプルプログラム
では実際に簡単なウィンドウを表示するプログラムを見てみましょう。まずは以下のプロジェクトを実行してみてください。
win32proj2-1:WinMain.cppダウンロード
// ************************************ // Ex ウインドウ描画の雛形 // ************************************ //必要なヘッダーファイルのインクルード #define STRICT #include <windows.h> #include <stdlib.h> #include <string.h> #include <tchar.h> // シンボル定義及びマクロ #define WINDOW_WIDTH 800 #define WINDOW_HEIGHT 600 // インスタンス(グローバル変数) HINSTANCE hInst; // ウィンドウプロシージャのコールバック関数 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { static TCHAR szWindowClass[] = _T("Sample02"); static TCHAR szTitle[] = _T("ウィンドウを使ったアプリのサンプル①"); WNDCLASSEX wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); wcex.hCursor = LoadCursor(NULL, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wcex.lpszMenuName = NULL; wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_APPLICATION)); if (!RegisterClassEx(&wcex)) { MessageBox(NULL, _T("RegisterClassExの処理に失敗しました"), _T("Sample02"), NULL); return 1; } hInst = hInstance; // グローバル変数に値を入れる // The parameters to CreateWindow explained: // szWindowClass : アプリケーションの名前 // szTitle : タイトルバーに現れる文字列 // WS_OVERLAPPEDWINDOW : 生成するウィンドウのタイプ // CW_USEDEFAULT, CW_USEDEFAULT : 最初に置くポジション (x, y) // WINDOW_WIDTH, WINDOW_HEIGHT : 最初のサイズ (幅, 高さ) // NULL : このウィンドウの親ウィンドウのハンドル // NULL : メニューバー(このサンプルでは使用せず) // hInstance : WinMain関数の最初のパラメータ // NULL : WM_CREATE情報(このアプリケーションでは使用せず) HWND hWnd = CreateWindow( szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH, WINDOW_HEIGHT, NULL, NULL, hInstance, NULL ); // ウィンドウが生成できなかった場合 if (!hWnd) { MessageBox(NULL, _T("ウィンドウ生成に失敗しました!"), _T("Sample02"), NULL); return 1; } // ウィンドウの表示に必要なパラメータ: // hWnd : CreateWindowの戻り値 // nCmdShow : WinMainの引数の4番目 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // メインのメッセージループ: MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; } // ウィンドウプロシージャ(メッセージに対するコールバック関数) LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); break; } return 0; }
実行結果は、以下のようになります。(図2-2.)
図2-2.最も基本的なウィンドウの表示
このプログラムを実行すると、何も表示されていない非常に簡素なウィンドウが出現します。右上の赤い×ボタンを押すと、ウィンドウは消えて、プログラムは終了します。
プログラムの仕組み
ウィンドウの生成
WinMain関数内で最初にやることは、ウィンドウの生成と、メッセージループの生成です。ここでは、最初にウィンドウの生成から見てみましょう。ウィンドウ生成には、CreateWindow関数を利用します。その時、ウィンドウの概要を設定するための引数を与える必要があります。その引数として用いられるのが、WNDCLASSEXという構造体です。これは、以下のような構造になっています。
WNDCLASSEX構造体UINT cbSize; // 構造体のサイズ
UINT style; // クラスのスタイル
WNDPROC lpfnWndProc; // ウインドウプロシージャ
int cbClsExtra; // 補助メモリ
int cbWndExtra; // 補助メモリ
HINSTANCE hInstance; // インスタンスハンドル
HICON hIcon; // アイコン
HCURSOR hCursor; // カーソル
HBRUSH hbrBackground; // 背景ブラシ
LPCTSTR lpszMenuName;// メニュー名
LPCTSTR lpszClassName;// クラス名
HICON hIconSm; // 小さいアイコン
} WNDCLASSEX, *PWNDCLASSEX;
情報が多いため、ここで全てを説明できませんが、特に大事な点だけを説明しておくことにします。まずは、34行目を見てください。
ウィンドウプロシージャの定義ここでは、このプログラムのウィンドウプロシージャが、WndProcという名前の関数であることを示しています。これにより、ウィンドウからメッセージが来た場合、WndProcという関数が呼び出されます。他にも様々なパラメータがありますが、今のところ、これだけ理解できていれば十分です。これらのパラメータは、45行目のRegisterClassEx関数で登録されます。
続いて、ウィンドウの生成を見てみましょう。(67行目~77行目)
ウィンドウの生成szWindowClass, // アプリケーションの名前
szTitle, // タイトルバーに現れる文字列
WS_OVERLAPPEDWINDOW, // 生成するウィンドウのタイプ(オーバーラップウィンドウ)
CW_USEDEFAULT, CW_USEDEFAULT, // 最初に置くポジション (x, y)
WINDOW_WIDTH, WINDOW_HEIGHT, // 最初のサイズ (幅, 高さ)
NULL, // このウィンドウの親ウィンドウのポインタ
NULL, // メニューバー(このサンプルでは使用せず)
hInstance, // アプリケーションインスタンスのハンドル
NULL // WM_CREATE情報
);
CreateWindowでは、オーバーラップウィンドウ、ポップアップウィンドウ、子ウィンドウのいずれかを作成できます。ウィンドウの作成にあたっては、ウィンドウクラス、ウィンドウタイトル、およびウィンドウスタイルを指定します。必要に応じて、ウィンドウの初期位置と初期サイズも指定できます。親ウィンドウまたはオーナーウィンドウも指定でき、メニューを持たせる場合はメニューも指定します。
この関数の戻り値が、ウィンドウハンドルで、ウィンドウを操作するために必要となります。これを用いて、ウィンドウの表示を行います。(91行目~93行目)
ウィンドウの表示nCmdShow);
UpdateWindow(hWnd);
ウィンドウプロシージャ
続けて、ウィンドウのイベントの処理を行うウィンドウプロシージャを見てみましょう。このサンプルでは、ウィンドウプロシージャの関数WndProcは、20行目で宣言されています。
ウィンドウプロシージャの定義ここに出てくる、CALLBACKとは、この関数がコールバック関数であることを示しています。コールバック関数とは、イベント駆動アプリのように、何らかのイベントが起こった場合、外部から呼び出される特殊な関数です。
この関数が実装されているのが、106行目以下です。変数messageに、イベントのIDが引数として与えられています。頭文字にWM_とついているのが、ウィンドウメッセージを表す定数です。
メッセージをswitch文で処理しています。 WM_DESTROYメッセージ(ウィンドウ破棄メッセージ)が来たら PostQuitMessage関数によりWM_QUITを送り出しWinMain関数のループを終了させます。
以上で、ウィンドウの基本が完成しました。これに様々なものを追加していくことにより、アプリは完成します。次回は、ここに文字や絵などを表示する方法について説明します。