文字の表示その2

文字列に書式を指定する

前ページで使用したTextOut関数は、文字列を手軽に描画できる関数です。
その代わり機能が少なく、実は改行することさえできません。
改行文字(\n)は奇妙な記号に化けますし、ウィンドウの右端を超えても自動改行などはされません。
(超えた分は画面に表示されません)

もう少し高機能な文字列出力関数にDrawText関数があります。
その説明の前に、この関数を使いやすくするためのGetClientRect関数を説明します。

GetClientRect関数

BOOL GetClientRect(
 HWND hWnd,
 LPRECT lpRect
);
ウィンドウhWndのクライアント領域の座標情報をlpRectに格納する。
成功した場合は0以外を返す。

GetClientRect関数は、指定したウィンドウのクライアント領域全体の矩形をRECT構造体で取得できます。
第二引数はRECT構造体のポインタであることに注意してください。

基準点はクライアント領域の左上なので、RECT構造体のleftメンバとtopメンバは常に0です。
rightメンバはクライアント領域の幅、bottomメンバは高さを表します。

DrawText関数

int DrawTextW(
 HDC hdc,
 LPCWSTR lpchText,
 int cchText,
 LPRECT lprc,
 UINT format
);
書式formatに従い、矩形lprc内に文字列lpchTextをcchText文字分描画する。
戻り値は描画した文字列の高さ。

第一引数hdcは、TextOut関数と同じくデバイスコンテキストハンドルを指定します。
第二引数lpchTextは、出力したい文字列を指定します。
第三引数cchTextは出力する文字数の指定です。
ここに-1を指定すると、lpchTextから自動的に文字数を計算してくれます。

第四引数lprcは描画する領域の矩形情報を格納するRECT構造体のポインタを指定します。
DrawText関数は、この矩形領域内に文字列を描画しようとします。
サンプルコードではクライアント領域全体に描画しています。

第五引数formatは文字列の書式です。
これは以下の定数の組み合わせを指定します。

