Win32: у окна есть один и тот же HDC на протяжении всего срока службы? - PullRequest
5 голосов
/ 15 января 2010

Могу ли я использовать 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 ().

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

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

  1. dc = GetDC (hWnd);

  2. BeginPaint (& PAINTSTRUCT);

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

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

Почему

Моя настоящая цель - попытаться сохранить постоянный графический объект GDI + против 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 ]

5 голосов
/ 16 января 2010

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

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

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

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

5 голосов
/ 16 января 2010

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

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

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

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

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

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

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

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флаг, который:

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

Если все остальное не удалось, просто реконструируйте CachedBitmap.

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

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

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

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

Edit2

В этом примере не вызывается GetUpdateRect для проверки необходимости окраски окна во время WM_PAINT.Это ошибка.

2 голосов
/ 15 января 2010

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

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


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


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

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

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

...