6

ペイント サイクル以外で DC を使用することはできますか? ウィンドウの DC は永久に有効であることが保証されていますか?

コントロールのデバイス コンテキスト (DC) が有効な期間を把握しようとしています。

私は私が呼び出すことができることを知っています:

GetDC(hWnd);

コントロールのウィンドウのデバイス コンテキストを取得することはできますか?

Windows から WM_PAINT メッセージが送信されたら、BeginPaint / EndPaintを呼び出して、ペイントしたことを適切に認識し、無効な領域を内部的にクリアする必要があります。

BeginPaint(hWnd, {out}paintStruct);
try
   //Do my painting
finally
   EndPaint(hWnd, paintStruct);
end;

しかし、BeginPaint を呼び出すと、PAINTSTRUCT 構造内の DC も返されます。これは、私がペイントする必要がある DC です。

BeginPaint() によって返される DC が、GetDC() から取得する DC と同じであるというドキュメントは見つかりません。

特にデスクトップ コンポジションの時代に、BeginPaint 以外で取得した DC にペイントすることは有効ですか?

ペイント サイクル中に DC をペイントする方法は 2 つあります。

  1. DC = GetDC (hWnd);

  2. BeginPaint(&paintStruct);

3 番目の方法がありますが、私が開発している Borland Delphi のバグのようです。

WM_PAINTの処理中に、Delphi は wParam が DC であると認識し、その上に描画を開始します。MSDN によると、WM_PAINT メッセージの wParam は使用されていません。

なぜ

私の本当の目標は、永続的な DC を持つことに依存する GDI+ のより優れたパフォーマンスの機能を使用できるように、永続的な GDI+ グラフィックス オブジェクトを HDC に対して保持しようとすることです。

WM_PAINT メッセージの処理中に、キャンバスに GDI+ イメージを描画したいと考えています。次の nieve バージョンは非常に遅いです。

WM_PAINT:
{
   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   Graphics g = new Graphics(ps.hdc);
   g.DrawImage(m_someBitmap, 0, 0);
   g.Destroy();
   EndPaint(h_hwnd, ps);
}

GDI には、より高速に実行されるビットマップである CachedBitmap が含まれています。しかし、何も考えずに使用すると、パフォーマンス上の利点はありません。

WM_PAINT:
{
   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);

   Graphics g = new Graphics(ps.hdc);
   CachedBitmap bm = new CachedBitmap(m_someBitmap, g);
   g.DrawCachedBitmap(m_bm, 0, 0);
   bm.Destroy();
   g.Destroy();
   EndPaint(h_hwnd, ps);
}

パフォーマンスの向上は、CachedBitmap を 1 回作成することで得られるため、プログラムの初期化では次のようになります。

m_graphics = new Graphics(GetDC(m_hwnd));
m_cachedBitmap = new CachedBitmap(b_someBitmap, m_graphcis);

そして今、ペイントサイクルに:

WM_PAINT:
{
   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0);
   EndPaint(h_hwnd, ps);
}        

今を除いて、アプリケーションが実行されている限り、プログラムの初期化後に取得した DC がウィンドウの同じ DC になると信じています。これは、次のように存続することを意味します。

  • 素早いユーザー切り替え
  • コンポジションの有効/無効
  • テーマの切り替え
  • テーマの無効化

ウィンドウが存在する限り、特定のウィンドウに同じ DC が使用されることを保証するものは、MSDN にはありません。

注:私は良い開発者になりたいし、正しいことをしたいので、ダブル バッファリングを使用していません。* 場合によっては、ダブル バッファリングが悪いことを意味します。

4

3 に答える 3

6

私が知っている唯一の方法は、CS_OWNDCクラス スタイルでウィンドウを作成することです。

これにより、クラス内の各ウィンドウに一意のデバイス コンテキストが割り当てられます。

編集

リンクされた MSDN の記事から:

デバイス コンテキストは、アプリケーションがウィンドウのクライアント領域に描画するために使用する特別な値のセットです。システムは、ディスプレイ上のウィンドウごとにデバイス コンテキストを必要としますが、システムがそのデバイス コンテキストを格納および処理する方法にはある程度の柔軟性があります。

