Неисправности двойной буферизации (обновлено 17 декабря 2013 г.)

ВВЕДЕНИЕ И СООТВЕТСТВУЮЩАЯ ИНФОРМАЦИЯ:

У меня есть сложная картина для реализации в обработчике WM_PAINT моего главного окна.

Я представил картинку ниже, чтобы проиллюстрировать это:

введите описание изображения здесь

Главное окно имеет статические элементы управления вместо кнопок, которые имеют стиль SS_NOTIFY,

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

На следующем рисунке показано, где находятся статические элементы управления в главном окне:

введите описание изображения здесь

Карта на оранжевой панели представляет собой EMF файл, верхний левый и правый логотипы PNG файлы и другие картинки bitmap s.

Visual Styles включены через #pragma директивы. Я также использую GDI+, вместе с GDI,

Проект был создан как пустой проект, и я все кодировал с нуля.

Чтобы выполнить эту задачу, я решил нарисовать всю картину в WM_PAINT и поставить прозрачный static control s над изображениями на картинке, которые соответствуют им.

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

ОБНОВЛЕНИЕ № 1(обновлено 17 декабря 2013 г.):

Для реализации рекомендаций, полученных от члена arx, я публикую один исходный код, который можно скомпилировать и который может воспроизвести проблему:

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <gdiplus.h>

#pragma comment( linker, "/manifestdependency:\"type='win32' \
                 name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \
                 processorArchitecture='*' publicKeyToken='6595b64144ccf1df' \
                 language='*'\"" )

#pragma comment( lib, "comctl32.lib")
#pragma comment( lib, "Msimg32.lib" ) 
#pragma comment( lib, "Gdiplus.lib" )

using namespace Gdiplus;

// variable for storing the instance

static HINSTANCE hInst; 

// variables for painting the window

static HBRUSH hbPozadina, // gray background brush for grid on the top
   BlueFrame, // needed to frame blue static controls
       hbr; // orange brush for orange panel

/********* helper functions for WM_PAINT **********/

// Fills triangle with gradient brush

void GradientTriangle( HDC MemDC, 
                       LONG x1, LONG y1, 
                       LONG x2, LONG y2, 
                       LONG x3, LONG y3,
                       COLORREF top, COLORREF bottom )
{
    TRIVERTEX vertex[3];

    vertex[0].x     = x1;
    vertex[0].y     = y1;
    vertex[0].Red   = GetRValue(bottom) << 8;
    vertex[0].Green = GetGValue(bottom) << 8;
    vertex[0].Blue  = GetBValue(bottom) << 8;
    vertex[0].Alpha = 0x0000;

    vertex[1].x     = x2;
    vertex[1].y     = y2;
    vertex[1].Red   = GetRValue(top) << 8;
    vertex[1].Green = GetGValue(top) << 8;
    vertex[1].Blue  = GetBValue(top) << 8;
    vertex[1].Alpha = 0x0000;

    vertex[2].x     = x3;
    vertex[2].y     = y3; 
    vertex[2].Red   = GetRValue(bottom) << 8;
    vertex[2].Green = GetGValue(bottom) << 8;
    vertex[2].Blue  = GetBValue(bottom) << 8;
    vertex[2].Alpha = 0x0000;

    // Create a GRADIENT_TRIANGLE structure that
    // references the TRIVERTEX vertices.

    GRADIENT_TRIANGLE gTriangle;

    gTriangle.Vertex1 = 0;
    gTriangle.Vertex2 = 1;
    gTriangle.Vertex3 = 2;

    // Draw a shaded triangle.

    GradientFill( MemDC, vertex, 3, &gTriangle, 1, GRADIENT_FILL_TRIANGLE);
 }

 // draws the background for the part of the window between header and footer

 void drawBackground( HDC MemDC, RECT r )
 {
      /******* main window's gradient background ********/

      GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, 
                               r.left, r.bottom - r.top - 30,
                               r.left, r.top + 120,
                               RGB( 0x95, 0xB3, 0xD7 ), 
                               RGB( 0xDB, 0xE5, 0xF1 ) );

      GradientTriangle( MemDC, r.right, r.bottom - r.top - 30, 
                               r.right, r.top + 120,
                               r.left, r.top + 120, 
                               RGB( 0x95, 0xB3, 0xD7 ), 
                               RGB( 0xDB, 0xE5, 0xF1 ) );
}

