TextOutとDrawTextで文字列を描画する違い
Windows APIで文字列を表示する方法として、GDIのTextOutとDrawTextはよく使われます。どちらもデバイスコンテキスト(HDC)へ文字を描画する関数ですが、想定している用途はかなり異なります。単純な一行表示にはTextOut、領域内のレイアウトを伴う表示にはDrawTextが適しています。
両者の違いは、文字列を出力できるかどうかではなく、描画前にどこまで処理を任せられるかにあります。座標を細かく制御したいのか、折り返しや中央揃えを自動化したいのかを判断すると、APIの選択で迷いにくくなります。
TextOutの基本的な性質
TextOutは、指定した開始位置から文字列を順番に描画する関数です。代表的な形式は次のとおりです。
TextOutW(hdc, x, y, text, length);
xとyは文字列を描き始める位置で、lengthには終端のヌル文字を含めない文字数を渡します。GDIが現在選択しているフォント、文字色、背景モードなどを使って描画するため、表示位置と描画属性を呼び出し側で明確に管理できます。
TextOutは、行の折り返しや複数行の配置を自動では行いません。文字列に改行コードが含まれていても、DrawTextのような段落レイアウトは期待できません。複数行を描く場合は、行ごとに分割してTextOutを呼び出すか、文字幅を測定しながら次の行へ移動する処理が必要です。
DrawTextが担うレイアウト処理
DrawTextは、指定した長方形の中へ文字列を配置するための関数です。折り返し、複数行、左右の揃え、上下方向の配置、クリッピングなどをフラグで指定できます。
RECT rc{ 20, 20, 320, 140 };
DrawTextW(hdc, text, -1, &rc, DT_WORDBREAK | DT_LEFT);
DT_WORDBREAKを指定すると、長い文字列を矩形の幅に合わせて折り返せます。DT_CENTERやDT_RIGHTを使えば水平方向の配置を変更でき、DT_VCENTERとDT_SINGLELINEを組み合わせれば、矩形の中央に一行を配置する処理も簡単です。
ただし、DrawTextは便利な分、内部で文字幅の計算や行配置を行います。描画範囲を厳密に制御したい場面や、多数の短い文字列を高速に描く場面では、TextOutのほうが処理の意図を明確にしやすいことがあります。UIスレッドでの描画タイミングを考える際は、スレッド処理の注意点も確認しておくと安全です。
機能と使い分けの比較
両方の関数はHDCを対象にするため、フォントや色などのGDI状態を共有します。しかし、文字列の配置を関数へ任せる範囲、戻り値の意味、矩形の扱いには明確な差があります。
| 比較項目 | TextOut | DrawText |
|---|---|---|
| 主な用途 | 固定位置への一行描画 | 矩形内の文章レイアウト |
| 折り返し | 呼び出し側で実装 | フラグで指定可能 |
| 複数行 | 行ごとの処理が必要 | 改行や折り返しに対応 |
| 座標指定 | 開始点を直接指定 | RECTで描画領域を指定 |
| 配置制御 | 基本的に左上基準 | 左右・上下の揃えに対応 |
| 戻り値 | 成功・失敗 | 必要な高さ、または失敗値 |
| 適した表示 | ラベル、座標値、ログ | 説明文、ダイアログ、概要文 |
DrawTextの戻り値は、通常の描画では使用した高さを表します。DT_CALCRECTを付けて描画せずに呼び出せば、文字列を収めるために必要な矩形サイズを計算できます。この機能は、可変長の説明文や自動サイズ調整するコントロールに向いています。
一方、TextOutの戻り値は成功したかどうかを示す値です。描画後の文字列幅を知りたい場合は、GetTextExtentPoint32などの測定APIを別に呼び出します。描画と測定を分けて扱うため、ピクセル単位の配置計算を組み立てやすい点が特徴です。
座標、フォント、文字コードの注意点
両関数の座標は、通常、対象HDCの原点とマッピングモードに従います。ウィンドウの再描画ではWM_PAINT内のHDC、ビットマップへの描画ではメモリDCなど、適切なデバイスコンテキストを使う必要があります。フォントを選択した後は、不要になったら元のフォントを再選択するのが基本です。
ANSI版のTextOutAやDrawTextAは現在のコードページに依存します。日本語や複数言語を扱うアプリケーションでは、通常はUnicode版のTextOutWとDrawTextWを選ぶほうが安全です。文字数もバイト数ではなく、W版ではワイド文字単位として扱われるため、長さの計算方法を混同してはいけません。
背景の描画方法も結果を左右します。SetBkMode(hdc, TRANSPARENT)を指定すると、文字の背後を塗りつぶさずに描画できます。背景色を使う場合はSetBkColorの設定が反映されます。TextOutとDrawTextで同じ見た目にしたいなら、フォント、文字色、背景モード、クリッピング領域を共通に設定します。
測定と再描画を正しく扱う
DrawTextで必要な高さを調べる場合は、描画用とは別のRECTを用意し、DT_CALCRECTを指定します。計算によって矩形の右端や下端が変更されるため、元の領域を後で使うならコピーを渡します。折り返し幅を固定したいときは、計算前に矩形の幅を設定しておくことが重要です。
TextOutでは、文字列の幅と高さを測定して次の描画位置を決めます。固定幅フォントなら単純な計算も可能ですが、プロポーショナルフォントや日本語フォントでは、文字ごとの幅が異なります。GetTextMetricsやGetTextExtentPoint32を使うことで、実際のフォントに基づいた配置ができます。
再描画時には、以前の文字列が新しい文字列より長かった場合の残像にも注意が必要です。背景を先に消去する、無効領域だけを再描画する、あるいはダブルバッファリングを使うなど、描画全体の設計が求められます。Windows APIの資料を横断的に確認するなら、Windows APIの技術情報も関連関数を探す手がかりになります。
実装時に選択を誤らない基準
文字列描画の選択は、次のような基準で整理すると実装しやすくなります。
- 固定された座標へ短い文字列を置くならTextOutを使う
- 長文を領域内で折り返すならDrawTextを使う
- 表示に必要な高さを先に知りたいならDrawTextの
DT_CALCRECTを使う - 文字列の幅を測りながら独自配置するならTextOutと測定APIを組み合わせる
- 日本語や多言語対応ではUnicode版の関数を優先する
- 再描画が多い画面では、GDI状態とダブルバッファリングを管理する
TextOutは単純だから低機能、DrawTextは高機能だから常に優れている、という関係ではありません。たとえば、ゲーム画面のスコアや座標表示ではTextOutの直接性が便利です。設定画面の説明文やエラーメッセージでは、領域の大きさに応じて配置できるDrawTextが適しています。
DrawTextExを使えば、より詳細な表示オプションや追加情報を扱える場合があります。ただし、基本的な一行表示まで複雑なAPIに置き換える必要はありません。必要な機能に対して最小限の関数を選ぶことが、保守しやすいGDIコードにつながります。
TextOutは開始位置を中心に文字を描くための関数であり、DrawTextは矩形を中心に文章を整えるための関数です。折り返し、揃え、必要な高さの計算が必要ならDrawText、描画位置や測定結果を自分で管理したいならTextOutを選ぶ、この区別を覚えておけば、文字列表示の実装方針を明確にできます。