描画処理

GDI

Graphics Device Interface (GDI) は,Windows の描画処理を担当するコンポーネントの一つです。
GDI 関数を用いることで,ディスプレイやプリンタといった出力デバイスに,図形,テキスト,画像等を描画することができます。

GDI 関数による描画は,出力デバイスを問わず,ほぼ同様の記述で行うことができます。
次に示すのは,文字列を描画する TextOut という GDI 関数 を使って,ディスプレイ,プリンタに文字列を描画するコードの例です。

TCHAR szText[] = TEXT("This is some sample text.");
HDC hdc;

// ディスプレイに文字列を描画
hdc = GetDC(hwnd);
TextOut(hdc, 0, 0, szText, lstrlen(szText));
ReleaseDC(hwnd, hdc);

// プリンタに文字列を描画
hdc = GetPrinterDC();
TextOut(hdc, 0, 0, szText, lstrlen(szText));
DeleteDC(hdc);

ここで hdc はデバイスコンテキスト (device context) と呼ばれるデータ構造へのハンドルです。
デバイスコンテキストは GDI が管理するデータ構造であり,出力デバイスの属性が格納されます。

デバイスコンテキスト

本章の残りの部分では,ウィンドウのクライアント領域に描画を行うための基本操作について説明します。

## 作例

次のような長方形を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    TCHAR szAppName[] = TEXT("TestApp");
    WNDCLASS wc;
    HWND hwnd;
    MSG msg;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hInstance;
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = szAppName;

    if (!RegisterClass(&wc)) return 0;

    hwnd = CreateWindow(
        szAppName, TEXT(""),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT,
        CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL,
        hInstance, NULL);

    if (!hwnd) return 0;

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while (GetMessage(&msg, NULL, 0, 0) > 0) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);       // 描画開始
        Rectangle(hdc, 50, 50, 200, 150);  // 描画
        EndPaint(hwnd, &ps);               // 描画終了
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

更新領域

次の図のようにウィンドウを移動すると,隠れて見えていなかった部分の再描画が必要となります。
図の斜線部分は,再描画が必要な領域であることから,更新領域 (update region) と呼ばれます。
更新領域は,ウィンドウが初めて表示される時や,ウィンドウのサイズが変更された時などにも発生します。

更新領域の説明

更新領域が発生すると,システムは WM_PAINT メッセージを発行し,メッセージキューにポストします。

メッセージコード説明
WM_PAINTウィンドウの再描画が必要な時に発生

WM_PAINT メッセージは比較的優先度の低いメッセージです。
GetMessage 関数は,他のメッセージがメッセージキューから無くなるまで,WM_PAINT メッセージを取り出しません。

描画処理

ウィンドウのクライアント領域の描画は,WM_PAINT メッセージを受け取った時に行うのが基本です。

今回のプログラムの描画に関係する処理は,次のように書かれています。

case WM_PAINT:
    hdc = BeginPaint(hwnd, &ps);       // 描画開始
    Rectangle(hdc, 50, 50, 200, 150);  // 描画
    EndPaint(hwnd, &ps);               // 描画終了
    return 0;

BeginPaint は描画を開始する関数,EndPaint は描画を終了する関数です。
WM_PAINT メッセージに対する応答では,実際の描画処理を行う前後で,BeginPaint 関数と EndPaint 関数を呼び出す必要があります。

BeginPaint 関数 [MSDN]

描画を開始します。この関数は更新領域を有効化します。

HDC BeginPaint(
    HWND hWnd,             // ウィンドウハンドル
    LPPAINTSTRUCT lpPaint  // PAINTSTRUCT 構造体
);

EndPaint 関数 [MSDN]

描画を終了します。

BOOL EndPaint(
    HWND hWnd,                  // ウィンドウハンドル
    CONST PAINTSTRUCT *lpPaint  // PAINTSTRUCT 構造体
);

PAINTSTRUCT 構造体は,GDI が使用する描画情報を保持する構造体です。

PAINTSTRUCT 構造体 [MSDN]