// draws the header of the main window

void drawHeader( HDC MemDC, RECT rect, HBRUSH hbPozadina )
{
    FillRect( MemDC, &rect, hbPozadina );
}

// fills rectangle with gradient brush

void GradientRectangle( HDC MemDC, 
                        LONG x1, LONG y1,
                        LONG x2, LONG y2,
                        COLORREF top,
                        COLORREF bottom )
{
    // vertexes for static's gradient color

    TRIVERTEX vertexS[2];

    vertexS[0].x     = x1;
    vertexS[0].y     = y1;
    vertexS[0].Red   = GetRValue(top) << 8;
    vertexS[0].Green = GetGValue(top) << 8;
    vertexS[0].Blue  = GetBValue(top) << 8;
    vertexS[0].Alpha = 0x0000;

    vertexS[1].x     = x2;
    vertexS[1].y     = y2; 
    vertexS[1].Red   = GetRValue(bottom) << 8;
    vertexS[1].Green = GetGValue(bottom) << 8;
    vertexS[1].Blue  = GetBValue(bottom) << 8;
    vertexS[1].Alpha = 0x0000;

    // Create a GRADIENT_RECT structure that 
    // references the TRIVERTEX vertices.

    GRADIENT_RECT gRect;

    gRect.UpperLeft  = 0;
    gRect.LowerRight = 1;

    // Draw a shaded rectangle. 

    GradientFill( MemDC, vertexS, 2, &gRect, 1, GRADIENT_FILL_RECT_V );
}

// fills the "button" with blue gradient and frames it with blue brush

void FillButton( HDC MemDC, RECT rect, HBRUSH BlueFrame )
{
    // fill upper half of the rectangle

    GradientRectangle( MemDC, 
                       rect.left, rect.top, 
                       rect.right, rect.top + ( rect.bottom - rect.top ) / 2,
                       RGB( 0x95, 0xB3, 0xD7 ), 
                       RGB( 0x4F, 0x8B, 0xBD ) );

    // fill bottom half of the rectangle

    GradientRectangle( MemDC, 
                       rect.left, rect.top + ( rect.bottom - rect.top ) / 2,
                       rect.right, rect.bottom, 
                       RGB( 0x4F, 0x8B, 0xBD ),
                       RGB( 0x95, 0xB3, 0xD7 ) );

    FrameRect( MemDC, &rect, BlueFrame );
}

// draws the "status bar" at the bottom of the main window

void drawFooter( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
    // down triangle

    GradientTriangle( MemDC, 
                      r.right, r.bottom, 
                      ( r.right - r.left ) / 2,
                      r.bottom - r.top - 15,
                      r.left, r.bottom, 
                      top, bottom );

    // upper triangle

    GradientTriangle( MemDC, 
                      r.right, r.bottom - r.top - 30, 
                      ( r.right - r.left ) / 2, r.bottom - r.top - 15,
                      r.left, r.bottom - r.top - 30, 
                      top, bottom );

    // left triangle

    GradientTriangle( MemDC, 
                      r.left, r.bottom, 
                      ( r.right - r.left ) / 2, r.bottom - r.top - 15,
                      r.left, r.bottom - r.top - 30, 
                      top, bottom );

   // right triangle

    GradientTriangle( MemDC, 
                      r.right, r.bottom - r.top - 30,
                      ( r.right - r.left ) / 2, r.bottom - r.top - 15, 
                      r.right, r.bottom, 
                      top, bottom );
}

