文字の表示その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_ELLIPSIS 、DT_PATH_ELLIPSIS 、DT_END_ELLIPSIS と同時には指定できない。 |
DT_TABSTOP | この定数を指定するとタブ文字の広さ(文字数)を指定できる。 引数 format の15ビットから8ビット(下位ワードの上位バイト)で文字数を指定。(初期値は8) DT_CALCRECT 、DT_EXTERNALLEADING 、DT_INTERNAL 、DT_NOCLIP 、DT_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_ELLIPSIS 、DT_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_LEFT
、DT_CENTER
、DT_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
フラグを指定しているので、文字列が領域幅内に収まりきらない場合は自動的に改行されます。
また、文字列に改行(\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;
}
一行目が改行によって複数行になった場合でも、二行目はそのすぐ下に表示されます。
DrawTextEx関数
DrawText関数の拡張版にDrawTextEx関数というものもあります。
- int DrawTextExW(
HDC hdc,
LPWSTR lpchText,
int cchText,
LPRECT lprc,
UINT format,
LPDRAWTEXTPARAMS lpdtp
); - 書式formatに従い、矩形lprc内に文字列lpchTextをcchText文字分描画する。
lpdtpで拡張フォーマットを指定できる。
戻り値は描画した文字列の高さ。
DrawText関数との違いは、引数の最後に拡張フォーマットの指定が追加されている点です。
拡張フォーマットはDRAWTEXTPARAMS構造体
を使用して指定します。
(LPDRAWTEXTPARAMS
はDRAWTEXTPARAMS
のポインタ型です)
- typedef struct tagDRAWTEXTPARAMS {
UINT cbSize;
int iTabLength;
int iLeftMargin;
int iRightMargin;
UINT uiLengthDrawn;
} DRAWTEXTPARAMS, *LPDRAWTEXTPARAMS; - DrawTextEx関数の拡張フォーマットを格納する構造体。
cbSize
メンバはこの構造体のサイズです。
つまりsizeof(DRAWTEXTPARAMS)
の戻り値を指定します。
iTabLength
メンバは文字列中のタブ文字の幅を指定します。
使用するにはDT_EXPANDTABS
とDT_TABSTOP
フラグを同時に指定します。
ちなみに初期値は8です。
(文字8つ分)
iLeftMargin
メンバは左マージン、iRightMargin
メンバは右マージンの設定です。
マージンというのは余白のことです。
この数値の単位はピクセル数のようです。
(Microsoft Docsには「平均文字幅」と書かれていますが)
uiLengthDrawn
メンバはDrawTextEx関数の実行後に、出力した文字列の文字数が格納されます。
ちなみに拡張フォーマットを使用しない場合はDrawTextEx関数のlpdtp
はNULL
を指定できます。
#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に設定しています。
文字を描画する関数は他にもいくつかありますが、TextOut関数かDrawText(Ex)関数で大抵は対応できます。