Win32: у окна есть один и тот же HDC в течение всего срока службы?

Могу ли я использовать DC вне цикла рисования? DC моего окна гарантированно будет действовать вечно?

я пытаюсь выяснить, как долго действителен контекст устройства (DC) моего элемента управления.

я знаю, что могу позвонить:

GetDC(hWnd);

получить контекст устройства окна моего элемента управления, но разрешено ли это?

Когда Windows отправляет мне сообщение WM_PAINT, я должен вызвать BeginPaint/ EndPaint, чтобы правильно подтвердить, что я его нарисовал, и внутренне очистить недопустимую область:

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

Но вызов BeginPaint также возвращает мне DC внутри структуры PAINTSTRUCT. Это тот DC, на котором я должен рисовать.

я не могу найти ничего в документации, которая говорит, что DC, возвращаемый BeginPaint(), является тем же DC, который я получил бы от GetDC().

Особенно сейчас, во времена Desktop Composition, допустимо ли рисовать на DC, который я получаю за пределами BeginPaint?

Кажется, есть два способа, которыми я могу заставить DC рисовать во время цикла рисования:

  1. dc = GetDC(hWnd);

  2. BeginPaint (& PAINTSTRUCT);

Есть третий способ, но, похоже, это ошибка в Borland Delphi, с которой я разрабатываю.

Во время обработки WM_PAINT Delphi полагает, что wParam является DC, и продолжает рисовать на нем. Принимая во внимание, что MSDN говорит, что wParam сообщения WM_PAINT не используется.

Почему

Моя настоящая цель - попытаться сохранить постоянный объект GDI+ Graphics против HDC, чтобы я мог использовать некоторые более эффективные функции GDI +, которые зависят от наличия постоянного DC.

Во время обработки сообщения 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 один раз, поэтому при инициализации программы:

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 для моего окна, пока приложение работает. Это означает, что оно выживает благодаря:

  • быстрые пользовательские переключатели
  • композиция включена / отключена
  • переключение тем
  • отключение темы

в MSDN я не нахожу ничего, что гарантировало бы, что один и тот же DC будет использоваться для определенного окна до тех пор, пока оно существует.

Примечание: я не использую двойную буферизацию, потому что хочу быть хорошим разработчиком и делать правильные вещи. Иногда это означает, что двойная буферизация - это плохо.

3 ответа

Решение

Есть исключения, но в целом вы можете получать разные DC каждый раз, когда вы звоните GetDC или же BeginPaint, Таким образом, вы не должны пытаться сохранить состояние в DC. (Если вы должны сделать это для производительности, есть специальные контроллеры домена, которые вы можете создать для класса окон или конкретного экземпляра окна, но это не похоже на то, что вам действительно нужно или нужно.)

Однако в большинстве случаев эти контроллеры домена будут совместимы. Они будут представлять один и тот же графический режим, поэтому совместимое растровое изображение должно работать, даже если у вас другой DC.

Есть сообщения Windows, которые сообщают вам, когда меняется графический режим, например WM_DISPLAYCHANGE а также WM_PALETTECHANGED, Вы можете прослушать их и воссоздать кешированное растровое изображение. Поскольку это редкие события, вам не придется беспокоиться о влиянии на производительность воссоздания кэшированного растрового изображения в этот момент.

Вы также можете получать уведомления о таких вещах, как изменения темы. Они не меняют графический режим - они представляют собой концепцию более высокого уровня - поэтому ваше кэшированное растровое изображение должно быть совместимо с любым DC, который вы получаете. Но если вы хотите изменить растровое изображение при изменении темы, вы можете прослушать WM_THEMECHANGED также.

Единственный способ, которым я знаю об этом, может (или не может) делать то, что вы ищете, - это создать окно со стилем класса CS_OWNDC.

То, что это делает, выделяет уникальный контекст устройства для каждого окна в классе.

редактировать

Из связанной статьи MSDN:

Контекст устройства - это особый набор значений, которые приложения используют для рисования в клиентской области своих окон. Система требует контекст устройства для каждого окна на дисплее, но дает некоторую гибкость в том, как система хранит и обрабатывает этот контекст устройства.