定数 説明
DT_TOP テキストの上揃え。
DT_LEFT テキストの左揃え。
DT_CENTER テキストの水平方向の中央揃え。
DT_RIGHT テキストの右揃え。
DT_VCENTER テキストの垂直方向の中央揃え。
DT_SINGLELINEと同時に指定する。
DT_BOTTOM テキストの下揃え。
DT_SINGLELINEと同時に指定する。
DT_WORDBREAK テキストの複数行表示。
テキストは右端で折り返される。
改行文字(\n)が反映される。
DT_SINGLELINE テキストの単一行表示。
テキストの折り返しは行われない。
改行文字(\n)は反映されない。
DT_EXPANDTABS タブ文字(\t)を展開する。
DT_WORD_ELLIPSISDT_PATH_ELLIPSISDT_END_ELLIPSISと同時には指定できない。
DT_TABSTOP この定数を指定するとタブ文字の広さ(文字数)を指定できる。
引数formatの15ビットから8ビット(下位ワードの上位バイト)で文字数を指定。
(初期値は8)
DT_CALCRECTDT_EXTERNALLEADINGDT_INTERNALDT_NOCLIPDT_NOPREFIXと同時に指定できない。
DT_NOCLIP クリッピングしない。
(はみ出した部分を消去しない)
描画が少し速くなる。
DT_EXTERNALLEADING 行の高さの計算にフォントの外部レディングを含める。
(通常は含まれない)
外部レディングとは行間のスペースのこと。
DT_CALCRECT テキストを表示するのに必要な矩形サイズを計算し、lprcの幅と高さを変更する。
テキストの描画は行わない。
複数行表示では、lprcの幅を使用して高さを拡張/縮小する。
テキストの長さが幅に満たない場合は幅も縮小される。
単一行表示では、幅を拡張/縮小する。
テキストの表示に高さが足りない場合は拡張され、余分な高さがある場合は縮小される。
DT_NOPREFIX プリフィックス処理を無効にする。
通常「&」はその次の文字に下線を付けて表示するという特殊な意味を持つが、これを無効にする。
例:
入力文字: "A&bc&&d"
通常の処理: "Abc&d"
DT_NOPREFIX: "A&bc&&d"
DT_INTERNAL テキストサイズの計算にシステムフォントを使用する。
DT_EDITCONTROL 複数行エディットコントロールと同じになるように描画する。
平均文字幅がエディットコントロールと同じになる。
部分的に表示される最後の行は表示されない。
DT_PATH_ELLIPSIS 矩形領域に対してテキストが長すぎる場合、領域に収まるようにテキストの途中を省略記号(...)に置き換えて表示する。
\記号を含む文字列でないと効果がないのかもしれない。
(長いパス文字列を省略して表示するためのフラグか)
DT_MODIFYSTRINGと同時に指定するとlpchTextに変更を加える。
DT_END_ELLIPSIS 矩形領域に対してテキストが長すぎる場合、はみ出す部分がテキストの末尾であれば領域に収まるようにテキストの末尾を省略記号(...)に置き換えて表示する。
末尾以外の部分がはみ出す場合は省略記号なしで切り取られる。
DT_MODIFYSTRINGと同時に指定するとlpchTextに変更を加える。
DT_MODIFYSTRING DT_END_ELLIPSISDT_PATH_ELLIPSISと同時に指定するとlpchTextを置き換える。
このフラグを使用するとlpchTextは元のテキストの文字数よりも最大で4文字分の追加の可能性があるので、その分の余裕を持たせておく必要がある。
DT_RTLREADING デバイスコンテキストがヘブライ語、アラビア語の場合にテキストの順序を右から左にする。
DT_WORD_ELLIPSIS 矩形領域に対してテキストが長すぎる場合、領域に収まるようにテキストの末尾を省略記号(...)に置き換えて表示する。
DT_END_ELLIPSISとの違いは、はみ出す部分がテキストの途中であっても省略される点。
(改行のあるテキストの場合に違いが現れる)
DT_NOFULLWIDTHCHARBREAK ダブルバイト文字セット(DBCS)文字で改行することを防止する。
可能な限り半角文字の場所で改行するようになる。
DT_WORDBREAKと同時に指定する。
DT_HIDEPREFIX プリフィックスを無視する。
通常「&」はその次の文字に下線を付けて表示するという特殊な意味を持つが、これを無効にし、&も表示しない。
&を表示したい場合は二つ続けて記述する。
例:
入力文字: "A&bc&&d"
通常の処理: "Abc&d"
DT_HIDEPREFIX: "Abc&&d"
DT_PREFIXONLY プリフィックスの下線のみを表示する。
通常「&」はその次の文字に下線を付けて表示するという特殊な意味を持つが、この下線のみを表示し、他の文字を表示しない。
(下線を付けられる文字自体も表示されない)
例:
入力文字: "A&bc&&d"
通常の処理: "Abc&d"
DT_PREFIXONLY: " _  "

数が多いですが、DT_WORDBREAK(複数行表示)やDT_LEFTDT_CENTERDT_SINGLELINEによるテキストの左/中央/右揃えあたりがよく使用されます。
「&」という文字はDrawText関数では特殊な意味を持ちます。
&自体を表示したい場合は「&&」と二つ続けて記述するか、DT_NOPREFIXフラグを使用します。

戻り値は描画したテキストの高さです。

サンプルコード1


#include <windows.h>

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

#define BUFFERSIZE 128

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

	RECT rect;
	WCHAR buf[BUFFERSIZE];
	static const WCHAR* format = L"クライアント領域のRECT情報\n"
		L"left  : %ld\n"
		L"top   : %ld\n"
		L"right : %ld\n"
		L"bottom: %ld";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画発生
		//クライアント領域のサイズを取得
		GetClientRect(hWnd, &rect);
		StringCchPrintf(buf, BUFFERSIZE, format,
			rect.left,
			rect.top,
			rect.right,
			rect.bottom);

		hdc = BeginPaint(hWnd, &ps);
		DrawText(hdc, buf, -1, &rect, DT_WORDBREAK);
		EndPaint(hWnd, &ps);
		break;

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

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

このコードはクライアント領域のサイズ情報(RECT構造体)を表示します。
ウィンドウサイズを変更すると数値がリアルタイムに更新されます。
(サイズ変更毎にWM_PAINTメッセージが発行されているということです)
DT_WORDBREAKフラグを指定しているので、文字列が領域幅内に収まりきらない場合は自動的に改行されます。
DrawText関数のサンプル1

また、文字列に改行(\n)が含まれていることにも注目してください。
ウィンドウ右端での自動改行のほか、任意の位置での改行も可能です。

