【问题标题】:Drawing a rectangle on some area in window causing screen flickering在窗口的某个区域绘制一个矩形导致屏幕闪烁
【发布时间】:2014-04-18 01:42:15
【问题描述】:

在我的绘图功能中,我通过在屏幕上绘制黑色矩形来清除屏幕,然后绘制一组水平和垂直线以显示一些图形。在我的应用程序中,x 轴很长。所以我使用水平滚动来允许用户查看 x 轴上的数据。以下是如上所述绘制背景和线条的程序的一部分。

case WM_PAINT:
HRGN hrgnUpdate = CreateRectRgn(0, 0, 0, 0);
PAINTSTRUCT ps;
if (GetUpdateRgn(m_hWnd, hrgnUpdate, FALSE) != NULLREGION)
{
    if (BeginPaint(m_hWnd, &ps))
    {
        HBRUSH hBr;
        hBr = CreateSolidBrush(RGB(0,0,0));
        RECT rect;
        // Set rect to window size 
        // Draw black background. This will also work like erasing previous data.
        FillRect( m_hDC, &rect, hBr ) ;

        COLORREF testColor = RGB(0,255,0);
        HPEN hPen = CreatePen(PS_SOLID, 0, testColor);
        HPEN hOldPen = (HPEN) SelectObject(m_hDC, hPen);

        // Draw Numbers of Horizontal and Vertical lines using MoveToEx and LineTo.

    }

    EndPaint(m_hWnd, &ps);
}
break;

似乎每次我通过拖动滚动条滚动窗口时,屏幕都会闪烁。在我看来,当绘制背景时,它会在绘制线条之前立即显示在窗口上,然后绘制线条并显示在屏幕上。

有没有办法确保只有在绘制完所有线条后才能在屏幕上更新所有绘图?或者有什么办法可以延迟背景绘制的效果,直到所有线条都画完?

【问题讨论】:

  • 是的,双缓冲(检查 WS_EX_COMPOSITED 窗口样式)。此外,您正在为每幅画创建画笔(从某种意义上说,这在资源和时间方面都是一项相当广泛的任务)。你的代码里没有但是你也释放资源吗?
  • 感谢您指出创建画笔。我会做一些事情的:)
  • WM_ERASEBKGRND中绘制背景
  • @DavidHeffernan :感谢您的评论 :) 但将来,我还计划绘制一个黑色矩形来隐藏图形的某些部分。在这种情况下,我最终需要在 WM_PAINT 中进行。
  • 我无法读懂你的想法。问题中的代码做得不好。 FillRect 需要在 WM_ERASEBKGRND 中。

标签: c windows winapi


【解决方案1】:

是的,使用双缓冲:将WS_EX_COMPOSITED 窗口样式添加到您的窗口(除此之外,请参阅MSDN)。 手动也可以在使用CreateCompatibleBitmap() 创建的位图中绘制所有内容,然后使用BitBlt() 将其内容复制到屏幕 DC。

此外,您正在为每个绘画事件创建一个新画笔,这在某种程度上是一项相当耗费资源和时间的任务。只创建一次,例如在 WM_CREATE 处理程序中,并在每个 WM_PAINT 中重用它(当然这不是强制性的,现在你可能永远看不到任何区别;如果你不这样做,请不要忘记打电话给DeleteObject()释放他们):

case WM_CREATE:
    // Add your existing code for this event
    hBackgroundBrush = CreateSolidBrush(RGB(0, 0, 0));
    hForegroundPen = CreatePen(PS_SOLID, 0, RGB(0, 255, 0));
    break;

您的代码不完整,所以我不能说,但您还应该释放分配的资源(并恢复绘图对象)。

正如大卫在评论中指出的那样,您不应该同时混合前景和背景绘画。现在它的完成方式将首先绘制背景(由窗口本身),然后由您的WM_PAINT 消息(使用FillRect())擦除。您有两种改进方法:在适当的位置处理背景绘制(然后,如评论中所建议,在 WM_ERASEBKGRND 事件处理程序中)或避免 Windows 绘制控制背景并在您的 WM_PAINT 事件处理程序中执行所有操作。让我们看看两者:

case WM_ERASEBKGND:
{
    GetClientRect(m_hWnd, &rect);
    FillRect((HDC)wParam), &rect, hBackgroundBrush);

    return (LRESULT)1;
}
case WM_PAINT:
{
    HRGN hrgnUpdate = CreateRectRgn(0, 0, 0, 0);
    PAINTSTRUCT ps;

    if (GetUpdateRgn(m_hWnd, hrgnUpdate, FALSE) != NULLREGION) {
        if (BeginPaint(m_hWnd, &ps)) {
            HPEN hOldPen = (HPEN)SelectObject(m_hDC, hForegroundPen);
            // Do painting here
            SelectObject(m_hDC, hOldPen);

            EndPaint(m_hWnd, &ps);
        }
    }
    break;
}

第二种情况更简单,对WM_ERASEBKGND 什么都不做(但通知Windows 你处理了那个)并保持WM_PAINT 原样:

case WM_ERASEBKGND:
    return (LRESULT)1;
case WM_PAINT:
{
    HRGN hrgnUpdate = CreateRectRgn(0, 0, 0, 0);
    PAINTSTRUCT ps;

    if (GetUpdateRgn(m_hWnd, hrgnUpdate, FALSE) != NULLREGION) {
        if (BeginPaint(m_hWnd, &ps)) {
            FillRect(m_hDC, &rect, hBackgroundBrush);

            HPEN hOldPen = (HPEN)SelectObject(m_hDC, hForegroundPen);
            // Do painting here
            SelectObject(m_hDC, hOldPen);

            EndPaint(m_hWnd, &ps);
        }
    }
    break;
}

【讨论】:

  • 在你的WM_ERASEBKGND处理程序中,你不应该使用WPARAM提供的设备上下文而不是调用BeginPaintEndPaint吗?
  • @AlwaysLearningNewStuff 非常正确,我只是从一个句柄复制并粘贴到另一个句柄!
  • 我一度害怕自己对处理WM_ERASEBKGND 的理解是错误的。感谢您如此迅速地回复并澄清我的疑问。赞成。最好的问候。
  • GetUpdateRgn 调用是不必要的优化尝试。如果它曾经是 NULLREGION(我对此表示怀疑),那么您将永远不会调用 BeginPaint 和 EndPaint,因此您最终会陷入无限的绘制循环。从 BeginPaint 开始,然后检查 PAINTSTRUCT 的 rcPaint 矩形是否与绘图中的任何有趣内容重叠。
  • @AdrianMcCarthy 我半同意。它可能以不同/更好的方式完成(我试图尽可能地保留代码作为原始代码)但是当涉及滚动时(尤其是与剪辑结合使用时)区域可以是 NULLREGION。
猜你喜欢
  • 2012-08-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-29
  • 2021-02-09
相关资源
最近更新 更多