1

我想用下面的代码在 WM_PAINT 消息处理程序中画很多线。

//DrawLine with double buffering
LRESULT CALLBACK CMyDoc::OnPaint(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
    std::vector<Gdiplus::Point> points;
    std::vector<Gdiplus::Point>::iterator iter1, iter2;
    HDC hdc, hdcMem;
    HBITMAP hbmScreen, hbmOldBitmap;
    PAINTSTRUCT ps;
    RECT    rect;

    hdc = BeginPaint(hWnd, &ps);

    //Create memory dc
    hdcMem = CreateCompatibleDC(hdc);
    GetClientRect(hWnd, &rect);
    hbmScreen = CreateCompatibleBitmap(hdc, rect.right, rect.bottom);
    hbmOldBitmap = (HBITMAP)SelectObject(hdcMem, hbmScreen);

    //Fill the rect with white
    FillRect(hdcMem, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));

    //Draw the lines
    Gdiplus::Graphics graphics(hdcMem);
    Gdiplus::Pen blackPen(Gdiplus::Color(255, 0, 0));

    points = m_pPolyLine->GetPoints();
    for (iter1 = points.begin(); iter1 != points.end(); iter1++) {
        for (iter2 = iter1 + 1; iter2 != points.end(); iter2++)
            graphics.DrawLine(&blackPen, *iter1, *iter2);
    }

    //Copy the bitmap from memory dc to the real dc
    BitBlt(hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, SRCCOPY);

    //Clean up
    SelectObject(hdcMem, hbmOldBitmap);
    DeleteObject(hbmScreen);
    DeleteDC(hdcMem);

    EndPaint(hWnd, &ps);
    return 0;
}

但是,如果点的大小超过 20,则客户端 rect 会闪烁。我认为原因是 Gdiplus::DrawLines 太慢了。

有什么方法可以解决闪烁问题吗?谢谢。

4

4 回答 4

6

闪烁可能是由于绘制缓慢以及其他原因造成的。通常,请尝试/确保以下内容:

  • 尽量不要依赖WM_ERASEBKGND消息,即返回非零值,或者尽可能指定NULLin 。WNDCLASS::hbrBackground通常paint方法绘制脏区域的所有背景,则无需进行擦除。

  • 如果您需要擦除,通常可以对其进行优化,使其WM_ERASEBKGND返回非零,然后paint方法通过绘制常规绘制内容未覆盖的区域(如果PAINTSTRUCT::fErase已设置)来确保“擦除”。

  • 如果可能的话,编写paint方法,这样它就不会在一次调用中重新绘制相同的像素。例如,用红色边框制作蓝色矩形,不要FillRect(red),然后用 重新绘制它的内部部分FillRect(blue)。尝试尽可能合理地绘制每个像素一次。

  • 对于复杂的控件/窗口,通常可以优化绘制方法,以便PAINTSTRUCT::rcPaint通过适当组织控件数据轻松跳过脏矩形 ( ) 之外的大量绘制。

  • 更改控件状态时,仅使控件的最小所需区域无效。

  • 如果它不是顶级窗口,请考虑使用CS_PARENTDC. 如果您的绘制方法不依赖于系统将剪切矩形设置为控件的客户端矩形,则此类样式将带来更好的性能。

  • 如果您看到控件/窗口大小调整时闪烁,请考虑不使用CS_HREDRAWand CS_VREDRAWWM_SIZE而是手动使控件的相关部分无效。这通常允许仅使控件的较小部分无效。

  • 如果您看到控件滚动时闪烁,请不要使整个客户端无效,而ScrollWindow()应仅使用和无效暴露新(滚动)内容的小区域。

  • 如果上述一切都失败了,那么使用双缓冲。

于 2012-06-18T08:08:59.903 回答
3

使用双缓冲。对于 Win32 C++ 应用程序,尤其是 OnPaint 函数和 DC 工具,这是一个令人讨厌的问题。

这里有一些链接可以帮助您检查双缓冲区的实现是否一切正常:MFC 中的无闪烁绘图和 SO 问题“使用 GDI+ 和 C++ 减少闪烁

于 2012-06-18T03:46:51.833 回答
0

如果您的线条碰巧超出了 DC(图形)的范围,Win32/GDI+ 的剪切速度会非常缓慢。比如,比滚动你自己的裁剪函数慢两个数量级。这是一些实现 Liang/Barsky 的 C# 代码——我从一个 20 年前最初使用 C++ 的旧库中搜罗到了它。应该很容易移植回来。

如果您的线条可以延伸到客户矩形之外,请在您的点上调用 ClipLine(rect, ...),然后再将它们交给 Graphics::DrawLine。

private static bool clipTest(double dp, double dq, ref double du1, ref double du2)
{
    double dr;
    if (dp < 0.0)
    {
        dr = dq / dp;

        if (dr > du2)
        {
            return false;
        }
        else if (dr > du1)
        {
            du1 = dr;
        }
    }
    else
    {
        if (dp > 0.0)
        {
            dr = dq / dp;
            if (dr < du1)
            {
                return false;
            }
            else if (dr < du2)
            {
                du2 = dr;
            }
        }
        else
        {
            if (dq < 0.0)
            {
                return false;
            }
        }
    }

    return true;
}

public static bool ClipLine(Rectangle clipRect, ref int x1, ref int y1, ref int x2, ref int y2)
{
    double dx1 = (double)x1;
    double dx2 = (double)x2;
    double dy1 = (double)y1;
    double dy2 = (double)y2;

    double du1 = 0;
    double du2 = 1;
    double deltaX = dx2 - dx1;
    double deltaY;

    if (clipTest(-deltaX, dx1 - clipRect.Left, ref du1, ref du2))
    {
        if (clipTest(deltaX, clipRect.Right - dx1, ref du1, ref du2))
        {
            deltaY = dy2 - dy1;
            if (clipTest(-deltaY, dy1 - clipRect.Top, ref du1, ref du2))
            {
                if (clipTest(deltaY, clipRect.Bottom - dy1, ref du1, ref du2))
                {
                    if (du2 < 1.0)
                    {
                        x2 = DoubleRoundToInt(dx1 + du2 * deltaX);
                        y2 = DoubleRoundToInt(dy1 + du2 * deltaY);
                    }
                    if (du1 > 0.0)
                    {
                        x1 = DoubleRoundToInt(dx1 + du1 * deltaX);
                        y1 = DoubleRoundToInt(dy1 + du1 * deltaY);
                    }

                    return x1 != x2 || y1 != y2;
                }
            }
        }
    }
    return false;
}
于 2016-02-27T02:37:33.903 回答
-1

问题是我自己没有处理 WM_ERASEBKGND 消息。

于 2012-06-18T06:04:27.213 回答