デバイス コンテキスト スタイルが明示的に指定されていない場合、システムは、システムが保持するコンテキストのプールから取得したデバイス コンテキストを各ウィンドウが使用すると想定します。このような場合、各ウィンドウは、描画前にデバイス コンテキストを取得して初期化し、描画後に解放する必要があります。

ウィンドウ内を描画する必要があるたびにデバイス コンテキストを取得することを避けるために、アプリケーションはウィンドウ クラスに CS_OWNDC スタイルを指定できます。このクラス スタイルは、システムにプライベート デバイス コンテキストを作成するように指示します。つまり、クラス内の各ウィンドウに一意のデバイス コンテキストを割り当てます。アプリケーションは、コンテキストを 1 回取得するだけで、その後のすべての描画に使用できます。

Windows 95/98/Me: CS_OWNDC スタイルは便利ですが、各デバイス コンテキストが 64K GDI ヒープのかなりの部分を使用するため、注意して使用してください。

おそらく、次の例は CS_OWNDC の使用法をよりよく示しています。

#include <windows.h>

static TCHAR ClassName[] = TEXT("BitmapWindow");
static TCHAR WindowTitle[] = TEXT("Bitmap Window");

HDC m_hDC;
HWND m_hWnd;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    static PAINTSTRUCT ps;

    switch (msg)
    {
    case WM_PAINT:
        {
            BeginPaint(hWnd, &ps);

            if (ps.hdc == m_hDC)
                MessageBox(NULL, L"ps.hdc == m_hDC", WindowTitle, MB_OK);
            else
                MessageBox(NULL, L"ps.hdc != m_hDC", WindowTitle, MB_OK);

            if (ps.hdc == GetDC(hWnd))
                MessageBox(NULL, L"ps.hdc == GetDC(hWnd)", WindowTitle, MB_OK);
            else
                MessageBox(NULL, L"ps.hdc != GetDC(hWnd)", WindowTitle, MB_OK);

            RECT r;
            SetRect(&r, 10, 10, 50, 50);
            FillRect(m_hDC, &r, (HBRUSH) GetStockObject( BLACK_BRUSH ));

            EndPaint(hWnd, &ps);
            return 0;
        }
    case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    }
    return DefWindowProc(hWnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{   
    WNDCLASSEX wcex;

    wcex.cbClsExtra = 0;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.cbWndExtra = 0;
    wcex.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH );
    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
    wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION );
    wcex.hIconSm = NULL;
    wcex.hInstance = hInstance;
    wcex.lpfnWndProc = WndProc;
    wcex.lpszClassName = ClassName;
    wcex.lpszMenuName = NULL;
    wcex.style = CS_OWNDC;

    if (!RegisterClassEx(&wcex))
        return 0;

    DWORD dwExStyle = 0;
    DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

    m_hWnd = CreateWindowEx(dwExStyle, ClassName, WindowTitle, dwStyle, 0, 0, 300, 300, NULL, NULL, hInstance, NULL);

    if (!m_hWnd)
        return 0;

    m_hDC = GetDC(m_hWnd);

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

    return msg.wParam;
}

CS_OWNDC フラグは、次のような CS_CLASSDC フラグと混同しないでください。

クラス内のすべてのウィンドウで共有される 1 つのデバイス コンテキストを割り当てます。ウィンドウ クラスはプロセス固有であるため、アプリケーションの複数のスレッドが同じクラスのウィンドウを作成する可能性があります。スレッドがデバイス コンテキストを同時に使用しようとすることも可能です。これが発生すると、システムは 1 つのスレッドのみが描画操作を正常に終了できるようにします。

他のすべてが失敗した場合は、CachedBitmapを再構築します。

CachedBitmap オブジェクトを作成するときは、Graphics オブジェクトのアドレスをコンストラクターに渡す必要があります。その Graphics オブジェクトに関連付けられた画面のビット深度が、キャッシュされたビットマップの構築後に変更された場合、DrawCachedBitmap メソッドは失敗するため、キャッシュされたビットマップを再構築する必要があります。または、表示変更通知メッセージをフックして、その時点でキャッシュされたビットマップを再構築することもできます。

