SendMessage関数

前ページではボタンの作成について説明しました。
ボタンのスタイルについても説明しましたが、もっと詳細なカスタマイズを行うにはSendMessage関数を使用します。

LRESULT SendMessage(
 HWND hWnd,
 UINT Msg,
 WPARAM wParam,
 LPARAM lParam
);
ウィンドウhWndのウィンドウプロシージャにメッセージMsgを送信する。
追加のデータとしてwParam、lParamを指定できる。
戻り値は送信するメッセージにより異なる。

SendMessage関数はコントロール類の設定に限らず、Windows APIでは頻繁に使用される関数です。
引数はウィンドウプロシージャと同じで、この関数は指定のウィンドウハンドルのウィンドウプロシージャに任意のメッセージを送信(=実行)することができます。
通常のウィンドウのほか、コントロールもウィンドウの一種なので、コントロールに対しても様々な命令を実行することができます。

送信可能なメッセージの種類やパラメーター、戻り値などは送信先によって異なります。
ここではボタンコントロールに対して使用できるメッセージを説明します。

ボタンのカスタマイズ

フォントの変更

SendMessage関数を使用してボタンのフォントを変更してみましょう。
フォントの変更はWM_SETFONTメッセージをコントロールに送信します。


#include <windows.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100
#define IDC_BUTTON2 101

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HFONT hFont;
	HWND hBtn;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		hBtn = CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE,
			10, 10, 60, 20,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);

		//ボタンの作成
		//こちらはフォントを変更しない
		CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE,
			10, 35, 60, 20,
			hWnd, (HMENU)IDC_BUTTON2, hInst, NULL);

		//フォントの作成
		hFont = CreateFont(12, 0, 0, 0,
			FW_NORMAL, FALSE, FALSE, 0,
			SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
			CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
			DEFAULT_PITCH | FF_DONTCARE,
			L"MS UI Gothic");

		//フォントの変更
		SendMessage(hBtn, WM_SETFONT, (WPARAM)hFont, MAKELPARAM(TRUE, 0));
		break;

	case WM_DESTROY: //ウィンドウの破棄
		//フォントを削除
		DeleteObject(hFont);
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果です。
上がフォントを変更したボタン、下が何も設定していないボタンです。
ボタンのフォントの変更

メッセージの送信先はボタンのハンドル(CreateWindow関数の戻り値)を指定します。
(ボタン識別子(IDC_BUTTON1)ではない)
メッセージにはWM_SETFONTを指定します。

パラメーターのWPARAMにはあらかじめ取得/作成したフォントのハンドルを指定します。
要求されるのはWPARAM型ですから、キャストしておきます。
(キャストしなくても動作はしますがVisual Studioが警告を出します)
なお、フォントを作成した場合はそのフォントを指定したコントロールが破棄されるまではフォントを削除しないようにしてください。

WPARAM、LPARAMは常に必要なパラメータがセットされるわけではなく、使用されないこともあります。
その場合は0またはNULLを指定します。
0を指定する場合はキャストは不要です。

LPARAMMAKELPARAMマクロを使用してパラメータを作成します。

なお、WM_SETFONTメッセージ送信時のSendMessage関数の戻り値はありません。
(常に0。何か値を返したとしても意味のない値)

MAKEWPARAM、MAKELPARAMマクロ

SendMessage関数の第三引数はWPARAM型、第四引数はLPARAM型です。
これらの型の実体は整数値ですが、一度に複数の情報をやり取りするために、メモリ領域を二つに分割してそれぞれに別のデータを格納することがあります。
メモリ領域の前半分を上位ワード、後半分を下位ワードといいます。

ふたつのデータを合体してひとつのデータにして送信するわけですが、そのためにMAKEWPARAMマクロやMAKELPARAMマクロを使用します。
名前の通り、MAKEWPARAMマクロはWPARAM用のデータを作成し、MAKELPARAMマクロはLPARAM用のデータを作成します。

