WM_PAINT中的绘图(太慢)会导致闪烁?

时间:2012-06-18 03:34:32

标签: winapi gdi+ gdi

我想在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,则客户端矩形只会闪烁。我认为原因是Gdiplus :: DrawLines太慢了。

有没有解决闪烁问题的方法? 感谢。

4 个答案:

答案 0 :(得分:4)

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

  • 尝试不依赖WM_ERASEBKGND消息,即返回非零,或者如果可能,请在NULL中指定WNDCLASS::hbrBackground。通常,绘画方法会绘制脏区域的所有背景,然后不需要进行擦除。

  • 如果你需要擦除,它通常可以进行优化,以便WM_ERASEBKGND返回非零值,然后paint方法通过绘制未被常规绘制内容覆盖的区域来确保“擦除” ,如果设置了PAINTSTRUCT::fErase

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

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

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

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

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

  • 如果您看到控件滚动时出现闪烁,请不要使整个客户端无效,但请使用ScrollWindow()并仅使展示新(滚动内容)内容的小区域无效。

  • 如果上述所有内容都失败,请使用双缓冲。

答案 1 :(得分:3)

使用双缓冲区。这是Win32 C ++应用程序的一个棘手问题,特别是OnPaint函数和DC工具。

以下是一些链接,可帮助您检查双缓冲区的实现是否一切正常:Flicker Free Drawing In MFC和SO问题“Reduce flicker with GDI+ and C++

答案 2 :(得分: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;
}

答案 3 :(得分:-1)

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