Re: Windows API Tab Control flickering
От: Dervish Россия http://www.dervish.ru
Дата: 12.07.04 11:22
Оценка: 26 (3)
Здравствуйте, x84, Вы писали:

x84>Вопрос, собственно, следующий: кто-нть может показать код, как на Windows API сделать таб контрол, который бы не мигал при ресайзе окна. Причем задача такова, чтобы таб контрол был "приклеен" внутри к правой границе родительского окна и сдвигался-раздвигался и уменьшал-увеличивал свой размер при ресайзе родительского окна.


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

Столкнулся с аналогичной задачей, мне тоже не понравилось, что TabControl мерцает и попробовал это дело исправить. Вроде как удалось успешно решить эту задачу, поэтому решил выложить код, может быть ещё кому-нибудь пригодится.

Если рассматривать именно TabControl, то мерцание в нём происходит по традиционным причинам: этот контрол честно отрабатывает сообщения WM_ERASEBKGND и WM_PAINT. Двойная буферизация в реализации этого контрола не предусмотрена.

Кстати, в отличии от других стандартных контролов Windows, TabControl не использует внутреннюю прорисовку, напрямую вызывая код рисования в обработке каких-то событий, а "по честному" делает InvalidateRect. Что, в данном случае, нам играет на руку.

Ситуация с мерцанием TabControl осложняется тем, что для этого класса окна прописаны стили CS_VREDRAW и CS_HREDRAW. Причины, по которым они были добавлены в общем понятны, но именно эти стили вместе с традиционной прорисовкой контрола и приводят к мерцанию.

Мой вариант решения проблемы мерцания основан на подмене оконной процедуры для TabControl-а (subclassing). Новая оконная процедура передаёт все приходящие сообщения "родной" процедуре TabControl-a, но подменяет обработку двух сообщений: WM_ERASEBKGND и WM_PAINT.

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

А вот WM_PAINT обрабатывается посложнее: чтобы самому не рисовать TabControl (если рисовать самому, то запросто может получиться новый контрол, да и пятью строчками кода там не обойдёшься — слишком много вариантов в реализациях этого контрола, слишком много стилей влияет на его вид), для прорисовки вызывается обработчик стандартного сообщения WM_PRINTCLIENT, который рисует контрол в битмапе. А после этого сформированный битмап просто копируется на экран. Таким образом получается, что для фактической прорисовки окна TabControl-a используется всего один BitBlt, в итоге — никакого мерцания.

А теперь код новой оконной процедуры:


WNDPROC pTabControlProc;

LRESULT CALLBACK TabCtrlSubclassProc (HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    if (uMsg == WM_ERASEBKGND)
        return 1;
    if (uMsg == WM_PAINT) {
        // Чтобы поменьше генерилось WM_ERASEBKGND, сэкономим время на
        // диспетчеризации сообщений.
        ValidateRect (hWnd, NULL);

        HDC hControlDC;
        HDC hMemoryDC;
        RECT rc;        // Для определения размеров окна
        HBITMAP hBitmap;    // Битмап для двойной буферизации
        HGDIOBJ hSave;        // Оригинальный битмап из hMemoryDC

        GetClientRect (hWnd, & rc); // TabControl не использует non-client area

        hControlDC = GetDC (hWnd);
        hMemoryDC = CreateCompatibleDC (hControlDC);

        hBitmap = CreateCompatibleBitmap (hControlDC, rc.right, rc.bottom);
        hSave = SelectObject (hMemoryDC, hBitmap);

        // Очистим буфер-битмап, зальём его фоном
        CallWindowProc (pTabControlProc, hWnd, WM_ERASEBKGND, (WPARAM)hMemoryDC, 0);

        // Прорисуем контрол в битмапе
        CallWindowProc (pTabControlProc, hWnd, WM_PRINTCLIENT, (WPARAM)hMemoryDC, PRF_CLIENT);

        // Ну и нарисуем контрол на дисплее
        BitBlt (hControlDC, 0, 0, rc.right, rc.bottom, hMemoryDC, 0, 0, SRCCOPY);

        DeleteObject (SelectObject (hMemoryDC, hSave));
        DeleteDC (hMemoryDC);
        ReleaseDC (hWnd, hControlDC);

        return 0;
    }
    return CallWindowProc (pTabControlProc, hWnd, uMsg, wParam, lParam);
}


А вот так создаётся немерцающий TabControl:

    hControl = CreateWindowEx (dwExStyle, WC_TABCONTROL, pCaption,
        dwControlStyle, 0, 0, 50, 50, hParent,
        (HMENU)iControlId, hInstance, NULL);

    if (::IsWindow (hControl)) {
        pTabControlProc = (WNDPROC) SetWindowLong (
            hControl, GWL_WNDPROC, (LONG) TabCtrlSubclassProc
        );
    }


Надеюсь, что это поможет.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.