WM_SETFONTメッセージのLPARAMは、下位ワードにコントロールを再描画するか否かをBOOL型で要求します。
TRUEを指定するとフォントの変更後に再描画されます。
(再描画しない場合はFALSEを指定します)
上位ワードは使用しないので0を指定します。
この二つのデータをMAKELPARAMマクロでLPARAM型に変換し、送信します。
データの指定は(下位ワード,上位ワード)の順で、これはMAKEWPARAMマクロでも同様です。


//MAKELPARAM(LOWORD, HIWORD)
MAKELPARAM(TRUE, 0)

ちなみに、このような合体されたデータから元のデータを復元するにはHIWORDマクロ、LOWORDマクロを使用します。


WPARAM wparam = MAKEWPARAM(123, 456);
WORD loword = LOWORD(wparam);	//123
WORD hiword = HIWORD(wparam);	//456

WM_○○メッセージ

WM_SETFONTメッセージなどのWM_から始まるメッセージは、ボタンに限らずウィンドウ全般に対して使用できるメッセージです。
(WindowMessageの略)

対して、次に紹介するBM_から始まるメッセージはボタン用のメッセージです。
(ButtonMessageの略)
その他にもエディットコントロール用のEM_やリストボックス用のLB_など、プリフィックス(接頭辞)である程度メッセージの種類を見分けることができます。

ビットマップの表示

ボタンにはビットマップ画像を表示することもできます。
ビットマップリソースの追加およびビットマップの読み込みについてはリソースファイルビットマップの表示の項を参照してください。

ビットマップを表示するには、まずボタンスタイルにBS_BITMAPを追加します。

SendMessage関数ではBM_SETIMAGEメッセージを指定します。
WPARAMには定数IMAGE_BITMAPを指定します。
LPARAMにはビットマップハンドルを指定します。

戻り値は元々ボタンに設定されていたビットマップハンドルで、なければNULLです。


#include <windows.h>
#include "resource.h"

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP hBmp;
	HWND hBtn;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		hBtn = CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE | BS_BITMAP,
			10, 10, 64, 64,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);

		//ビットマップの読み込み
		hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));

		//ボタンにビットマップを表示
		SendMessage(hBtn, BM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hBmp);
		break;
		
	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			MessageBox(hWnd, L"クリック!", L"情報", MB_OK);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		DeleteObject(hBmp);
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果です。
ボタンにビットマップを表示

テキストの書き換え

ボタンは条件によってテキスト(キャプション)を書き換えたい時があります。
テキストの差し替えはWM_SETTEXTメッセージを送信します。

WPARAMは使用しません。
LPARAMは差し替えたい文字列を指定します。

戻り値はテキストの設定に成功した場合はTRUEです。
失敗した場合は0以下の値です。

このメッセージは失敗した時にFALSE、つまり0を返すとは限りません。