文字列リテラルを連続して記述すると、ひとつの文字列として扱われます。
途中に空白文字(改行、半角スペース、タブ文字)が含まれていてもかまいません。


//以下はすべて同じ文字列
char str1[] = "abcdef";
char str2[] = "abc""def";
char str3[] = "abc" "def";
char str4[] = "abc"
	"def";

サンプルコード2

DrawText関数の戻り値を利用して、文字列を出力した次の行に別の文字列を出力するサンプルです。


LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rect;

	static const WCHAR* str1 = L"長~~~~~~~~~い文字列";
	static const WCHAR* str2 = L"短い文字列";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画発生
		GetClientRect(hWnd, &rect);

		hdc = BeginPaint(hWnd, &ps);
		rect.top += DrawText(hdc, str1, -1, &rect, DT_WORDBREAK);
		rect.top += DrawText(hdc, str2, -1, &rect, DT_WORDBREAK);
		EndPaint(hWnd, &ps);
		break;

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

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

一行目が改行によって複数行になった場合でも、二行目はそのすぐ下に表示されます。
DrawText関数のサンプル2

DrawTextEx関数

DrawText関数の拡張版にDrawTextEx関数というものもあります。

int DrawTextExW(
 HDC hdc,
 LPWSTR lpchText,
 int cchText,
 LPRECT lprc,
 UINT format,
 LPDRAWTEXTPARAMS lpdtp
);
書式formatに従い、矩形lprc内に文字列lpchTextをcchText文字分描画する。
lpdtpで拡張フォーマットを指定できる。
戻り値は描画した文字列の高さ。

DrawText関数との違いは、引数の最後に拡張フォーマットの指定が追加されている点です。
拡張フォーマットはDRAWTEXTPARAMS構造体を使用して指定します。
(LPDRAWTEXTPARAMSDRAWTEXTPARAMSのポインタ型です)

typedef struct tagDRAWTEXTPARAMS {
 UINT cbSize;
 int iTabLength;
 int iLeftMargin;
 int iRightMargin;
 UINT uiLengthDrawn;
} DRAWTEXTPARAMS, *LPDRAWTEXTPARAMS;
DrawTextEx関数の拡張フォーマットを格納する構造体。

cbSizeメンバはこの構造体のサイズです。
つまりsizeof(DRAWTEXTPARAMS)の戻り値を指定します。

iTabLengthメンバは文字列中のタブ文字の幅を指定します。
使用するにはDT_EXPANDTABSDT_TABSTOPフラグを同時に指定します。
ちなみに初期値は8です。
(文字8つ分)

iLeftMarginメンバは左マージン、iRightMarginメンバは右マージンの設定です。
マージンというのは余白のことです。
この数値の単位はピクセル数のようです。
(Microsoft Docsには「平均文字幅」と書かれていますが)

uiLengthDrawnメンバはDrawTextEx関数の実行後に、出力した文字列の文字数が格納されます。

ちなみに拡張フォーマットを使用しない場合はDrawTextEx関数のlpdtpNULLを指定できます。


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

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

#define BUFFERSIZE 128

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

	RECT rect;
	DRAWTEXTPARAMS dtp;

	WCHAR buf[BUFFERSIZE];
	static const WCHAR* format = L"クライアント領域のRECT情報\n"
		L"left\t: %ld\n"
		L"top\t: %ld\n"
		L"right\t: %ld\n"
		L"bottom\t: %ld";

	switch (message)
	{
	case WM_PAINT: //ウィンドウの描画発生
		GetClientRect(hWnd, &rect);
		StringCchPrintf(buf, BUFFERSIZE, format,
			rect.left,
			rect.top,
			rect.right,
			rect.bottom);

		dtp.cbSize = sizeof(DRAWTEXTPARAMS);
		dtp.iTabLength = 16;
		dtp.iLeftMargin = 20;
		dtp.iRightMargin = 20;

		hdc = BeginPaint(hWnd, &ps);
		DrawTextEx(hdc, buf, -1, &rect,
			DT_WORDBREAK | DT_EXPANDTABS | DT_TABSTOP,
			&dtp);
		EndPaint(hWnd, &ps);
		break;

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

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

タブ文字幅を16に、左右のマージンを20に設定しています。
DrawTextEx関数のサンプル

文字を描画する関数は他にもいくつかありますが、TextOut関数かDrawText(Ex)関数で大抵は対応できます。