typedef struct tagPAINTSTRUCT {
    HDC  hdc;              // デバイスコンテキスト
    BOOL fErase;           // 背景を消去するか
    RECT rcPaint;          // 再描画を行う矩形領域
    BOOL fRestore;         // 予約済
    BOOL fIncUpdate;       // 予約済
    BYTE rgbReserved[32];  // 予約済
} PAINTSTRUCT;

fErase メンバの値が TRUE の時には,BeginPaint 関数は WM_ERASEBKGND メッセージを送信し,背景の消去を行います。

他の場所での描画

WM_PAINT メッセージに対する応答以外の場所でクライアント領域の描画処理を行うには,次のように,BeginPaint, EndPaint 関数の代わりに GetDC, ReleaseDC 関数を呼び出します。

hdc = GetDC(hwnd);                 // DC 取得
Rectangle(hdc, 50, 50, 200, 150);  // 描画
ReleaseDC(hwnd, hdc);              // DC 解放

各関数のプロトタイプは次の通りです。

GetDC 関数 [MSDN]

デバイスコンテキストを取得します。

HDC GetDC(
    HWND hWnd  // ウィンドウハンドル
);

ReleaseDC 関数 [MSDN]

デバイスコンテキストを解放します。

int ReleaseDC(
    HWND hWnd,  // ウィンドウハンドル
    HDC hdc     // デバイスコンテキストへのハンドル
);

長方形の描画

長方形の描画に用いた関数は,次の Rectangle 関数です。

Rectangle 関数 [MSDN]

長方形を描画します。

BOOL Rectangle(
    HDC hdc,    // デバイスコンテキストへのハンドル
    int left,   // 左上端の x 座標
    int top,    // 左上端の y 座標
    int right,  // 右下端の x 座標
    int bottom  // 右下端の y 座標
);

詳細は 図形の描画 で説明します。

領域の無効化

図のように,マウスの左ボタンをクリックすると,カーソル位置を中心に正方形を描画するプログラムを作ります。

作例

#include <windows.h>

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(
    HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine, int nCmdShow)
{
    // 省略
}

LRESULT CALLBACK WndProc(
    HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    static int x, y;
    HDC hdc;
    PAINTSTRUCT ps;

    switch (uMsg) {
    case WM_LBUTTONDOWN:
        y = HIWORD(lParam);
        x = LOWORD(lParam);
        InvalidateRect(hwnd, NULL, TRUE);  // 領域無効化
        return 0;

    case WM_PAINT:
        hdc = BeginPaint(hwnd, &ps);
        Rectangle(hdc, x - 10, y - 10, x + 10, y + 10);
        EndPaint(hwnd, &ps);
        return 0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }

    return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

WM_LBUTTONDOWN メッセージへの応答では,座標の更新と,クライアント領域の無効化のみを行い,実際の描画処理は WM_PAINT メッセージへの応答に記述しています。

InvalidateRect 関数は,矩形で指定した領域を無効化する関数です。
今回は第 2 引数に NULL を指定し,クライアント領域全体を無効化しています。

InvalidateRect 関数 [MSDN]

矩形領域を更新領域に追加します。

BOOL InvalidateRect(
    HWND hWnd,           // ウィンドウハンドル
    CONST RECT *lpRect,  // 無効化する矩形領域
    BOOL bErase          // 背景を消去するか
);
lpRect
無効化する領域を矩形で指定します。クライアント領域全体を無効化するには NULL を指定します。
bErase
背景を消去するかを指定します。TRUE を指定すると,BeginPaint 関数によって背景が消去されます。

矩形領域の代わりに任意の形状のリージョンを指定できる InvalidateRgn 関数もあります。
ここでは関数のプロトタイプのみ紹介します。

InvalidateRgn 関数 [MSDN]

リージョンを更新領域に追加します。

BOOL InvalidateRgn(
    HWND hWnd,   // ウィンドウハンドル
    HRGN hRgn,   // 無効化するリージョンへのハンドル
    BOOL bErase  // 背景を消去するか
);