#include <windows.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hBtn;
	static BOOL state;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		hBtn = CreateWindow(
			L"BUTTON", L"OFF",
			WS_CHILD | WS_VISIBLE,
			10, 10, 60, 25,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		break;
		
	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			state = !state;
			//テキストの書き換え
			SendMessage(hBtn, WM_SETTEXT, 0, (LPARAM)(state ? L"ON" : L"OFF"));
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

ボタンをクリックするごとに「ON」「OFF」の表示が切り替わります。
WM_SETTEXTによるテキストの書き換え

なお、テキスト関係のメッセージはハンドルさえわかっていればテキスト情報を持つウィンドウやコントロールにも使用できます。
例えばウィンドウにWM_SETTEXTメッセージを送信するとタイトルバー文字列を変更することができます。
(TextOut関数などでクライアント領域に直接描画しているテキストは書き換えることはできません)

SetWindowText関数

テキストの変更はWM_SETTEXTメッセージのほか、SetWindowText関数でも行えます。

BOOL SetWindowTextW(
 HWND hWnd,
 LPCWSTR lpString
);
ウィンドウhWndのテキストを文字列lpStringに設定する。
成功した場合は0以外を、失敗した場合は0を返す。

こちらのほうが引数が少なく扱いやすいかと思います。
ただし変更可能なのは関数呼び出し元と同一プロセスのウィンドウやコントロールだけです。

SetWindowText関数は内部的にWM_SETTEXTメッセージを送信しています。
この関数のように、いくつかのメッセージはSendMessage関数で送信するのと同等の機能を持つ関数(またはマクロ関数)が用意されています。

テキストの取得

テキストの取得はWM_GETTEXTメッセージを送信します。

WPARAMは取得する最大の文字数です。
(NULL文字を含む)
LPARAMは文字列を格納するバッファへのポインタです。

戻り値は取得された文字数です。
(NULL文字は含まない)

バッファサイズが足りない場合でも文字列の終端にはNULL文字が挿入されます。
(ただし文字列は途中で切れます)


#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100

#define BUFFERSIZE 32

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hBtn;
	WCHAR buf[BUFFERSIZE];

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		hBtn = CreateWindow(
			L"BUTTON", L"あいうえお",
			WS_CHILD | WS_VISIBLE,
			10, 10, 120, 25,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		break;
		
	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			//ボタンテキストの取得
			SendMessage(hBtn, WM_GETTEXT, (WPARAM)BUFFERSIZE, (LPARAM)buf);
			MessageBox(hWnd, buf, L"ボタンのテキスト", MB_OK);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

ボタンのテキストを取得し、メッセージボックスに表示しています。
テキストの取得

GetWindowText関数

テキストの取得はWM_GETTEXTメッセージのほか、GetWindowText関数でも行えます。
ただし取得可能なのは同一プロセスのウィンドウやコントロールだけです。

int GetWindowTextW(
 HWND hWnd,
 LPWSTR lpString,
 int nMaxCount
);
ウィンドウhWndのテキストを文字列バッファlpStringにnMaxCount文字分コピーする。
成功した場合はコピーされた文字列の文字数を返す。
失敗した場合は0を返す。

テキストの長さの取得

テキストの長さの取得はWM_GETTEXTLENGTHメッセージを送信します。

WPARAMLPARAMは使用しないので、両方とも0を指定します。
戻り値は文字列の文字数です。
(終端のNULL文字は含まない)


#include <windows.h>
#include <strsafe.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100

#define BUFFERSIZE 32

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static const WCHAR *format = L"ボタンテキストの長さは%uです。";
	static HWND hBtn;

	size_t length;	
	WCHAR buf[BUFFERSIZE];

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		//ボタンの作成
		hBtn = CreateWindow(
			L"BUTTON", L"あいうえお",
			WS_CHILD | WS_VISIBLE,
			10, 10, 120, 25,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		break;

	case WM_COMMAND: //コントロールの操作
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			//ボタンテキストの長さを取得
			length = (size_t)SendMessage(hBtn, WM_GETTEXTLENGTH, 0, 0);
			StringCchPrintf(buf, BUFFERSIZE, format, length);
			MessageBox(hWnd, buf, L"情報", MB_OK);
			break;
		}
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

実行結果です。
テキストの長さを取得

GetWindowTextLength関数

テキストの長さの取得はWM_GETTEXTLENGTHのほか、GetWindowTextLength関数でも行えます。
ただし取得可能なのは同一プロセスのウィンドウやコントロールだけです。

int GetWindowTextLengthW(
 HWND hWnd
);
ウィンドウhWndのテキストの長さを返す。

ボタンのクリック

ボタンのクリックをプログラムから行う場合はBM_CLICKメッセージを使用します。

WPARAMLPARAM共に使用しないのでNULL(または0)を指定します。
戻り値はありません。


HWND hBtn;
hBtn = CreateWindow(L"BUTTON"
	//省略

//hBtnをクリック
SendMessage(hBtn, BM_CLICK, 0, 0);

ボタンの強調表示

ボタンの状態を強調表示状態にするにはBM_SETSTATEメッセージを使用します。

WPARAMTRUEを指定すると強調表示がオンになります。
FALSEを指定するとオフになります。
LPARAMは使用しないのでNULL(または0)を指定します。
戻り値はありません。

強調表示とはボタンを押している状態の「外見」のことです。
このメッセージはボタンの見た目が変化するだけで、ボタンのクリックは発生しません。
(WM_COMMANDメッセージは送信されない)


#include <windows.h>

//ウィンドウの生成等は省略

#define IDC_BUTTON1 100

//ウィンドウプロシージャ
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HWND hBtn;

	HDC hdc;
	PAINTSTRUCT ps;

	switch (message)
	{
	case WM_CREATE: //ウィンドウ作成
		hBtn = CreateWindow(
			L"BUTTON", L"Click",
			WS_CHILD | WS_VISIBLE,
			10, 10, 60, 20,
			hWnd, (HMENU)IDC_BUTTON1, hInst, NULL);
		break;

	case WM_LBUTTONDOWN: //マウス左ダウン
		SendMessage(hBtn, BM_SETSTATE, TRUE, 0);
		break;
	case WM_LBUTTONUP: //マウス左アップ
		SendMessage(hBtn, BM_CLICK, 0, 0);
		SendMessage(hBtn, BM_SETSTATE, FALSE, 0);
		break;

	case WM_RBUTTONDOWN: //マウス右ダウン
		SendMessage(hBtn, BM_SETSTATE, TRUE, 0);
		break;
	case WM_RBUTTONUP: //マウス右アップ
		SendMessage(hBtn, BM_SETSTATE, FALSE, 0);
		break;

	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDC_BUTTON1:
			MessageBox(hWnd, L"ボタンをクリックしました。", L"情報", MB_OK);
			break;
		}
		break;

	case WM_PAINT:
		hdc = BeginPaint(hWnd, &ps);
		TextOut(hdc, 10, 40, L"マウス左: ボタンをクリック", 14);
		TextOut(hdc, 10, 65, L"マウス右: ボタンを押下(見た目だけ)", 19);
		EndPaint(hWnd, &ps);
		break;

	case WM_DESTROY: //ウィンドウの破棄
		PostQuitMessage(0);
		break;

	default:
		return DefWindowProc(hWnd, message, wParam, lParam);
	}
	return 0;
}

ボタンのクリック/強調表示のサンプルです。
ボタンクリックメッセージのサンプル

ウィンドウ上を左クリックするとボタンに対してBM_CLICKメッセージを送信します。
ボタンがクリックされ、メッセージボックスが表示されます。
右クリックすると同じようにボタンが押されますが、BM_SETSTATEメッセージで見た目を変化させているだけで実際にクリック処理は発生しません。

PostMessage関数

SendMessage関数と似た動作をするものにPostMessage関数があります。

BOOL PostMessage(
 HWND hWnd,
 UINT Msg,
 WPARAM wParam,
 LPARAM lParam
);
ウィンドウhWndのメッセージキューにメッセージMsgをポストする。
追加のデータとしてwParam、lParamを指定できる。
成功した場合は0以外、失敗した場合は0を返す。

SendMessage関数はウィンドウプロシージャを直接実行します。
送信したメッセージの処理が終了するまで呼び出し元は待機し、処理結果を戻り値として取得します。
いわゆる同期処理です。

PostMessage関数はウィンドウのメッセージキューにメッセージを送信します。
メッセージキューに送信されたメッセージは、システムが都合の良いタイミングで実行します。
呼び出し元はウィンドウプロシージャの処理を待たないので、すぐに制御が返ってきます。
いわゆる非同期処理です。
メッセージを送信してもすぐに処理が実行される保証はなく、処理の実行結果を戻り値で受け取ることはできません。
(メッセージの「ポスト」とも言います。マイクロソフトの翻訳では「投稿」となっていることもあります。)

非同期であるため、PostMessage関数の実行から送信先のウィンドウプロシージャの処理の実行までの間には多少の時間差があります。
そのため、パラメーターにポインタを指定する場合は注意が必要です。
PostMessage関数の実行時にはポインタが指す先のデータが存在していても、メッセージの送信先がデータを処理する時には既にそのデータが解放されていて存在しない(不定なデータである)可能性があるためです。
特にローカル変数のポインタを指定する場合は変数の寿命に気を付けましょう。