// draw orange panel on which map and 3 static controls will be drawn

void drawOrangePanel( HDC MemDC, RECT r, COLORREF top, COLORREF bottom )
{
    // down triangle

    GradientTriangle( MemDC, 
                      r.right, r.bottom, 
                      r.left + ( r.right - r.left ) / 2,
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.bottom, 
                      top, bottom );

    // upper triangle

    GradientTriangle( MemDC, 
                      r.right, r.top, 
                      r.left + ( r.right - r.left ) / 2, 
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.top, 
                      top, bottom );

    // left triangle

    GradientTriangle( MemDC, 
                      r.left, r.bottom, 
                      r.left + ( r.right - r.left ) / 2, 
                      r.top + ( r.bottom - r.top ) / 2,
                      r.left, r.top, 
                      top, bottom );

   // right triangle

   GradientTriangle( MemDC, 
                     r.right, r.top,
                     r.left + ( r.right - r.left ) / 2, 
                     r.top + ( r.bottom - r.top ) / 2, 
                     r.right, r.bottom, 
                     top, bottom );
}

void onPaint( HWND hwnd, WPARAM wParam, LPARAM lParam ) 
{
    PAINTSTRUCT ps;

    HDC hdc = BeginPaint( hwnd, &ps);

    RECT r; // rectangle for main window's client area

    GetClientRect( hwnd, &r);

    HDC MemDC = CreateCompatibleDC(hdc); // back buffer

    // compatible bitmap for MemDC

    HBITMAP bmp = CreateCompatibleBitmap( hdc, r.right - r.left, r.bottom - r.top ),
            oldBmp = (HBITMAP)SelectObject( MemDC, bmp ); // needed for cleanup

    // draw background for middle part of the window

    drawBackground( MemDC, r );

    // draw header with grid lines

    RECT rect; // position it properly at the top

    rect.left = r.left;
    rect.top = r.top;
    rect.right = r.right;
    rect.bottom = 120;

    drawHeader( MemDC, rect, hbPozadina );

    // draw "status bar"

    drawFooter( MemDC, r, RGB( 0x48, 0xAC, 0xC6), RGB( 0x31, 0x83, 0x99 ) );

    /******* draw static control's background ****/

    //======== top left static control ======//

    //position it properly

    rect.left = ( 3 * ( r.right - r.left ) / 4 - 340 ) / 3;
    rect.top = 120 + ( r.bottom - r.top - 450 ) / 3;
    rect.right = 150 + ( 3 * ( r.right - r.left ) / 4 - 340 ) / 3;
    rect.bottom = 270 + ( r.bottom - r.top - 450 ) / 3;

    // draw gradient button

    FillButton( MemDC, rect, BlueFrame );

    //======================== draw orange panel =================//

    //position it properly

    rect.left = 3 * ( r.right - r.left ) / 4 - 40;
    rect.top = r.top + 140;
    rect.right = rect.left + ( r.right - r.left ) / 4;
    rect.bottom = rect.top + ( r.bottom - r.top - 190 );

    drawOrangePanel( MemDC, rect, RGB( 0xFF, 0xC8, 0xAA ), RGB( 0xFF, 0x96, 0x48 ) );

    /****** draw back buffer on the screen DC *******/

    BitBlt( hdc, 0, 0, r.right - r.left, r.bottom - r.top, MemDC, 0, 0, SRCCOPY );

    /************** cleanup *******************/

    SelectObject( MemDC, oldBmp );

    DeleteObject(bmp); // compatible bitmap for MemDC

    DeleteDC(MemDC);

    EndPaint( hwnd, &ps);
}

