【问题标题】:Double buffering malfunctions ( updated on December 17th 2013 )双缓冲故障(2013 年 12 月 17 日更新)
【发布时间】:2014-01-05 15:39:54
【问题描述】:

简介及相关资料:

我有一个复杂的绘画要在我的主窗口的 WM_PAINT 处理程序中实现。

我已经提交了一张图片来说明它:

主窗口有静态控件,而不是按钮,样式为SS_NOTIFY

当用户点击它们时,程序中会发生某些动作,这些动作暂时无关紧要。

下图显示了主窗口中静态控件的位置:

橙色面板上的地图是EMF文件,左上和右上的标志是PNG文件,其他图片是bitmaps。

Visual Styles 通过#pragma 指令启用。我还使用GDI+GDI

项目是作为空项目创建的,我从“零开始”编写了所有代码。

为了实现这个任务,我决定WM_PAINT中绘制整张图片,并在图片上将透明的static controls放在上面对应的图片。

为了保持我的代码简洁明了,我做了实现上述功能的函数,所以我的WM_PAINT处理程序可以尽可能小。

更新 #1(2013 年 12 月 17 日更新):

为了实施从成员 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 Editionpure Win32 API

注意:因为 VS 的 Express 版没有资源编辑器,资源文件和资源头是使用 ResEdit 从这里创建的:http://www.resedit.net/.

问题:

为了避免闪烁,我使用了双缓冲,这是我从 *Paul Watt 的 CodeProject 上的文章中学到的,Charles Petzold 的 Programming Windows 5th editionForger 的 WIN32 教程

从理论上讲,一切正常,我的代码编译没有任何错误。

我已经把代码贴在这里了:http://pastebin.com/zSYT1i8L

我的英语不够好,无法准确描述我面临的问题(我只能说主窗口的边缘和静态控件的重绘“缓慢”并且闪烁)所以我创建了一个演示应用程序来演示它们:http://www.filedropper.com/geotermistgrafika

我为解决问题所做的努力:

我已经处理了WM_ERASEBKGND(返回(LRESULT)1),并且我已经从我的窗口类中排除了样式CS_VREDRAWCS_HREDRAW——因此不应该因此导致闪烁。

我的窗口没有WS_CLIPCHILDREN 样式,因为在静态控件所在的位置可以看到部分桌面图片。

在我的WM_SIZE 处理程序中,我有:

  1. 使用SetWindowPos(...) API 重新定位静态控件并通过添加SWP_NOCOPYBITS 标志减少闪烁。

  2. InvalidateRect( hWnd, NULL, FALSE )使整个窗口无效,所以这个API在无效时不会发送WM_ERASEBKGND(第三个参数是FALSE),但即使我尝试用TRUE效果是一样的.

我已经为WM_PAINT 处理程序实现了双缓冲,就像在上面的书籍/文章/教程中找到的示例一样(通过在内存 DC 中执行所有操作并在屏幕 DC 上执行 BitBlt(...) 以避免闪烁)。

我没有处理 WM_SIZINGWM_WINDOWPOSCHANGINGWM_MOVING 消息。