CS_OWNDC が完璧なソリューションだと言っているわけではありませんがより良いソリューションへの一歩です。

編集

サンプル プログラムは、CS_OWNDC フラグを使用した画面解像度/ビット深度の変更テスト中に同じ DC を保持しているように見えましたが、そのフラグを削除すると、DC が異なりました (Windows 7 64 ビット Ultimate)(異なる OS でも同じように動作するはずです)。バージョン...テストしても害はありませんが)。

編集2

この例では、WM_PAINT 中にウィンドウを描画する必要があるかどうかを確認するために GetUpdateRect を呼び出しません。それはエラーです。

于 2010-01-15T22:44:35.997 に答える
5

GetDC例外もありますが、一般に、またはを呼び出すたびに異なる DC を取得する場合がありますBeginPaint。したがって、DC に状態を保存しようとしないでください。(パフォーマンスのためにこれを行う必要がある場合は、ウィンドウのクラスまたは特定のウィンドウ インスタンス用に作成できる特別な DC がありますが、それが本当に必要または望んでいるようには思えません。)

ただし、ほとんどの場合、これらの DC には互換性があります。これらは同じグラフィック モードを表すため、別の DC を取得した場合でも、互換性のあるビットマップが機能するはずです。

WM_DISPLAYCHANGEやなど、グラフィック モードが変更されたことを知らせる Windows メッセージがありますWM_PALETTECHANGED。これらをリッスンして、キャッシュされたビットマップを再作成できます。これらはまれなイベントであるため、その時点でキャッシュされたビットマップを再作成することによるパフォーマンスへの影響について心配する必要はありません。

テーマの変更などの通知を受け取ることもできます。これらはグラフィックモードを変更しません--それらはより高いレベルの概念です--したがって、キャッシュされたビットマップは、取得したDCと互換性があるはずです。ただし、テーマが変更されたときにビットマップを変更したい場合は、リッスンすることもできますWM_THEMECHANGED

于 2010-01-15T22:41:20.450 に答える
2

好きなウィンドウ dc に描画できます。どちらも有効です。ウィンドウは、一度にそれを表現できる dc を 1 つだけ持つわけではありません。そのため、GetDC を呼び出すたびに (BeginPaint が内部的に呼び出すと、同じ表示領域を表す新しい一意の DC が取得されます)。作業が終わったら、ReleaseDC (または EndPaint) をリリースするだけです。Windows 3.1 の時代には、デバイス コンテキストは限られた、または非常に高価なシステム リソースであったため、アプリケーションはそれらを保持するのではなく、GetDC キャッシュから取得することが奨励されていました。最近では、ウィンドウの作成時に dc を作成し、ウィンドウの存続期間中はそれをキャッシュすることは完全に受け入れられます。

唯一の「問題」は、を処理するときにWM_PAINT、BeginPaint によって返された dc が無効な四角形にクリップされ、保存されたものはクリップされないことです。


ただし、gdiplus で何を達成しようとしているのかわかりません。通常、あるオブジェクトが長期間 DC に選択されている場合、その DC はメモリ DC であり、ウィンドウ DC ではありません。


GetDC が呼び出されるたびに、独自の状態を持つ個別のデバイス コンテキストを表す新しい HDC を取得します。そのため、1 つの DC に設定されたオブジェクト、背景色、テキスト モードなどは、GetDC または BeginPaint の別の呼び出しによって取得された別の DC の状態には影響しません。

システムは、クライアントによって取得された HDC を無作為に無効にすることはできず、実際にはバックグラウンドで多くの作業を行って、表示モードが切り替わる前に取得された HDC が引き続き機能するようにします。ビット深度を変更しても、技術的には DC の互換性がなくなりますが、アプリケーションが hdc を引き続き使用してブリットすることを妨げることはありません。

とはいえ、WM_DISPLAYCHANGE を LEAST で監視し、キャッシュされた DC とデバイス ビットマップをすべて解放し、それらを再作成することをお勧めします。

于 2010-01-15T20:13:14.200 に答える