在 MFC CPaintDC 中进行双缓冲后闪烁不会减少

Posted

技术标签:

【中文标题】在 MFC CPaintDC 中进行双缓冲后闪烁不会减少【英文标题】:Flicker does not reduce after double buffering in MFC CPaintDC 【发布时间】:2013-07-05 07:15:03 【问题描述】:

我正在尝试使用 MFC 制作交互式图形,其中可以使用鼠标单击更改图形中样本点的 y 轴。我使用本教程实现了双缓冲enter link description here。我还应该指出,我需要不时为我的程序更改视口的来源。但是,当我单击要更新的样本点的图形时,我仍然可以看到它在闪烁。这不是一个不便,但我需要扩展这个图以包含许多样本点和其他特征,如网格线、轴、标签、边界区域等,我担心闪烁可能会成为我未来的问题,因为这个项目的规模越来越大。实施双缓冲似乎对输出没有任何改变。此外,现在我已经实现了双缓冲,程序似乎在执行过程中停止(当我在 Visual Studio 中以调试模式运行它时)并出现以下错误:

Unhandled exception at 0xffffff3a in graph_on_dlgbox.exe: 0xC0000005: Access violation reading location 0xffffff3a.

我仍然不确定是什么原因导致它出现,但如果我开始在图形区域周围快速随机单击似乎会发生这种情况。由于我在不使用双缓冲的代码中(尚未)看到此错误,因此我假设它与双缓冲代码有关,但我不确定。 无论如何,我想一次解决这个问题,第一个问题是闪烁。这是我没有双缓冲的代码:

void Cgraph_on_dlgboxDlg::OnPaint()

    CPaintDC dc(this);
    if (IsIconic())
    
        // CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    
    else
    
        CDialogEx::OnPaint();
    

    CPen pen;
    COLORREF pencolour = RGB(0, 0, 0);
    COLORREF brushcolour = RGB(0, 0, 255);
    COLORREF graphColour = RGB(0, 0, 150);

    // Draw boarder
    pen.CreatePen(PS_SOLID, 3, pencolour);
    // CBrush brush(HS_CROSS, brushcolour);
    dc.SetBkMode(TRANSPARENT);
    dc.SetMapMode(MM_TEXT);
    dc.SetViewportOrg(theGraph.x1, theGraph.y1);
    // Dc.SetViewportExt(theGraph.width, theGraph.height);
     dc.SelectObject(&pen);
    // dc.SelectObject(&brush);

    // Draw graph boundary
    CPoint point1(0,0);
    point1.x = 0;
    point1.y = 0;
    CPoint point2(0,0);
    point2.x = point1.x + theGraph.width;
    point2.y = point1.y + theGraph.height;
    dc.Rectangle(CRect(point1, point2));
    pen.DeleteObject();

    // Draw Horizontal at 0
    pen.CreatePen(PS_SOLID, 1, pencolour);
    dc.SelectObject(&pen);
    dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
    dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
    pen.DeleteObject();

    // Shift overall graph origin from top left corner to beginning of this horizontal line
    dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin

    // Draw graph line
    pen.CreatePen(PS_SOLID, 2, graphColour);
    dc.SelectObject(&pen);
    for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++)
        dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
        dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
    

    // draw circles at graph sample points
    for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++)
        CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);        
    

这是带有双缓冲的修改版本:

