ウィンドウ

ウィンドウの概要

ウィンドウの構成

次の図は,代表的なウィンドウの構成を示したものです。

ウィンドウの構成

ウィンドウ枠内の白く塗り潰された部分は,クライアント領域 (client area) と呼ばれます。
ウィンドウで表示すべきコンテンツは,主にクライアント領域に描画します。

次の図は,各種コントロールを持つウィンドウです。

コントロール

エディットコントロール,ボタンコントロールのような,ウィンドウの部品として設計されたウィンドウのことを,コントロール (control) と言います。
コントロールはウィンドウの一種であり,普通のウィンドウを作るのと同じ関数で作成することができます。

### ウィンドウ間の主従関係

ウィンドウ間の主従関係には,次の 2 通りの関係があります。

親ウィンドウと子ウィンドウの関係は,ウィンドウ間に所有関係があり,子ウィンドウが親ウィンドウのクライアント領域に描画される関係を言います。
エディットコントロールやボタンコントロールといったコントロールは,代表的な子ウィンドウです。

親ウィンドウと子ウィンドウ

一方のオーナーウィンドウとオーナー付きウィンドウの関係は,ウィンドウ間に所有関係があり,オーナー付きウィンドウの描画位置がオーナーウィンドウから自立している関係を言います。
ダイアログボックスやメッセージボックスは,代表的なオーナー付きウィンドウです。

オーナーウィンドウ

ウィンドウの種類

Windows におけるウィンドウは,ウィンドウスタイルによって主に次の 3 つに分類できます。

次の図のような,いわゆる一般的なスタイルを持つウィンドウは,オーバーラップウィンドウと呼ばれます。
本来,オーバーラップという言葉はウィンドウの配置方式を指す言葉ですが,ここでは配置方式は関心事ではありません。

オーバーラップウィンドウ

ポップアップウィンドウは,メッセージボックスやダイアログボックスに代表されるスタイルを持つウィンドウです。

ポップアップウィンドウ

子ウィンドウについては先に説明した通りです。
エディットコントロールやボタンコントロールといったコントロールは,代表的な子ウィンドウです。

座標系

Windows プログラミングでは次の 2 つの座標系がよく使われます。

座標系

ウィンドウの作成

ウィンドウ作成の概要

ウィンドウの作成は,次のようなステップで行います。

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 1. ウィンドウの雛型を登録する

    // 2. 雛型を元にウィンドウを作成する

    // 3. ウィンドウを表示する

    // 4. ウィンドウを維持管理する
}

ステップ 1-3 は,次の関数の呼び出しが対応します。

  1. RegisterClass 関数
  2. CreateWindow 関数
  3. ShowWindow 関数,UpdateWindow 関数

ステップ 4 の部分には,メッセージループと呼ばれる while ループを記述します。

今回は手始めに,ステップ 1-3 だけを行うプログラムを作成します。

作例

ウィンドウを作成する最初のプログラムです。

作例

