CreateFontとSelectObjectでカスタムフォントを使う方法
Windows APIで文字の見た目を変更する場合、GDI(Graphics Device Interface)のフォントオブジェクトを作成し、描画対象のデバイスコンテキストへ選択します。中心となる関数は、フォントを生成するCreateFontと、そのフォントをDCへ設定するSelectObjectです。
この2つを正しく組み合わせるには、フォントの作成、選択、描画、元のフォントへの復帰、オブジェクトの解放という順序を理解する必要があります。単に見た目を変えるだけでなく、リソース管理やDPI、Unicode文字への対応も重要です。
CreateFontでフォントオブジェクトを作成する
CreateFontは、指定したサイズ、太さ、傾き、書体などの属性を持つHFONTハンドルを返します。現在のWindowsアプリケーションでは、マルチバイト文字列を使うCreateFontAよりも、Unicode対応のCreateFontWを使う構成が扱いやすいでしょう。
HFONT hFont = CreateFontW(
-24, // 高さ
0, // 幅
0, // Escapement
0, // Orientation
FW_NORMAL, // 太さ
FALSE, // 斜体
FALSE, // 下線
FALSE, // 取り消し線
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
VARIABLE_PITCH | FF_DONTCARE,
L"メイリオ"
);
第1引数の高さに負数を指定すると、文字の内部高さを基準にしたサイズとして扱えます。正確なポイント数を画面解像度に合わせたい場合は、LOGPIXELSYを取得し、MulDivで論理単位へ変換します。書体名は環境依存なので、特定のフォントがない場合に備えて代替フォントも考慮します。
SelectObjectで描画用DCへ設定する
作成したHFONTは、SelectObjectでデバイスコンテキストへ選択します。関数の戻り値は、選択前にDCへ設定されていたオブジェクトです。描画後にこのハンドルを使って元のフォントへ戻すことが、GDIオブジェクトを安全に扱う基本になります。
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HFONT oldFont = static_cast<HFONT>(
SelectObject(hdc, hFont)
);
RECT rc{ 20, 20, 500, 100 };
DrawTextW(
hdc,
L"カスタムフォントで描画します",
-1,
&rc,
DT_LEFT | DT_VCENTER | DT_SINGLELINE
);
SelectObject(hdc, oldFont);
EndPaint(hwnd, &ps);
SelectObjectはフォントだけでなく、ペン、ブラシ、ビットマップなどにも使われます。ただし、戻り値の型は選択したオブジェクトに応じて扱います。フォント選択に失敗した場合はNULLが返る可能性があるため、作成結果と戻り値を確認すると原因を追いやすくなります。
GDIリソースを正しい順序で解放する
CreateFontで作ったフォントは、使用後にDeleteObjectで解放します。ただし、HFONTが現在のDCに選択されたままの状態で削除してはいけません。必ず元のフォントを再選択してから、作成したフォントを削除します。
HFONT hFont = CreateFontW(
-MulDiv(10, GetDeviceCaps(hdc, LOGPIXELSY), 72),
0, 0, 0, FW_BOLD,
FALSE, FALSE, FALSE,
DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS,
CLEARTYPE_QUALITY,
DEFAULT_PITCH | FF_SWISS,
L"Segoe UI"
);
if (hFont != nullptr) {
HFONT oldFont = static_cast<HFONT>(
SelectObject(hdc, hFont)
);
TextOutW(hdc, 10, 10, L"文字列", 3);
SelectObject(hdc, oldFont);
DeleteObject(hFont);
}
ウィンドウの再描画が多い場合、WM_PAINTのたびにフォントを作成するより、WM_CREATEで生成してウィンドウごとに保持し、WM_DESTROYで解放する方が効率的です。一方、一時的な印字処理だけで使うフォントなら、処理の範囲内で作成と削除を完結させる方法が適しています。
文字の大きさや太さだけでなく、背景色、行間、余白との組み合わせによって可読性は変わります。ゲーム画面などで文字の優先順位や視線誘導を考えるときは、視覚設計の参考例のような画面構成も、UIを検討する材料になります。
文字コードと描画環境を確認する
日本語を表示する場合、CreateFontWとTextOutW、DrawTextWのように、フォント作成と描画の両方をUnicode版で統一します。A版とW版を混在させると、文字列変換の箇所で文字化けが起こることがあります。ウィンドウプロシージャやソースファイルもUnicodeを前提にすると管理しやすくなります。
フォントサイズは、画面のピクセル数だけで決めると高DPI環境で小さく見える場合があります。論理インチあたりのピクセル数を取得してポイントから変換し、必要に応じてDPI変更通知にも対応します。Per-Monitor DPI Awarenessを利用するアプリケーションでは、モニター移動後にフォントを再生成する設計が必要です。
また、CreateFontで指定した書体がシステムに存在しない場合、Windowsは近いフォントへ置き換えることがあります。最終的な表示結果を固定したい場合は、対象環境にフォントを用意するか、アプリケーションへ埋め込んだフォントを適切なライセンスのもとで利用します。
用途に応じた実装を選ぶ
次のように、処理の目的によってフォントの管理方法を分けると、コードの責任範囲が明確になります。
| 用途 | フォントの作成時期 | 解放時期 | 主な注意点 |
|---|---|---|---|
| WM_PAINT内の一時描画 | 描画処理の直前 | 元のフォントへ戻した直後 | 毎回の生成コストに注意 |
| ウィンドウ固有の表示 | WM_CREATE | WM_DESTROY | ハンドルの所有者を明確にする |
| ダイアログや子コントロール | 初期化時 | コントロール破棄前 | WM_SETFONTなどとの使い分け |
| 複数画面で共有 | アプリケーション初期化時 | アプリケーション終了時 | 共有中に削除しない |
| 高DPI対応画面 | DPI確定後または変更時 | 古いフォントを差し替える時 | 論理単位で再計算する |
標準コントロールへフォントを設定する場合は、直接DCへSelectObjectするのではなく、WM_SETFONTメッセージを送る方法が一般的です。自分で行う描画処理にはSelectObjectを使い、ボタンやエディットなどの管理されたコントロールには、そのコントロールの仕組みに合わせます。
Win32 APIの宣言や定数、GDI関連関数を調べるときは、関連するAPI資料も参照先の一つになります。実装では、関数の戻り値を確認し、フォントハンドルの所有者と解放場所を決めておくことが、見た目の調整以上に重要です。
覚えておきたいのは、CreateFontでHFONTを作り、SelectObjectでDCへ設定し、描画後に元のオブジェクトへ戻してからDeleteObjectで解放する流れです。Unicode、DPI、フォントの存在確認まで含めて設計すれば、カスタムフォントを使ったGDI描画を安定して管理できます。