void Cgraph_on_dlgboxDlg::OnPaint()


    // /*****
    CPaintDC dc_blt(this);
    CDC dc;
    CBitmap bmpDC;
    // CRect rcClient;
    // GetClientRect(rcClient);

    if (IsIconic())
    
        // CPaintDC dc(this); // device context for painting

        SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

        // Center icon in client rectangle
        int cxIcon = GetSystemMetrics(SM_CXICON);
        int cyIcon = GetSystemMetrics(SM_CYICON);
        CRect rect;
        GetClientRect(&rect);
        int x = (rect.Width() - cxIcon + 1) / 2;
        int y = (rect.Height() - cyIcon + 1) / 2;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    
    else
    
        CDialogEx::OnPaint();
    

    dc.CreateCompatibleDC(&dc_blt);
    // bmpDC.CreateCompatibleBitmap(&dc_blt, rcClient.Width(),rcClient.Height());
    bmpDC.CreateCompatibleBitmap(&dc_blt, theGraph.width,theGraph.height );
    dc.SelectObject(&bmpDC);

    // ----------- After this point, do all operations considering (0,0) to be the origin of the bitmap
    // consider bitmap coordinates a device coordinates for Viewport operations

    CPen pen;
    COLORREF pencolour = RGB(0, 0, 0);
    COLORREF brushcolour = RGB(0, 0, 255);
    COLORREF graphColour = RGB(0, 0, 150);

    // Draw boarder
    pen.CreatePen(PS_SOLID, 3, pencolour);
    // CBrush brush(HS_CROSS, brushcolour);
    dc.SetBkMode(TRANSPARENT);
    dc.SetMapMode(MM_TEXT);
    // dc.SetViewportOrg(theGraph.x1, theGraph.y1);
    // dc.SetViewportExt(theGraph.width, theGraph.height);
     dc.SelectObject(&pen);
    // dc.SelectObject(&brush);

    // Draw graph boundary
    CPoint point1(0,0);
    point1.x = 0;
    point1.y = 0;
    CPoint point2(0,0);
    point2.x = point1.x + theGraph.width;
    point2.y = point1.y + theGraph.height;
    dc.Rectangle(CRect(point1, point2));
    pen.DeleteObject();

    // Draw Horizontal at 0
    pen.CreatePen(PS_SOLID, 1, pencolour);
    dc.SelectObject(&pen);
    dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
    dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
    pen.DeleteObject();

    // Shift overall graph origin from top left corner to beginning of this horizontal line
    // dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the client area origin
    // New origin defined in terms of the Bitmap's origin
    dc.SetViewportOrg(0, theGraph.height - ORG_DIST_FROM_BOTTOM);

    // Draw graph line
    pen.CreatePen(PS_SOLID, 2, graphColour);
    dc.SelectObject(&pen);
    for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++)
        dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
        dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
    

    // draw circles at graph sample points
    for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++)
        CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);        
    

    dc.SetViewportOrg(0, 0);
    // dc_blt.BitBlt(rcClient.left+100,rcClient.top+50,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
    // dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, theGraph.x1, theGraph.y1, SRCCOPY);

    dc_blt.BitBlt(theGraph.x1,theGraph.y1, theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
    // --- Bring the bitmap to this particular location on screen specified by (theGraph.x1,theGraph.y1, theGraph.width, theGraph.height)

    // dc_blt.BitBlt(0,0,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
    // dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
    // *****/

     m_bMyDraw = FALSE;

这是输出的示例屏幕截图:

点击可以改变图形上采样点的y轴值,每次点击后程序都会通过调用InvalidateRect()来重绘图形,图形的面积就是要重绘的矩形。 样本点的坐标存储在 CPoint 对象数组中,每次在相应区域单击图形时都会更新其成员。由于对InvalidateRect() 的调用,该图随后会重新绘制,但会闪烁;当然,除非程序在调试模式下崩溃并出现以下错误:

如何消除闪烁?

---- 更新----

BOOL Cgraph_on_dlgboxDlg::OnEraseBkgnd(CDC* pDC)

    // TODO: Add your message handler code here and/or call default
    if ( m_bMyDraw )
        return TRUE;
    else
        return CDialogEx::OnEraseBkgnd(pDC);

这个函数是这样制作的,因为它在我之前提到的教程中是这样完成的 ------ 更新 2 ---------- 如果我只是把 return TRUE;在上述函数的主体中,闪烁似乎消失了,但现在输出看起来像这样 对话框背景似乎采用了我的 Visual Studio 窗口的内容。如何防止这种情况发生?

【问题讨论】:

【参考方案1】:

你已经接近了!双缓冲的想法是将窗口中的每个像素只绘制一次。如果它被绘制零次,像 Visual Studio 这样的工件仍然存在。如果第一次画,然后用不同的颜色再次画,你会看到闪烁。因此,为了确保每个像素都被绘制,创建兼容的 dc 窗口的完整宽度和高度,以便当它被复制到 CPaintDC 时,它会覆盖整个区域而不仅仅是 theGraph。继续在OnEraseBkgnd 中返回TRUE,这样像素就不会先在OnEraseBkgnd 中绘制,然后再在OnPaint 中绘制。

【讨论】:

【参考方案2】:

两件事:

    您是否确保 OnEraseBkgnd() 只返回 TRUE 并且不调用基类来擦除视图?

    您不需要为 OnPaint() 中的双缓冲进行所有绘制。在 OnPaint() 中你需要做的就是 BitBlt。您可以在 UpdateRect() 函数中绘制内存位图,该函数在您需要更新屏幕时调用,然后调用 InvalidateRect() 来更新屏幕。我已经发布了一些关于我多次使用的无闪烁双缓冲方法的代码here,这可能会有所帮助。

【讨论】:

我的问题中包含了我的 OnEraseBkgnd()。你的意思是它的函数体应该只有 return TRUE;? 很抱歉,我不理解在 UpdateRect() 函数中绘制到内存位图的部分。什么是 UpdateRect()? OnPaint() 不是在我需要更新屏幕时自动调用的函数吗?我在一个不可调整大小的对话框上绘制它,所以我只需要担心其他重叠窗口和父对话框被最小化的情况。 另外,如果我错了,请告诉我,但是将所有用于绘图的代码放入 OnPaint() 不应该是闪烁的原因吗?因为它们仍然只绘制到 memDC 而不是屏幕 另外,只是参考,我在对话框的'OnLButtonDown()'中调用了InvalidateRect() 刚刚看到您的第二次更新。您看到 VS 内容的原因是您没有在 OnPaint() 中绘制整个窗口。您需要创建一个与整个客户区大小相同的背景位图,并用颜色(或其他)填充它,即使您只对图形区域感兴趣。【参考方案3】:

防止闪烁的工作方式是首先从 OnEraseBkgnd 返回 TRUE 以抑制默认擦除。但是您的 OnPaint 代码必须包含对窗口的完全擦除。您不这样做是为了获得源代码的背景图像或之前的任何内容。因此,向您的 OnPaint 添加一个 FillSolidRect 调用以清除窗口。

在调用 CDialogEx::OnPaint 之前创建 CPaintDC 会破坏对话框正确绘制自身的能力,因为该函数也会创建 CPaintDC。但是每条绘制消息只允许调用一次 CPaintDC。为了避免这个问题,您需要一种完全不同的方法。对话框上应该有一个图片控件(CStatic),并且您应该在从 CStatic 派生的类中绘制图形。

【讨论】:

或者不要使用CPaintDC,而是使用普通的CDC @NikBougalis :你的意思是我需要在上面的代码中有两个 CDC 吗?上面的 CPaintDC 仅用于将位图 BitBlt 到对话框窗口上,而我在 CDC 中绘图(至少我认为我一直在这样做) Scott:我已经尝试将绘图绘制到从 CStatic 控件派生的类中但我遇到了其他问题,所以我现在使用这种方法。您认为从 CStatic 派生并在其上绘画是消除闪烁的唯一方法,还是有什么方法可以使当前的方法也起作用?

以上是关于在 MFC CPaintDC 中进行双缓冲后闪烁不会减少的主要内容,如果未能解决你的问题,请参考以下文章

MFC双缓冲绘图实例

GDI双缓冲绘图

基于c#的双缓冲技术画图

java 双缓冲,消除闪烁 的问题

Java中用双缓冲技术消除闪烁

MFC PrintWindow(CPaintDC) 有效,但 PrintWindow(CDC) 无效