ボタン
クリックに反応するボタン
ボタンは、ユーザーの入力に反応して押すことができるコントロールです。マウスカーソルがボタン上にある状態で左クリックすれば、ボタンが押されたように見た目が変化します。スタティックコントロールは動作のないコントロールでしたが、多くのコントロールはボタンのようにユーザーの入力に反応します。
ボタンを作成する方法はスタティックコントロールと同じです。CreateWindow() 関数か CreateWindowEx() 関数を用いて、ウィンドウクラス名に BUTTON という文字列を指定します。
#include <windows.h> #define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window") LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; default: return DefWindowProc(hwnd , uMsg , wParam , lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND window, button; WNDCLASSEX wcx; int returnCode = 0; ULONG p; wcx.cbSize = sizeof(WNDCLASSEX); wcx.style = CS_HREDRAW | CS_VREDRAW; wcx.lpfnWndProc = SampleWindowProc; wcx.cbClsExtra = 0; wcx.cbWndExtra = 0; wcx.hInstance = hInstance; wcx.hIcon = NULL; wcx.hCursor = NULL; wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1; wcx.lpszMenuName = NULL; wcx.lpszClassName = WINDOWS_CLASS_NAME; wcx.hIconSm = NULL; if (!RegisterClassEx(&wcx)) { OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n")); return 0; } window = CreateWindowEx( WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (!window) { OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n")); return 0; } button = CreateWindowEx( WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 400, 100, window, NULL, hInstance, NULL ); if (!button) { OutputDebugString(TEXT("Error: コントロールを作成できません。\n")); return 0; } while(TRUE) { MSG msg; int r = GetMessage(&msg, NULL, 0, 0); if (r > 0) DispatchMessage(&msg); else if(r == -1) { OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n")); break; } else { returnCode = msg.wParam; break; } } return returnCode; }
コード1はボタンを生成し、アプリケーションウィンドウの子ウィンドウとして設定しています。実行結果のように 1 つのボタンが表示されます。ボタン上でクリックすると、ボタンが押し込まれるように外観が変化します。しかし、このプログラムではボタンがクリックされた時の処理を書いていないため、ボタンを押しても何も反応しません。
クリックの通知を処理する
ボタンが押されたことをプログラムで感知し、ボタンが押されたときに何らかの反応を返すには、ボタンから送られてくるメッセージを処理します。ボタンが押されると、親ウィンドウのウィンドウプロシージャに WM_COMMAND メッセージを送ります。このメッセージは、子ウィンドウによって何らかのコマンドが発行されたときに送られます。
#include <windows.h> #define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window") LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_COMMAND: MessageBox(hwnd, TEXT("Set up!"), TEXT("確認"), MB_ICONINFORMATION); return 0; default: return DefWindowProc(hwnd , uMsg , wParam , lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND window, button; WNDCLASSEX wcx; int returnCode = 0; ULONG p; wcx.cbSize = sizeof(WNDCLASSEX); wcx.style = CS_HREDRAW | CS_VREDRAW; wcx.lpfnWndProc = SampleWindowProc; wcx.cbClsExtra = 0; wcx.cbWndExtra = 0; wcx.hInstance = hInstance; wcx.hIcon = NULL; wcx.hCursor = NULL; wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1; wcx.lpszMenuName = NULL; wcx.lpszClassName = WINDOWS_CLASS_NAME; wcx.hIconSm = NULL; if (!RegisterClassEx(&wcx)) { OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n")); return 0; } window = CreateWindowEx( WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (!window) { OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n")); return 0; } button = CreateWindowEx( WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 400, 100, window, NULL, hInstance, NULL ); if (!button) { OutputDebugString(TEXT("Error: コントロールを作成できません。\n")); return 0; } while(TRUE) { MSG msg; int r = GetMessage(&msg, NULL, 0, 0); if (r > 0) DispatchMessage(&msg); else if(r == -1) { OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n")); break; } else { returnCode = msg.wParam; break; } } return returnCode; }
コード2はウィンドウプロシージャで WM_COMMAND メッセージを受け取った時にメッセージボックスを表示します。ボタンが押されると、ボタンによって親ウィンドウに WM_COMMAND メッセージが送られるため、ボタンを押すとメッセージが表示されるプログラムのように動作します。
しかし WM_COMMAND メッセージは、ウィンドウに登録されている様々なコントロールから送られてきます。そのため、複数のコントロールが配置されている場合、どのコントロールから、どのようなコマンドが発行されたのかを調べる必要があります。
WM_COMMAND メッセージがコントロールによって発行された場合 lParam パラメータにメッセージを送出したコントロールのハンドルが格納されています。wParam パラメータは 32 ビット中の上位 16 ビットと下位 16 ビットにデータが分割され、上位 16 ビットにコントロール定義の通知コードが、下位 16 ビットにコントロールの識別 ID が格納されています。
コントロール定義の通知コードは、コントロールの種類によって意味が異なります。ボタンから送られた WM_COMMAND メッセージの場合、以下の通知コードのいずれかが格納されています。多くの場合は、ボタンがクリックされたことを表す BN_CLICKED と比較する形になるでしょう。
定数 | 意味 |
---|---|
BN_CLICKED | ユーザーがボタンをクリックした。 |
BN_DBLCLK | ユーザーが BS_OWNERDRAW または BS_RADIOBUTTON スタイルを持つボタンをダブルクリックした。 |
BN_DISABLE | ボタンが使用不能になった。 |
BN_DOUBLECLICKED | BN_DBLCLK と同じ。 |
BN_HILITE | ユーザーがボタンを選択した。この通知コードは Windows 3.0 以前の 16 ビット版との互換性のために残されている。アプリケーションは BS_OWNERDRAW ボタンスタイルと DRAWITEMSTRUCT 構造体を使うべきである。 |
BN_KILLFOCUS | キーボードフォーカスを失った。ボタンは、この通知メッセージを送るために BS_NOTIFY スタイルを持たなければならない。 |
BN_PAINT | ボタンを再描画しなければならない。 |
BN_PUSHED | BN_HILITE と同じ。 |
BN_SETFOCUS | キーボードフォーカスを得た。ボタンは、この通知メッセージを送るために BS_NOTIFY スタイルを持たなければならない。 |
BN_UNHILITE | ハイライトがボタンから削除されなければならない。 |
BN_UNPUSHED | BN_UNHILITE と同じ。 |
子ウィンドウは識別用の ID を持つことができます。ID は CreateWindow() 関数または CreateWindowEx() 関数の hMenu パラメータに指定します。通常、このパラメータにはメニューのハンドルを指定しますが、子ウィンドウ(ウィンドウに追加するコントロール)の場合は、コントロールを識別する ID を設定できます。その場合、一意に識別できる任意の整数を HMENU 型キャストしてください。
wParam パラメータを上位 16 ビットと下位 16 ビットに分けるには、シフト演算と論理演算を使います。
WORD hiWord = (WORD)((wParam >> 16) & 0xFFFF); //上位 16 ビット WORD loWord = (0xFFFF & wParam); //下位 16 ビット
上記のような形で上位ビットと下位ビットを取り出せますが、このような演算に展開してくれるマクロ関数が用意されています。上位 16 ビットの抽出には HIWORD() マクロ関数を、下位 16 ビットの抽出には LOWORD() マクロ関数を使います。
WORD HIWORD(DWORD dwValue);
WORD LOWORD(DWORD dwValue);
それぞれ dwValue パラメータには分割する 32 ビット整数を指定します。
#include <windows.h> #define WINDOWS_CLASS_NAME TEXT("WisdomSoft.Sample.Window") #define ID_BUTTON1 10000 #define ID_BUTTON2 10001 LRESULT CALLBACK SampleWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { switch(uMsg) { case WM_DESTROY: PostQuitMessage(0); return 0; case WM_COMMAND: if (HIWORD(wParam) == BN_CLICKED) { if(LOWORD(wParam) == ID_BUTTON1) MessageBox(hwnd, TEXT("Set up!"), TEXT("確認"), MB_ICONINFORMATION); if(LOWORD(wParam) == ID_BUTTON2) MessageBox(hwnd, TEXT("Sammlung!"), TEXT("確認"), MB_ICONINFORMATION); } return 0; default: return DefWindowProc(hwnd , uMsg , wParam , lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND window, button1, button2; WNDCLASSEX wcx; int returnCode = 0; ULONG p; wcx.cbSize = sizeof(WNDCLASSEX); wcx.style = CS_HREDRAW | CS_VREDRAW; wcx.lpfnWndProc = SampleWindowProc; wcx.cbClsExtra = 0; wcx.cbWndExtra = 0; wcx.hInstance = hInstance; wcx.hIcon = NULL; wcx.hCursor = NULL; wcx.hbrBackground = (HBRUSH)COLOR_BACKGROUND + 1; wcx.lpszMenuName = NULL; wcx.lpszClassName = WINDOWS_CLASS_NAME; wcx.hIconSm = NULL; if (!RegisterClassEx(&wcx)) { OutputDebugString(TEXT("Error: ウィンドウクラスの登録ができません。\n")); return 0; } window = CreateWindowEx( WS_EX_LEFT, WINDOWS_CLASS_NAME, TEXT("Window Title"), WS_OVERLAPPEDWINDOW | WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL ); if (!window) { OutputDebugString(TEXT("Error: ウィンドウを作成できません。\n")); return 0; } button1 = CreateWindowEx( WS_EX_LEFT, TEXT("BUTTON"), TEXT("Stand by Ready!!"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 0, 400, 100, window, (HMENU)ID_BUTTON1, hInstance, NULL ); button2 = CreateWindowEx( WS_EX_LEFT, TEXT("BUTTON"), TEXT("Anfang"), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, 0, 110, 400, 100, window, (HMENU)ID_BUTTON2, hInstance, NULL ); if (!button1 || !button2) { OutputDebugString(TEXT("Error: コントロールを作成できません。\n")); return 0; } while(TRUE) { MSG msg; int r = GetMessage(&msg, NULL, 0, 0); if (r > 0) DispatchMessage(&msg); else if(r == -1) { OutputDebugString(TEXT("Error: メッセージの取得に失敗しました。\n")); break; } else { returnCode = msg.wParam; break; } } return returnCode; }
コード3は 2 つのボタンをウィンドウに追加しています。それぞれのボタンが押されると WM_COMMAND メッセージが送られてくるので、wParam パラメータの上位 16 ビットを取り出してクリックイベントによるコマンドかどうか通知コードを調べます。通知コードがボタンクリックだった場合、wParam パラメータの下位 16 ビットを取り出してボタン ID と比較し、どちらのボタンが押されたのか識別しています。
WM_COMMAND メッセージが送られたとき lParam パラメータに子ウィンドウのハンドルが格納されているため、ハンドルによって識別できる場合は lParam パラメータと比較する方法も検討できるでしょう。