// WinMain's procedure

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
    case WM_CREATE:
        {
             //******** brushes ***********//

             // load gray background brush for the top banner

             hbPozadina = CreateSolidBrush( RGB( 230, 230, 230 ) );

             // brush for orange panel that holds 3 static controls and a map

             hbr = CreateSolidBrush( RGB( 255, 163, 94 ) ); 

             // blue frame for blue static controls

             BlueFrame = CreateSolidBrush( RGB(79, 129, 189) ); 

             /*******************************************/
        }
        return (LRESULT)0;

    case WM_ERASEBKGND:
        return (LRESULT)1; // so we avoid flicker ( all painting is in WM_PAINT )

    case WM_PAINT:
        {
             // paint the picture
             onPaint( hwnd, wParam, lParam );
        }
        return (LRESULT)0;

    case WM_SIZE:
        InvalidateRect( hwnd, NULL, FALSE ); 
        return (LRESULT)0;

    case WM_CLOSE:

        // destroy brushes

        DeleteObject(hbPozadina);
        DeleteObject(hbr);
        DeleteObject(BlueFrame);

        DestroyWindow(hwnd);

        return (LRESULT)0;

    case WM_DESTROY:
        PostQuitMessage(0);
        return (LRESULT)0;

    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

// WinMain

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, 
               int nCmdShow)
{
    // store hInstance in global variable for later use

    hInst = hInstance;

    WNDCLASSEX wc;
    HWND hwnd;
    MSG Msg;

    /**** variables for GDI+ initialization ******/

    GdiplusStartupInput gdiplusStartupInput;
    ULONG_PTR           gdiplusToken;

    /********* Initialize GDI+. ********/

    GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    /**** finished GDI+ initialisation *****/

    // initialize common controls

    INITCOMMONCONTROLSEX iccex;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    iccex.dwICC = ICC_STANDARD_CLASSES ;
    InitCommonControlsEx(&iccex);

    // register main window class

    wc.cbSize = sizeof(WNDCLASSEX);
    wc.style = 0;
    wc.lpfnWndProc = WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInst;
    wc.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wc.hCursor = LoadCursor( NULL, IDC_ARROW );
    wc.hbrBackground = NULL;//(HBRUSH)GetStockObject( WHITE_BRUSH );
    wc.lpszMenuName = NULL;
    wc.lpszClassName = L"Main_Window";
    wc.hIconSm = LoadIcon (NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
         MessageBox( NULL, L"Window Registration Failed!", L"Error!", 
                     MB_ICONEXCLAMATION | MB_OK );

         return 0;
    }

    // create main window

    hwnd = CreateWindowEx( 0, // WS_EX_COMPOSITED "improved" drawing of the edges
                           L"Main_Window", 
                           L"Геотермист", 
                           WS_OVERLAPPEDWINDOW,
                           ( GetSystemMetrics(SM_CXMAXIMIZED) - 1020 ) / 2, 
                           ( GetSystemMetrics(SM_CYMAXIMIZED) - 600 ) / 2, 
                           1020, 600, NULL, NULL, hInstance, 0 );

    if(hwnd == NULL)
    {
          MessageBox( NULL, L"Window creation failed!", L"Error!", 
                      MB_ICONEXCLAMATION | MB_OK );

          return 0; 
    }

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);

    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }

    // shutdownd GDI+

    GdiplusShutdown(gdiplusToken);

    return Msg.wParam;
}

я работаю на Windows XP, с помощью MS Visual Studio C++ 2008 Express Edition а также pure Win32 API,

Одно замечание: поскольку в экспресс-версии VS нет редактора ресурсов, файл ресурса и заголовок ресурса были созданы с помощью ResEdit отсюда: http://www.resedit.net/.

ПРОБЛЕМА:

Чтобы избежать мерцания, я использовал двойную буферизацию, о которой я узнал из статей Пола Ватта о CodeProject, Чарльза Петцольда по программированию для Windows 5-й редакции и учебника Forger по WIN32.

Теоретически все в порядке, и мой код компилируется без ошибок.

Я вставил код здесь: http://pastebin.com/zSYT1i8L

