【MFC】CPaintDC vs CClientDC vs CWindowDC

【MFC】CPaintDC vs CClientDC vs CWindowDC

MFCで描画処理を書くとき、最初に必要になるのがデバイスコンテキスト(DC)です。MFCにはCPaintDC、CClientDC、CWindowDCの3つのDCラッパークラスがありますが、それぞれ用途が異なります。


目次

3つのDCクラスの比較

クラス対応API描画領域使用場面
CPaintDCBeginPaint / EndPaintクライアント領域(無効化された部分)OnPaint内のみ
CClientDCGetDC / ReleaseDCクライアント領域(全体)OnPaint以外での即時描画
CWindowDCGetWindowDC / ReleaseDCウィンドウ全体(タイトルバー・枠線含む)非クライアント領域への描画

実行結果では、3種のDCがどの領域を対象にするのかを模式図で並べて確認できます。CPaintDCは無効化されたクライアント領域だけ、CClientDCはクライアント領域全体、CWindowDCはタイトルバーや枠線を含むウィンドウ全体を扱う、という対応関係が見て取れます。

CPaintDCとCClientDCとCWindowDCの描画対象領域を比較した実行結果

CPaintDC:OnPaintの定番

CPaintDCはWM_PAINTメッセージハンドラ(OnPaint)でのみ使うクラスです。コンストラクタでBeginPaint、デストラクタでEndPaintを呼びます。

void CMyView::OnPaint()
{
    CPaintDC dc(this);  // BeginPaint が呼ばれる

    dc.TextOut(10, 10, _T("Hello, MFC!"));

    // デストラクタで EndPaint が呼ばれる
}

OnPaint内ではCPaintDCを使ってください。CClientDCをOnPaint内で使うと、BeginPaintによるupdate regionのクリアが行われず、WM_PAINTが繰り返し発生して高CPUの原因になります。


CClientDC:OnPaintの外で描画する

CClientDCはOnPaint以外の場所で即座に描画したいときに使います。よく使うのはマウス操作中のリアルタイム描画です。

void CMyView::OnMouseMove(UINT nFlags, CPoint point)
{
    if (nFlags & MK_LBUTTON)
    {
        CClientDC dc(this);  // GetDC が呼ばれる

        dc.SetPixel(point, RGB(0, 0, 0));

        // デストラクタで ReleaseDC が呼ばれる
    }

    CView::OnMouseMove(nFlags, point);
}

ただし、CClientDCで描いた内容は永続しません。ウィンドウが最小化されて復帰したり、他のウィンドウに隠されて再表示されたりすると消えます。消えてほしくない内容は、データをメンバ変数に持っておき、OnPaintでも描くようにしてください。


CWindowDC:タイトルバーや枠線に描く

CWindowDCはウィンドウ全体(非クライアント領域を含む)を描画対象とします。タイトルバーにカスタム描画するときなどに使います。

void CMyFrame::OnNcPaint()
{
    CWindowDC dc(this);  // GetWindowDC が呼ばれる

    CRect rc;
    GetWindowRect(&rc);
    rc.OffsetRect(-rc.left, -rc.top);

    // タイトルバー領域に描画
    dc.FillSolidRect(0, 0, rc.Width(), 30, RGB(50, 50, 50));

    // デストラクタで ReleaseDC が呼ばれる
}

OSバージョンやテーマで挙動が変わりやすいので、互換性まわりは注意してください。


よくある間違い

間違い結果正しい方法
OnPaint内でCClientDCを使うWM_PAINTが繰り返し発生し高CPUの原因になるOnPaintではCPaintDCを使う
OnPaint外でCPaintDCを使うWM_PAINT用のDCを前提外で使うことになり、再描画の説明と実装がずれるOnPaint外ではCClientDCを使う
DCをメンバ変数に保持するリソースリーク。DCは短期間で解放すべき必要なときにスタック上で生成する

まとめ

  • CPaintDCはOnPaint専用で、BeginPaint / EndPaintを自動で呼びます
  • CClientDCはOnPaint以外で即時描画するときに使います
  • CWindowDCはタイトルバーなど非クライアント領域への描画用で、出番はほとんどありません
  • OnPaintでCClientDCを使うとWM_PAINTが繰り返し発生し、高CPUの原因になります
目次