Если стиль контекста устройства явно не задан, система предполагает, что каждое окно использует контекст устройства, извлеченный из пула контекстов, поддерживаемых системой. В таких случаях каждое окно должно извлечь и инициализировать контекст устройства перед рисованием и освободить его после рисования.

Чтобы избежать извлечения контекста устройства каждый раз, когда ему нужно рисовать внутри окна, приложение может указать стиль CS_OWNDC для класса окна. Этот стиль класса предписывает системе создавать частный контекст устройства, то есть выделять уникальный контекст устройства для каждого окна в классе. Приложению нужно только получить контекст один раз, а затем использовать его для всей последующей рисования.

Windows 95/98 / Me: хотя стиль CS_OWNDC удобен, используйте его осторожно, поскольку каждый контекст устройства использует значительную часть кучи GDI 64 КБ.

Возможно, этот пример лучше проиллюстрирует использование 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, который:

Выделяет один контекст устройства для совместного использования всеми окнами в классе. Поскольку классы окна зависят от процесса, для нескольких потоков приложения можно создать окно одного и того же класса. Также потоки могут пытаться использовать контекст устройства одновременно. Когда это происходит, система позволяет только одному потоку успешно завершить операцию рисования.

Если ничего не помогает, просто восстановите CachedBitmap.

Когда вы создаете объект CachedBitmap, вы должны передать адрес объекта Graphics в конструктор. Если экран, связанный с этим графическим объектом, имеет битовую глубину, измененную после создания кэшированного растрового изображения, то метод DrawCachedBitmap завершится ошибкой, и вам следует восстановить кэшированное растровое изображение. Кроме того, вы можете перехватить сообщение с уведомлением об изменении отображения и восстановить кэшированное растровое изображение в это время.

Я не говорю, что CS_OWNDC - идеальное решение, но это один шаг к лучшему решению.

редактировать

Кажется, что пример программы сохранил тот же DC во время тестирования разрешения экрана / изменения глубины в битах с флагом CS_OWNDC, однако, когда этот флаг был удален, DC были другими (Window 7 64-bit Ultimate)(должен работать одинаково на разных ОС версии... хотя это не мешало бы проверить).

Edit2

Этот пример не вызывает GetUpdateRect, чтобы проверить, нужно ли рисовать окно во время WM_PAINT. Это ошибка.

Вы можете рисовать в любом окне, которое вам нравится. Они оба действительны. Окно не имеет только одного постоянного тока, который может представлять его за один раз. Таким образом, каждый раз, когда вы вызываете GetDC - и BeginPaint внутренне делает это, вы получаете новый, уникальный постоянный ток, который, тем не менее, представляет одну и ту же область отображения. Просто ReleaseDC (или EndPaint), когда вы закончите с ними. Во времена Windows 3.1 контексты устройств были ограниченным или очень дорогим системным ресурсом, поэтому приложениям предлагалось никогда не держаться за них, а извлекать их из кеша GetDC. в настоящее время вполне приемлемо создавать постоянный ток при создании окна и кэшировать его на весь срок службы окна.

Единственная "проблема" заключается в том, WM_PAINTdc, возвращенный BeginPaint, будет обрезан до недопустимого прямоугольника, а сохраненный - нет.


Однако я не понимаю, чего вы пытаетесь достичь с помощью gdiplus. Обычно, если объект... выбирается в постоянный ток в течение длительного периода времени, этот постоянный ток является памятью постоянного тока, а не окном постоянного тока.


Каждый раз, когда вызывается GetDC, вы получите новый HDC, представляющий отдельный контекст устройства с его собственным состоянием. Таким образом, объекты, цвета фона, режимы текста и т. Д., Установленные на одном DC, НЕ будут влиять на состояние другого DC, полученное с помощью другого вызова GetDC или BeginPaint.

Система не может случайным образом аннулировать HDC, полученные клиентом, и фактически выполняет большую работу в фоновом режиме, чтобы гарантировать, что HDC, полученные до переключения режима отображения, продолжают функционировать. Даже изменение битовой глубины, которое технически делает DC несовместимым, никоим образом не помешает приложению продолжать использовать hdc для блиттинга.

Тем не менее, целесообразно посмотреть на LEAST WM_DISPLAYCHANGE, выпустить любые кэшированные контроллеры домена и растровые изображения устройств и воссоздать их.

Другие вопросы по тегам