Мой английский не достаточно хорош, чтобы точно описать проблемы, с которыми я сталкиваюсь (все, что я могу сказать, это то, что края основного окна и перерисовка статического элемента управления "медленно" и они мерцают), поэтому я создал демонстрационное приложение, которое демонстрирует их: http://www.filedropper.com/geotermistgrafika

МОИ УСИЛИЯ ДЛЯ РЕШЕНИЯ ПРОБЛЕМЫ:

Я справился WM_ERASEBKGND (вернулся (LRESULT)1) и я исключил стили CS_VREDRAW а также CS_HREDRAW из моего окна класса - поэтому мерцание не должно быть вызвано из-за этого.

Мое окно не имеет WS_CLIPCHILDREN стиль, потому что часть картинки на рабочем столе видна там, где находятся статические элементы управления.

В моем WM_SIZE У меня есть обработчик:

  1. Переставлен статический контроль с использованием SetWindowPos(...) API и уменьшенное мерцание, добавляя SWP_NOCOPYBITS флаг.

  2. Недействительно все окно с InvalidateRect( hWnd, NULL, FALSE ), так что этот API не отправляет WM_ERASEBKGND при аннулировании ( 3-й параметр FALSE), но даже если я попробую с TRUE Эффект тот же.

Я реализовал двойную буферизацию для WM_PAINT обработчик, как в примерах, приведенных в вышеупомянутых книгах / статьях / учебниках (делая все в памяти DC и делай BitBlt(...) на экране DC во избежание мерцания).

Я не справился WM_SIZING ни WM_WINDOWPOSCHANGING, или же WM_MOVING сообщение.

Я использовал инструмент GDIView ( http://www.nirsoft.net/utils/gdi_handles.html) для отслеживания GDI leaks,

Каждый раз, когда я изменяю размер своего окна / максимизирую его, GDIView показывает +4 в столбце для регионов, что должно означать, что я пропускаю регионы, но я не могу понять, как это возможно, так как я не использую API, которые манипулируют областями, и перепроверил все.

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

Если я добавлю WS_EX_COMPOSITED стиль главного окна, производительность не улучшается.

Я пытался найти в Интернете пример, который помог бы мне решить мою проблему, но все учебные пособия просты и не охватывают этот тип сложных картинок.

ВАЖНАЯ ЗАМЕТКА:

Оставив мой WM_PAINT обработчик пустой и вызывающий onPaint функция в WM_ERASEBKGND с контекстом устройства, полученным с GetDC(..) API, мерцание исчезло, но во время изменения размера, перерисовка окна была "остроконечной", и проблема с краями основного окна не была решена.

Тем не менее, это гораздо лучшее улучшение, чем оригинальный код.

ВОПРОС:

Как избавиться от проблем, продемонстрированных в моем демонстрационном приложении, представленном выше?

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

С наилучшими пожеланиями.

1 ответ

Решение

Я скомпилировал и запустил код на Windows 7. Используя стандартную тему (которая использует DWM), он выглядел хорошо при изменении размера.

Я переключился на тему Windows Classic (которая отключает DWM), и при изменении размера окна было много разрывов по краям кнопок. Я подозреваю, что это проблема, которую вы видите.

Разрыв происходит, когда рисование не синхронизировано с физическим обновлением экрана. Это приводит к тому, что часть экрана показывает старое изображение, а часть экрана показывает новое изображение. Эффект особенно заметен на вертикальных линиях, движущихся горизонтально.

Вы все еще используете Windows XP?

Насколько я знаю, единственный способ предотвратить разрыв на XP - это использовать DirectX для рисования и явно синхронизироваться с VSYNC. Хотя вы можете использовать существующий код рисования и просто нарисовать окончательный рисунок с помощью DirectX. Или может быть какой-то другой механизм для синхронизации. Я не знаю.

Однако, поскольку проблема устраняется в более поздних версиях Windows, я ничего не буду делать.

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