#include <windows.h>

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;

    // ウィンドウクラスの属性を設定
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = DefWindowProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    // ウィンドウクラスを登録
    if (!RegisterClass(&wc)) return 0;

    // ウィンドウを作成
    hwnd = CreateWindow(
        szAppName, TEXT("Title"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    // ウィンドウを表示
    ShowWindow(hwnd, nCmdShow);

    // ウィンドウを再描画
    UpdateWindow(hwnd);

    MessageBox(hwnd, TEXT("ウィンドウを生成しました。"), TEXT(""), MB_OK);

    return 0;
}

最後のメッセージボックスがストッパーの役割をしており,メッセージボックスを閉じればすぐにウィンドウは消えてしまいます。
ウィンドウを継続的に表示するには,次の章で紹介するメッセージループを導入する必要があります。

### ウィンドウクラスの登録

ウィンドウを作成するのに先立って,ウィンドウクラス (window class) と呼ばれる,ウィンドウの雛型を Windows に登録する必要があります。

ウィンドウクラスの登録自体は RegisterClass 関数の呼び出しという簡単なお仕事です。

RegisterClass 関数 [MSDN]

ウィンドウクラスを登録します。

ATOM RegisterClass(
    CONST WNDCLASS *lpwc  // WNDCLASS 構造体
);
返り値
登録されたウィンドウクラスを一意に示す整数値が返ります。クラスの登録に失敗すると 0 が返ります。

RegisterClass 関数を実行するためには,WNDCLASS 構造体にウィンドウクラスの属性を設定する必要があります。

WNDCLASS 構造体 [MSDN]

ウィンドウクラスの属性を表します。

typedef struct tagWNDCLASS {
    UINT    style;          // スタイル
    WNDPROC lpfnWndProc;    // ウィンドウプロシージャ
    int     cbClsExtra;     // 追加領域
    int     cbWndExtra;     // 追加領域
    HANDLE  hInstance;      // インスタンスハンドル
    HICON   hIcon;          // アイコン
    HCURSOR hCursor;        // カーソル
    HBRUSH  hbrBackground;  // 背景
    LPCTSTR lpszMenuName;   // メニュー名
    LPCTSTR lpszClassName;  // クラス名
} WNDCLASS;
style
ウィンドウクラスのスタイルを指定します。次の表に示す値はビット論理和で組み合わせることができます。

ウィンドウクラススタイル [MSDN]

説明
CS_VREDRAW垂直方向のサイズが変更されたとき再描画
CS_HREDRAW水平方向のサイズが変更されたとき再描画
CS_DBLCLKSダブルクリックを使用
CS_OWNDC各ウィンドウが独自のデバイスコンテキストを所有
CS_CLASSDCウィンドウクラスがデバイスコンテキストを所有
CS_PARENTDC子ウィンドウを親ウィンドウ上で描画
CS_NOCLOSE閉じるボタンを無効化
CS_SAVEBITSウィンドウにより隠される部分をビットマップに保存
CS_BYTEALIGNCLIENTクライアント領域をバイト境界に配置 (水平方向)
CS_BYTEALIGNWINDOWウィンドウをバイト境界に配置 (水平方向)
CS_GLOBALCLASSアプリケーショングローバルクラスとする
lpfnWndProc
ウィンドウプロシージャと呼ばれる関数を指定します。→ ウィンドウプロシージャ で説明
cbClsExtra
ウィンドウクラス構造体の後に確保する領域のバイト数を指定します。
cbWndExtra
ウィンドウインスタンスの後に確保する領域のバイト数を指定します。
hInstance
lpfnWndProc で指定した関数が含まれるインスタンスへのハンドルを指定します。
hIcon
アイコンへのハンドルを指定します。
NULL を指定した場合,既定のアイコンが適用されます。
例えば,LoadIcon(NULL, ○○○) の形で,次のようなシステム定義のアイコンを指定できます。

システムアイコン [MSDN]

説明
IDI_APPLICATIONアプリケーション既定
IDI_HAND
IDI_ERROR
エラー
IDI_QUESTION問合せ
IDI_EXCLAMATION
IDI_WARNING
警告
IDI_ASTERISK
IDI_INFORMATION
情報
IDI_WINLOGOWindows ロゴ
IDI_SHIELDシールド
hCursor
マウスカーソルへのハンドルを指定します。
NULL を指定した場合,マウスカーソルの描画はアプリケーションの責任となります。
例えば,LoadCursor(NULL, ○○○) の形で,次のようなシステム定義のカーソルを指定できます。

システムカーソル [MSDN]

説明
IDC_ARROW矢印 矢印
IDC_IBEAMアイビーム アイビーム
IDC_WAIT砂時計 砂時計
IDC_CROSS十字 十字
IDC_UPARROW上向き矢印 上向き矢印
IDC_SIZENWSE左上/右下向き矢印 左上/右下向き矢印
IDC_SIZENESW右上/左下向き矢印 右上/左下向き矢印
IDC_SIZEWE左右向き矢印 左右向き矢印
IDC_SIZENS上下向き矢印 上下向き矢印
IDC_SIZEALL全方向向き矢印 全方向向き矢印
IDC_NO禁止 禁止
IDC_HAND手
IDC_APPSTARTING矢印と砂時計 矢印と砂時計
IDC_HELP矢印と疑問符 矢印と疑問符
hbrBackground
ウィンドウの背景を描画するブラシへのハンドル,もしくはシステムカラーを表す値を指定します。
NULL を指定した場合,背景の描画はアプリケーションの責任となります。
システムカラーを指定するには,(HBRUSH) (○○○ + 1) のように記述します。システムカラーを指定する際には,定数に 1 を加えた値を指定しなければなりません。

システムカラー [MSDN]

説明
COLOR_SCROLLBARスクロールバーの軸
COLOR_BACKGROUNDデスクトップ
COLOR_ACTIVECAPTIONアクティブウィンドウのタイトルバー
COLOR_INACTIVECAPTION非アクティブウィンドウのタイトルバー
COLOR_MENUメニューの背景
COLOR_WINDOWウィンドウの背景
COLOR_WINDOWFRAMEウィンドウの枠
COLOR_MENUTEXTメニューのテキスト
COLOR_WINDOWTEXTウィンドウのテキスト
COLOR_CAPTIONTEXTタイトルバーのテキスト
COLOR_ACTIVEBORDERアクティブウィンドウの境界
COLOR_INACTIVEBORDERインアクティブウィンドウの境界
COLOR_APPWORKSPACEMDI アプリケーションの背景
COLOR_HIGHLIGHT選択項目
COLOR_HIGHLIGHTTEXT選択項目のテキスト
COLOR_BTNFACEボタンの表面
COLOR_BTNSHADOWボタンの影
COLOR_GRAYTEXT無効状態のテキスト
COLOR_BTNTEXTボタンのテキスト
lpszMenuName
メニューのリソース名を指定します。
メニューを用意しない場合は NULL を指定します。
lpszClassName
ウィンドウクラスに割り当てる名前,もしくはアトム値を指定します。

hbrBackground にシステムカラーを指定する際には,定数に 1 を加えた値を指定しなければなりません。
これは,定数 COLOR_SCROLLBAR の値が 0 であり,NULL と区別できないためです。
だったらシステムカラーを 1 始まりにすればよかったと思うのですが,修正が効かなかったのでしょう。

### ウィンドウの作成

CreateWindow 関数は,メモリ上にウィンドウを作成する関数です。

CreateWindow 関数 [MSDN]

ウィンドウを作成します。

HWND CreateWindow(
    LPCTSTR lpClassName,   // ウィンドウクラス名
    LPCTSTR lpWindowName,  // ウィンドウの名称
    DWORD dwStyle,         // ウィンドウスタイル
    int x,                 // 横方向の位置
    int y,                 // 縦方向の位置
    int nWidth,            // ウィンドウの幅
    int nHeight,           // ウィンドウの高さ
    HWND hWndParent,       // 親ウィンドウのハンドル
    HMENU hMenu,           // メニューハンドル
    HANDLE hInstance,      // インスタンスハンドル
    LPVOID lpParam         // ウィンドウ作成データ
);
lpClassName
ウィンドウクラスの名前を指定します。
lpWindowName
ウィンドウの名前を指定します。この名前はウィンドウのタイトルバーに表示されます。。
dwStyle
ウィンドウの表示スタイルを指定します。次の表に示す値はビット論理和で組み合わせることができます。

ウィンドウスタイル [MSDN]

値 (基本的なスタイル)説明
WS_OVERLAPPED
WS_TILED
オーバーラップウィンドウ
WS_POPUPポップアップウィンドウ
WS_CHILD
WS_CHILDWINDOW
子ウィンドウ
WS_OVERLAPPEDWINDOW
WS_TILEDWINDOW
オーバーラップウィンドウ (WS_OVERLAPPED, WS_CAPTION, WS_SYSMENU, WS_THICKFRAME, WS_MINIMIZEBOX, WS_MAXIMIZEBOX の組合せ)
WS_POPUPWINDOWポップアップウィンドウ (WS_POPUP, WS_BORDER, WS_SYSMENU の組合せ)
値 (構成要素)説明
WS_BORDER境界を持つウィンドウ
WS_DLGFRAMEダイアログボックスの枠を持つウィンドウ
WS_CAPTIONタイトルバーを持つウィンドウ (WS_BORDER, WS_DLGFRAME の組合せ)
WS_VSCROLL垂直スクロールバーを持つウィンドウ
WS_HSCROLL水平スクロールバーを持つウィンドウ
WS_SYSMENUシステムメニューを持つウィンドウ
WS_THICKFRAME
WS_SIZEBOX
サイズ変更境界を持つウィンドウ
WS_MINIMIZEBOX最小化ボタンを持つウィンドウ
WS_MAXIMIZEBOX最大化ボタンを持つウィンドウ
値 (その他)説明
WS_MINIMIZE
WS_ICONIC
最小化されたウィンドウ
WS_MAXIMIZE最大化されたウィンドウ
WS_VISIBLE初期状態で可視化されたウィンドウ
WS_DISABLED無効化されたウィンドウ
WS_CLIPSIBLINGS
WS_CLIPCHILDREN
WS_GROUPコントロールグループの最初のコントロール
WS_TABSTOPタブキーで移動可能なコントロール
x, y
ウィンドウ左上端の x 座標,y 座標を指定します。既定の位置を使うには CW_USEDEFAULT を指定します。
nWidth, nHeight
ウィンドウの幅,高さを指定します。既定の大きさを使うには CW_USEDEFAULT を指定します。
hWndParent
親ウィンドウへのハンドルを指定します。メインウィンドウを作成する場合には NULL を指定します。
hMenu
子ウィンドウ以外の場合: メニューハンドルを指定します。メニューを提供しない場合や,ウィンドウクラスのメニューを使用する場合には NULL を指定します。
子ウィンドウの場合: 子ウィンドウ識別子とする整数値を指定します。
hInstnance
このウィンドウを生成するプログラムのインスタンスハンドルを指定します。
lpParam
返り値
生成されたウィンドウへのハンドルを返します。失敗時 NULL を返します。

拡張ウィンドウスタイルが適用可能な,CreateWindowEx 関数もあります。
最初に引数が 1 つ追加されていますが,後は CreateWindow 関数と同じです。

ウィンドウの表示

ShowWindow 関数は,ウィンドウの表示状態を設定するための関数です。

ShowWindow 関数 [MSDN]

ウィンドウの表示状態を設定します。

BOOL ShowWindow(
    HWND hWnd,    // ウィンドウハンドル
    int nCmdShow  // 表示状態
);
nCmdShow
ウィンドウの表示状態を表す値を指定します。

ウィンドウの表示状態 [MSDN]

説明
SW_HIDE非表示にして,他のウィンドウをアクティブ化
SW_SHOWNORMAL
SW_NORMAL
アクティブ化して,普通のサイズで表示
SW_SHOWMINIMIZEDアクティブ化して,最小化
SW_SHOWMAXIMIZED
SW_MAXIMIZE
アクティブ化して,最大化
SW_SHOWNOACTIVATEアクティブ化せずに,以前の位置とサイズで表示
SW_SHOWアクティブ化して,現在の位置とサイズで表示
SW_MINIMIZE最小化して,他のウィンドウをアクティブ化
SW_SHOWMINNOACTIVEアクティブ化せずに,以前の位置とサイズで表示
SW_SHOWNAアクティブ化せずに,現在の位置とサイズで表示
SW_RESTOREアクティブ化して,普通の大きさで表示
SW_SHOWDEFAULTSTARTUPINFO 構造体で指定された値を使う

ShowWindow 関数の第 2 引数には,WinMain 関数の第 4 引数である nCmdShow の値を渡します。

UpdateWindow 関数は,ウィンドウの描画を直ちに行うために呼び出します。

UpdateWindow 関数 [MSDN]

無効領域がある場合に,ウィンドウを直ちに再描画します。

BOOL UpdateWindow(
    HWND hWnd  // ウィンドウハンドル
);

ウィンドウプロシージャ

イベントドリブンアーキテクチャ

Windows アプリケーションは,マウス操作やキーボード操作など,ユーザやプログラムが発生させたイベント (event) を契機として,次の処理を行います。
処理が終れば,アプリケーションは次のイベントが発生するまで待機します。
このような方式で処理を実行するプログラムは,一般にイベント駆動型プログラム (event-driven program) と呼ばれます。

イベント駆動型プログラムでは,イベントを処理する専用の関数が用意されます。
この関数のことを,イベントハンドラ (event handler) と呼びます。
Windows アプリケーションにおけるイベントハンドラは,ウィンドウプロシージャ (window procedure) という名前が付いています。

ウィンドウプロシージャ

イベントが発生すると,Windows はイベントの発生をメッセージ (message) としてアプリケーションに通知します。
メッセージには,イベントの種類の他に,イベントの発生時刻,発生場所などといった情報が添加されます。
メッセージは,メッセージの待ち行列であるメッセージキュー (message queue) にポスト (post) される場合と,直接ウィンドウプロシージャに送信 (send) される場合とがあります。

Windows で扱われるメッセージには,例えば次のようなものがあります。

メッセージコード説明
WM_CREATEウィンドウが生成される時に発生
WM_DESTROYウィンドウが破棄される時に発生
WM_MOVEウィンドウが移動された後に発生
WM_SIZEウィンドウサイズが変更された後に発生
WM_PAINTウィンドウの再描画が必要な時に発生
WM_CLOSEウィンドウの終了が選択された時に発生
WM_QUITPostQuitMessage 関数が実行された時に発生
WM_KEYDOWN非システムキーが押下された時に発生
WM_KEYUP非システムキーが解放された時に発生
WM_MOUSEMOVEマウスが移動した時に発生
WM_LBUTTONDOWN左マウスボタンが押下された時に発生
WM_LBUTTONUP左マウスボタンが解放された時に発生

作例

まともなウィンドウを作成する最初のプログラムです。
このプログラムが,今後作成するプログラムのスケルトンプログラムとなります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

メッセージループ

次に示す while ループは,メッセージループ (message loop) と呼ばれます。
メッセージループには,ウィンドウを維持する役割と,メッセージを仲介する役割があります。

while (GetMessage(&msg, NULL, 0, 0) > 0) {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

メッセージループ内の 3 つの関数は,次のような仕事を行います。

GetMessage 関数は,メッセージキューからメッセージを取り出す関数です。
唯一 WM_QUIT メッセージを取り出した時にだけ 0 を返し,メッセージループを終了させます。

GetMessage 関数 [MSDN]

メッセージキューからメッセージを取り出します。キューが空の場合は,新たなメッセージがポストされるまで制御を返しません。

BOOL GetMessage(
    LPMSG lpMsg,         // MSG 構造体
    HWND hWnd,           // ウィンドウハンドル
    UINT wMsgFilterMin,  // 取得するメッセージの最小値
    UINT wMsgFilterMax   // 取得するメッセージの最大値
);
lpMsg
MSG 構造体へのポインタを指定します (出力用)。
hWnd
メッセージ取得元のウィンドウを特定する際に指定します。ウィンドウを特定しない場合には NULL を指定します。
wMsgFilterMin, wMsgFilterMax
取得するメッセージの最小値,最大値を指定します。通常は,取得するメッセージを制限することはないので,共に 0 を指定します。
返り値
WM_QUIT を取得したときに 0 を,それ以外のメッセージを取得したときに 0 以外の値を返します。エラーが発生したときに -1 を返します。

TranslateMessage 関数は,キー入力メッセージを文字メッセージに変換します。

TranslateMessage 関数 [MSDN]

BOOL TranslateMessage(
    CONST MSG *lpmsg  // MSG 構造体
);

DispatchMessage 関数は,メッセージをウィンドウプロシージャへディスパッチ (dispatch) する関数です。

DispatchMessage 関数 [MSDN]

LRESULT DispatchMessage(
    CONST MSG *lpmsg  // MSG 構造体
);

MSG 構造体

MSG 構造体はメッセージのデータを表す構造体で,次のように定義されています。

MSG 構造体 [MSDN]

メッセージを表します。

typedef struct tagMSG {
   HWND hwnd;      // ウィンドウハンドル
   UINT message;   // メッセージコード
   WPARAM wParam;  // w-パラメータ
   LPARAM lParam;  // l-パラメータ
   DWORD time;     // ポストされた時刻
   POINT pt;       // カーソル位置
} MSG;
hwnd
メッセージを受け取るウィンドウプロシージャを持つウィンドウへのハンドルです。
message
WM_CREATE, WM_SIZE といった,メッセージの種類を表す整数値です。
wParam, lParam
メッセージ固有の付加情報を表します。各パラメータの意味はメッセージによって異なります。

wParam, lParam には,キーボードのキーコード,マウスカーソルの座標など,メッセージの種類に応じた付加情報が格納されます。
どのような情報が格納されるかは,メッセージごとに決められています。
型はそれぞれ WPARAM 型,LPARAM 型となっていますが,これらは次表のビット長を持つ整数型です。

WPARAMLPARAM
Win1616 ビット32 ビット
Win3232 ビット32 ビット
Win6464 ビット64 ビット

wParam, lParam というパラメータ名の由来ですが,これは Win16 時代に wParam の型が WORD (UINT) 型,lParam の型が LONG 型だったためです。
実際に Win16 では,ウィンドウプロシージャ (後述) を次のように書くことができました。

long FAR PASCAL WndProc(HWND hwnd, UINT uMsg, WORD wParam, LONG lParam)

ウィンドウプロシージャ

ウィンドウプロシージャ (window procedure) は,アプリケーションに送られてきたメッセージを処理する専用の関数です。

今回の作例では,WndProc という名前でウィンドウプロシージャを定義しています。
このウィンドウプロシージャを,次のようにしてウィンドウクラスに登録しています。

wc.lpfnWndProc   = WndProc;  // ウィンドウプロシージャを登録

ウィンドウプロシージャは,次のシグネチャを持つ関数として用意します。
ただし,関数名は必ずしも WindowProc である必要はありません。

WindowProc 関数 [MSDN]

LRESULT CALLBACK WindowProc(
    HWND hwnd,      // ウィンドウハンドル
    UINT uMsg,      // メッセージコード
    WPARAM wParam,  // w-パラメータ
    LPARAM lParam   // l-パラメータ
);

パラメータ hwnd, uMsg, wParam, lParam の意味は,MSG 構造体のメンバ hWnd, message, wParam, lParam に同じです。

返却型 LRESULT は,Win16/Win32 では 32 ビット長,Win64 では 64 ビット長の整数型です。
ウィンドウプロシージャの返り値の意味はメッセージによって異なりますが,返り値が特別な意味を持たないメッセージでは 0 を返すのが普通です。

呼び出し規約 CALLBACK は,この関数がコールバック関数 (callback function) であることを示します。

ウィンドウプロシージャは,次のように switch 文を用いて各メッセージ用の処理を記述するのが一般的です。

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg) {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

興味のないメッセージの処理は,既定のウィンドウプロシージャである DefWindowProc に丸投げします。

DefWindowProc 関数 [MSDN]

LRESULT DefWindowProc(
    HWND hwnd,      // ウィンドウハンドル
    UINT uMsg,      // メッセージコード
    WPARAM wParam,  // w-パラメータ
    LPARAM lParam   // l-パラメータ
);

アプリケーションの終了

ウィンドウプロシージャで WM_DESTROY メッセージを受け取ったときには,PostQuitMessage 関数を実行する必要があります。
この関数は WM_QUIT メッセージをメッセージキューにポストし,メッセージループを終了させます。

PostQuitMessage 関数 [MSDN]

VOID PostQuitMessage(
    int nExitCode  // 終了コード
);

WM_DESTROY メッセージは,ウィンドウの表示が画面から消された後,ウィンドウが破棄される前に送信されます。

PostQuitMessage 関数に指定した終了コードは,WM_CLOSE メッセージの wParam に格納されます。
この値を,次のように WinMain 関数の返り値に指定します。

return msg.wParam;

主要なメッセージ

この章では,代表的なウィンドウメッセージである以下のメッセージを紹介します。

メッセージコード説明
WM_CREATEウィンドウが生成される時に発生
WM_DESTROYウィンドウが破棄される時に発生
WM_MOVEウィンドウが移動された後に発生
WM_SIZEウィンドウサイズが変更された後に発生
WM_CLOSEウィンドウを閉じる操作が行われた時に発生
WM_QUITPostQuitMessage 関数が実行された時に発生
WM_KEYDOWN非システムキーが押下されている時に発生
WM_KEYUP非システムキーが解放された時に発生
WM_MOUSEMOVEマウスが移動した時に発生
WM_LBUTTONDOWN左マウスボタンが押下された時に発生
WM_LBUTTONUP左マウスボタンが解放された時に発生

ウィンドウの破棄

次の作例は,ウィンドウを閉じる前に確認のダイアログを表示するプログラムです。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int id;

    switch (uMsg) {
    case WM_CLOSE:
        id = MessageBox(hwnd, TEXT("終了しますか?"),
            TEXT("終了"), MB_YESNO | MB_ICONEXCLAMATION);
        if (id == IDYES)
            DestroyWindow(hwnd);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

このプログラムは,概ね次のように動作します。

  1. ウィンドウを閉じる操作を行う。→ WM_CLOSE メッセージが発行される。
  2. 終了を確認するメッセージボックスを表示する。
  3. DestroyWindow を実行する。→ WM_DESTROY メッセージが発行される。
  4. PostQuitMessage を実行する。→ WM_QUIT メッセージが発行される。
  5. メッセージループを終了し,アプリケーションを終了する。

プログラムの終了に関係するメッセージは,次に示す WM_CLOSE, WM_DESTROY, WM_QUIT メッセージの 3 つです。

WM_CLOSE メッセージ [MSDN]

ウィンドウを閉じる操作が行われた時に送信されます。ウィンドウの閉じるボタンがクリックされたときや,Alt+F4 が入力されたときなどに発行されます。

WM_DESTROY メッセージ [MSDN]

ウィンドウが破棄される時に送信されます。DestroyWindow 関数の実行時に送信されます。

WM_QUIT メッセージ [MSDN]

PostQuitMessage 関数が実行されたときにポストされます。

wParam
PowtQuitMessage 関数に指定された終了コードが入ります。この値は WinMain 関数の返り値に指定します。

デフォルトウィンドウプロシージャは,WM_CLOSE メッセージを受け取ると,実際にウィンドウの破棄を行う関数である DestroyWindow 関数を実行します。
メッセージの処理を独自に記述する場合は,自分で DestroyWindow 関数を呼び出す必要があります。

DestroyWindow 関数 [MSDN]

ウィンドウを破棄します。

BOOL DestroyWindow(
    HWND hWnd  // 破棄するウィンドウへのハンドル
);

DestroyWindow 関数を実行することで,次は WM_DESTROY メッセージが発行されます。
WM_DESTROY メッセージは,ウィンドウが画面から消去された後,ウィンドウの破棄が実行される前に送信されます。
WM_DESTROY メッセージへの応答では,PostQuitMessage 関数を呼び出す必要があります。

PostQuitMessage 関数 [MSDN]

VOID PostQuitMessage(
    int nExitCode  // 終了コード
);

PostQuitMessage は WM_CLOSE をメッセージキューにポストし,メッセージループを終了させます。

### ウィンドウの生成

次のプログラムは,WM_CREATE メッセージで獲得できる情報を,メッセージボックスに表示するプログラムです。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    LPCREATESTRUCT lpcs;
    TCHAR szTemp[1024];

    switch (uMsg) {
    case WM_CREATE:
        lpcs = (LPCREATESTRUCT) lParam;
        wsprintf(szTemp,
            TEXT("lpszClass: %s\nlpszName: %s\n")
            TEXT("x: %d\ny: %d\ncx: %d\ncy: %d"),
            lpcs->lpszClass, lpcs->lpszName,
            lpcs->x, lpcs->y, lpcs->cx, lpcs->cy);
        MessageBox(hwnd, szTemp, TEXT("WM_CREATE"), MB_OK);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_CREATE メッセージ [MSDN]

lParam
CREATESTRUCT 構造体へのポインタが入ります。

WM_CREATE メッセージは,CreateWindow 関数または CreateWindowEx 関数が呼び出されると送信されます。
このメッセージの lParam の値は,次に示す CREATESTRUCT 構造体へのポインタです。

CREATESTRUCT 構造体 [MSDN]

typedef struct tagCREATESTRUCT {
    LPVOID lpCreateParams;  // ウィンドウ作成データ
    HANDLE hInstance;       // インスタンスハンドル
    HMENU hMenu;            // メニュー
    HWND hwndParent;        // 親ウィンドウ
    int cy;                 // 高さ
    int cx;                 // 幅
    int y;                  // y 座標
    int x;                  // x 座標
    LONG style;             // ウィンドウスタイル
    LPCTSTR lpszName;       // ウィンドウ名
    LPCTSTR lpszClass;      // ウィンドウクラス名
    DWORD dwExStyle;        // 拡張ウィンドウスタイル
} CREATESTRUCT;

CREATESTRUCT 構造体の各メンバは,CreateWindow(Ex) 関数のパラメータに対応しています。

WM_CREATE メッセージを受け取ったウィンドウプロシージャは,普通は 0 を返します。
ただし,-1 を返すとウィンドウを破棄することができ,CreateWindow(Ex) 関数は NULL を返すことになります。

### ウィンドウの移動

次の作例は,ウィンドウを移動すると,クライアント領域の左上端の座標をタイトルバーに表示します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int x, y;
    TCHAR szTemp[1024];

    switch (uMsg) {
    case WM_MOVE:
        y = HIWORD(lParam);  // y 座標を取り出す
        x = LOWORD(lParam);  // x 座標を取り出す
        wsprintf(szTemp, TEXT("WM_MOVE (%d, %d)"), x, y);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_MOVE メッセージ [MSDN]

lParam
下位ワード,上位ワードは,それぞれ移動後のクライアント領域の x 座標,y 座標を表します。

WM_MOVE メッセージの lParam には,移動後のクライアント領域の左上端の座標が設定されます。
lParam の下位ワード (下位 16 ビット) は x 座標,上位ワード (下位 16 ビットの次の 16 ビット) は y 座標を表します。
例えば,移動後の座標が (x, y) = (0xab, 0xcd) であれば,lParam の値は Win32 では 0x00cd 00ab,Win64 では 0x0000 0000 00cd 00ab となります。
lParam から x 座標,y 座標を取り出すには,次のように LOWORD, HIWORD マクロを使います。

y = HIWORD(lParam);  // y 座標を取り出す
x = LOWORD(lParam);  // x 座標を取り出す

LOWORD, HIWORD マクロは,32 ビット値の下位ワード,上位ワードを取り出すマクロで,次のように定義されています。

#define LOWORD(l)  ((WORD) (((DWORD_PTR) (l)) & 0xffff))
#define HIWORD(l)  ((WORD) ((((DWORD_PTR) (l)) >> 16) & 0xffff))

SetWindowText 関数は,ウィンドウのテキスト (タイトルバーに表示される文字列) を設定する関数です。

SetWindowText 関数 [MSDN]

ウィンドウのテキストを設定します。

BOOL SetWindowText(
    HWND hWnd,         // ウィンドウハンドル
    LPCTSTR lpString   // テキスト
);

ウィンドウサイズの変更

次のプログラムは,ウィンドウサイズを変更すると,サイズ変更後のクライアント領域の幅と高さをタイトルバーに表示します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int cx, cy;
    TCHAR szTemp[1024];

    switch (uMsg) {
    case WM_SIZE:
        cy = HIWORD(lParam);
        cx = LOWORD(lParam);
        wsprintf(szTemp, TEXT("WM_SIZE (%d, %d)"), cx, cy);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_SIZE メッセージは,ウィンドウサイズが変更された時に送信されます。

WM_SIZE メッセージ [MSDN]

ウィンドウサイズが変更された時に送信されます。

wParam
サイズ変更の種類を表す次の値が入ります。SIZE_MAXSHOW, SIZE_MAXHIDE のメッセージは,すべてのポップアップウィンドウに対して送信されます。
説明
SIZE_RESTOREDサイズ変更された (最小化,最大化以外)
SIZE_MINIMIZED最小化された
SIZE_MAXIMIZED最大化された
SIZE_MAXSHOW他のウィンドウが非最大化された
SIZE_MAXHIDE他のウィンドウが最大化された
lParam
下位ワード,上位ワードは,それぞれサイズ変更後のクライアント領域の幅,高さを表します。

マウス入力

次のプログラムは,マウスの移動と左ボタンのクリックを検知し,カーソルの座標と共に表示するプログラムです。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int x, y;
    TCHAR szTemp[1024];

    switch (uMsg) {
    case WM_MOUSEMOVE:
        y = HIWORD(lParam);
        x = LOWORD(lParam);
        wsprintf(szTemp, TEXT("WM_MOUSEMOVE (%d, %d)"), x, y);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_LBUTTONDOWN:
        y = HIWORD(lParam);
        x = LOWORD(lParam);
        wsprintf(szTemp, TEXT("WM_LBUTTONDOWN (%d, %d)"), x, y);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_LBUTTONUP:
        y = HIWORD(lParam);
        x = LOWORD(lParam);
        wsprintf(szTemp, TEXT("WM_LBUTTONUP (%d, %d)"), x, y);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

今回のプログラムで処理したマウス関連のメッセージは次の 3 つです。

WM_MOUSEMOVE メッセージ [MSDN]

マウスが移動した時にポストされます。

WM_LBUTTONDOWN メッセージ [MSDN]

マウスの左ボタンが押下された時にポストされます。

WM_LBUTTONUP メッセージ [MSDN]

マウスの左ボタンが解放された時にポストされます。

いずれのメッセージも,lParam の下位ワード,上位ワードに,メッセージが発行された時点でのマウスカーソルの x 座標,y 座標 (クライアント座標) が格納されます。

キーボード入力

次のプログラムは,キーボードのキー入力を検知し,入力されたキーを表す値と共に表示します。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    int vkey;
    TCHAR szTemp[1024];

    switch (uMsg) {
    case WM_KEYDOWN:
        vkey = wParam;
        wsprintf(szTemp, TEXT("WM_KEYDOWN (vkey: %d)"), vkey);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_KEYUP:
        vkey = wParam;
        wsprintf(szTemp, TEXT("WM_KEYUP (vkey: %d)"), vkey);
        SetWindowText(hwnd, szTemp);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

今回のプログラムで処理したキーボード関連のメッセージは次の 2 つです。

WM_KEYDOWN メッセージ [MSDN]

非システムキーが押下されている時にポストされます。

WM_KEYUP メッセージ [MSDN]

非システムキーが解放された時にポストされます。

WM_KEYDOWN メッセージは,キーが押されている間,何回でも発行されます。
一方 WM_KEYUP メッセージは,キーが解放された時に 1 回だけ発行されます。

いずれのメッセージも,wParam には押されたキーを表す値が設定されます。
この値は,キーボードの種類に依存しない仮想的なキーコードであり,仮想キーコード (virtual-key code) と呼ばれます。