【MFC】CPaintDC vs CClientDC vs CWindowDC

【MFC】CPaintDC vs CClientDC vs CWindowDC

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


目次

3 つの DC クラスの比較

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

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

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

CPaintDC:OnPaint の定番

CPaintDCWM_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 の外で描画する

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

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 は短期間で解放すべき必要なときにスタック上で生成する

まとめ

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