我已经使用工具 GDIView (http://www.nirsoft.net/utils/gdi_handles.html) 来追踪GDI leaks

每次我调整/最大化我的窗口时,GDIView 在区域的列中显示 +4,这应该意味着我泄漏了区域,但我不知道这怎么可能,因为我这样做了不要使用操作区域的 API,并仔细检查所有内容。

在我看来一切都应该没问题,也许这无关紧要,但我只是想提一下,也许它很重要。

如果我在主窗口中添加WS_EX_COMPOSITED 样式,性能并没有提高。

我试图找到可以帮助我解决问题的在线示例,但所有教程都很简单,不包括这种复杂的图片。

重要提示:

将我的WM_PAINT 处理程序留空并使用GetDC(..) API 获得的设备上下文在WM_ERASEBKGND 中调用onPaint 函数后,闪烁消失了,但在调整大小期间,窗口的重绘是“尖尖的”并且主窗口边缘的问题没有解决。

不过,这比原来的代码有更好的改进。

问题:

如何摆脱上面提供的演示应用程序中显示的问题?

我在此感谢任何投入时间和精力来帮助我的人。

最好的问候。

【问题讨论】:

  • 您肯定已经从这些示例图片中获得了很多好处。
  • 调整 Windows 系统上几乎任何窗口的大小,您会看到边缘闪烁。 Windows 只是没有达到我认为您期望的性能水平。
  • @JonathanPotter 问题还在于静态控件(蓝色控件)的边缘以及地图和字符串所在的橙色面板。那应该不是Windows的问题吧?一定是我做错了什么,还是我弄错了?无论如何,谢谢你的评论。最好的问候。
  • 您可能希望摆脱静态控件并在您的主 WM_PAINT 处理程序中绘制这些框。
  • @JonathanPotter 我确实在WM_PAINT 中绘制了它们,并且static controls 放置在这些框的顶部,因为它们是透明的。通过使用答案from this question,我显着减少了闪烁。我相信代码是“理论上的”声音,但我只是不明白问题可能是什么。这就是为什么我制作了一个演示应用程序来演示这个问题,希望有人能指出我正确的方向。谢谢你。问候。

标签: c++ winapi flicker double-buffering


【解决方案1】:

我在 Windows 7 上编译并运行了代码。使用标准主题(使用 DWM)调整大小时看起来不错。

我切换到 Windows Classic 主题(禁用 DWM),当窗口调整大小时,按钮边缘出现大量撕裂。我怀疑这是您看到的问题。

当绘画与屏幕的物理更新不同步时会发生撕裂。这导致屏幕的一部分显示旧图像,而屏幕的一部分显示新图像。这种效果在水平移动的垂直线上尤为明显。

您还在使用 Windows XP 吗?

据我所知,在 XP 上防止撕裂的唯一方法是使用 DirectX 进行绘画并明确与 VSYNC 同步。尽管您可能能够使用现有的绘画代码并使用 DirectX 绘制最终位图。或者可能有一些其他的同步机制。我不知道。

但是,由于问题在更高版本的 Windows 上自行解决,我不会做任何事情。

【讨论】:

  • 非常感谢!我在互联网上浏览时得出了相同的结论(我认为是在微软的论坛上,但我不是 100% 确定)但不是 100% 确定所以我决定在这里寻求帮助以防万一这是我的错误!我将在笔记本电脑上的 Windows 7 上对此进行测试。我们将保持联系。再次感谢你。最好的问候。
  • 我刚刚在笔记本电脑的 Windows 7 上尝试了我的第一个演示应用程序(包含所有静态控件、图片和字符串的应用程序)。撕裂不明显,但是当我调整大小时,仍然有类似闪烁的效果。当我从左到右以及从上到下调整大小时,尤其会发生这种情况。除了DirectX之外,没有其他方法可以解决这个问题吗?再次感谢您的努力,我只想尽我所能,使这在视觉上尽可能吸引人。问候。
  • 你的计算有点不确定。例如,橙色框应该与右侧有固定距离,但它移动了几个像素。我尝试了带有文本的版本。我认为文本会闪烁一点,因为亚像素抗锯齿会随着文本的移动而变化。您可以通过放大文本自己看到这一点:只有边缘闪烁。此外,如果您的帧速率不足,您会感到有些不稳定。您可以尝试衡量您的更新速度。
  • 我从未尝试过测量更新速度。至于我的数学,我想它可以改进。我没有注意到文本的闪烁,感谢您指出这一点。我认为您的答案是我所寻求的答案,所以我会接受。最后一个好处:你能给我一些关于如何改进我的代码的提示/指针(关于我不确定的数学和类似的东西,因为我真的很想做这个项目)?谢谢你。你有我的+1。最好的问候。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多