CreatePenとCreateSolidBrushでGDI描画を整える方法
Windows APIで線や塗りつぶしを描画する場合、GDI(Graphics Device Interface)のペンとブラシを使い分けます。CreatePenは線の色や太さ、種類を指定するための関数であり、CreateSolidBrushは指定色で内部を塗るブラシを作成する関数です。
両者は単独で画面へ描画するものではありません。作成したGDIオブジェクトをSelectObjectでデバイスコンテキスト(HDC)へ選択し、その状態でLineTo、Rectangle、Ellipseなどを呼び出します。描画後は元のオブジェクトを戻し、作成したペンやブラシをDeleteObjectで解放することが重要です。
この管理を省略すると、色が意図せず残ったり、別の図形へ設定が引き継がれたりします。さらに、ウィンドウを長時間表示するアプリケーションではGDIオブジェクトの枯渇につながるため、作成、選択、復元、削除という流れを基本形として覚えておくと安全です。
| 項目 | CreatePen | CreateSolidBrush |
|---|---|---|
| 役割 | 線の外観を指定 | 図形内部の塗りつぶしを指定 |
| 主な設定 | 線種、太さ、色 | 色 |
| 戻り値 | HPEN | HBRUSH |
| 使用例 | 枠線、直線、円の輪郭 | 長方形、円、領域の塗り |
| 解放方法 | DeleteObject | DeleteObject |
CreatePenで線の属性を指定する
CreatePenの基本形は次のとおりです。
HPEN hPen = CreatePen(PS_SOLID, 3, RGB(0, 120, 215));
第1引数は線種、第2引数は幅、第3引数は色です。PS_SOLIDなら実線になり、PS_DASH、PS_DOT、PS_DASHDOTなどを使うと破線や点線を作れます。線幅を太くすると見やすくなりますが、細い破線では線種が期待どおりに表示されない場合があります。
作成に失敗した場合、戻り値はNULLです。したがって、描画処理へ進む前にハンドルの有効性を確認します。高DPI環境や拡大表示を考慮するなら、固定値の太さをそのまま使うのではなく、表示倍率に応じて計算する設計も有効です。
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(30, 30, 30));
if (hPen == NULL) {
return;
}
CreateSolidBrushで内部を塗りつぶす
CreateSolidBrushは単色ブラシを作成します。たとえば、青い長方形を描くには次のように記述できます。
HBRUSH hBrush = CreateSolidBrush(RGB(240, 248, 255));
ブラシは、長方形や楕円、多角形の内部を塗る目的で使われます。枠線をペン、内部をブラシとして別々に指定すれば、輪郭と塗りの色を分離できます。片方だけを変更したい場合にも扱いやすく、ボタン、状態表示、簡易グラフなどの描画に向いています。
ウィンドウの位置やハンドルを調べて描画対象を確認する処理では、ウィンドウ検索解説で扱われるようなFindWindowやFindWindowExが補助的に役立つことがあります。ただし、対象ウィンドウのHDCを直接取得して描く方法は、所有権や再描画の問題があるため、通常は自分のウィンドウのWM_PAINT内で描画する構成が適切です。
HDCへ選択して図形を描く
作成したペンとブラシは、SelectObjectでHDCへ選択します。選択前のオブジェクトを保存し、描画が終わったら必ず元へ戻します。
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
HPEN hPen = CreatePen(PS_SOLID, 3, RGB(0, 90, 160));
HBRUSH hBrush = CreateSolidBrush(RGB(220, 240, 255));
if (hPen != NULL && hBrush != NULL) {
HPEN oldPen = (HPEN)SelectObject(hdc, hPen);
HBRUSH oldBrush = (HBRUSH)SelectObject(hdc, hBrush);
Rectangle(hdc, 40, 40, 240, 140);
Ellipse(hdc, 280, 40, 440, 140);
SelectObject(hdc, oldBrush);
SelectObject(hdc, oldPen);
}
if (hBrush != NULL) {
DeleteObject(hBrush);
}
if (hPen != NULL) {
DeleteObject(hPen);
}
EndPaint(hwnd, &ps);
return 0;
}
BeginPaintとEndPaintは、通常のウィンドウ再描画で使う基本的な組み合わせです。描画領域が無効化されたときにWM_PAINTを処理し、ウィンドウの移動やほかの画面による遮蔽後にも同じ内容を再現できるようにします。
リソース管理と描画状態を分離する
GDIオブジェクトは、作成したスコープや初期化処理に応じて管理方法を決めます。毎回同じ色のペンやブラシを作り直すと処理回数が増えるため、固定的な外観のアプリケーションでは起動時に作成して終了時に解放する方法もあります。一方、色や太さが頻繁に変わる場合は、不要になった旧オブジェクトを確実に削除します。
HDCへ選択中のペンやブラシを削除してはいけません。先に元のオブジェクトへ戻してから削除する順番を守ります。また、GetStockObjectで取得したストックオブジェクトは、通常、自分でDeleteObjectを呼びません。作成したオブジェクトとシステム提供のオブジェクトを区別することが必要です。
描画の状態を確認するには、線色、塗り色、座標、再描画のタイミングを個別に記録すると原因を追いやすくなります。ブラウザで表示するサンプルや説明画面を作る場合も、メガウェイズの解説記事のように情報量の多いページを想定すると、文字やリンクとの重なりを避けるレイアウト確認が役立ちます。
安定した描画へ進む確認項目
実装時は、見た目だけでなくハンドルの寿命と再描画への対応を確認します。次の項目を順番に点検すると、典型的なGDIの不具合を減らせます。
CreatePenとCreateSolidBrushの戻り値がNULLでないか確認するSelectObjectの前に元のペンとブラシを保存する- 描画後に元のオブジェクトをHDCへ戻す
- 作成したGDIオブジェクトだけを
DeleteObjectで解放する - 描画処理を
WM_PAINTとBeginPaint、EndPaintの流れに合わせる - 色、線幅、座標を定数や設定値として分離する
線と塗りを同じHDCで扱う場合でも、ペンとブラシは別々のリソースです。どちらか一方の作成に失敗したときの後処理も考慮し、成功したオブジェクトだけを解放するようにします。特に複数の図形を描くコードでは、描画状態を必要以上に残さないことが保守性につながります。
まず小さなウィンドウでWM_PAINTに長方形を1つ描き、ペンの色、ブラシの色、線幅、DeleteObjectの順序を確認する実装から始めます。