Winapi 在WM_PAINT中绘制(太慢)会导致闪烁吗?

Winapi 在WM_PAINT中绘制(太慢)会导致闪烁吗?,winapi,gdi+,gdi,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;

我想用下面的代码在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太慢了

有没有办法解决闪烁问题?
谢谢。

使用双缓冲区。这是一个关于Win32 C++应用程序的问题,特别是OnPaint函数和DC设备。
这里有几个链接可以帮助您检查双缓冲区的实现是否一切正常:所以问题是使用双缓冲区。这是一个关于Win32 C++应用程序的问题,特别是OnPaint函数和DC设备。
这里有几个链接可以帮助您检查双缓冲区的实现是否一切正常:所以问题是,我没有自己处理WM_ERASEBKGND消息。

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

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

尽量不要依赖WM_橡皮擦BKGND消息,即返回非零,或者尽可能在WNDCLASS::hbrBackground中指定NULL。通常,绘制方法绘制脏区域的所有背景,然后不需要进行擦除

如果需要擦除,通常可以对其进行优化,使WM_ERASEBKGND返回非零,如果设置了PAINTSTRUCT::fErase,则paint方法还可以通过绘制常规绘制内容未覆盖的区域来确保擦除

如果合理可行,请编写paint方法,使其不会在一次调用中重新绘制相同的像素。例如,若要制作带有红色边框的蓝色矩形,请不要使用FillRectred,然后用FillRectblue重新喷涂其内部。尽可能多地画一次每个像素

对于复杂的控件/窗口,通常可以优化绘制方法,以便通过正确组织控件数据轻松跳过脏rect PAINTSTRUCT::rcPaint之外的许多绘制

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

如果不是顶级窗口,考虑使用CSJPARTDC。如果paint方法不依赖于系统设置将clipping rectangle剪切到控件的客户端rect,则此类样式将带来更好的性能

如果您看到控件/窗口大小调整的闪烁,请考虑不要使用CSH-HeReDead和CSVVReDRAW。相反,手动使WM_大小中控件的相关部分无效。这通常只允许使控件的较小部分无效

如果您看到控件滚动时闪烁,请不要使整个客户端无效,而是使用ScrollWindow并仅使显示新滚动内容的小区域无效

如果以上所有操作都失败,则使用双缓冲


闪烁可能是由缓慢的绘画以及其他因素引起的。通常,请尝试/确保以下几点:

尽量不要依赖WM_橡皮擦BKGND消息,即返回非零,或者尽可能在WNDCLASS::hbrBackground中指定NULL。通常,绘制方法绘制脏区域的所有背景,然后不需要进行擦除

如果需要擦除,通常可以对其进行优化,使WM_ERASEBKGND返回非零,如果设置了PAINTSTRUCT::fErase,则paint方法还可以通过绘制常规绘制内容未覆盖的区域来确保擦除

如果合理可行,请编写paint方法,使其不会在一次调用中重新绘制相同的像素。例如,若要制作带有红色边框的蓝色矩形,请不要使用FillRectred,然后用FillRectblue重新喷涂其内部。尽可能多地画一次每个像素

对于复杂的控件/窗口,通常可以优化绘制方法,以便通过正确组织控件数据轻松跳过脏rect PAINTSTRUCT::rcPaint之外的许多绘制

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

如果不是顶级窗口,考虑使用CSJPARTDC。如果paint方法不依赖于系统设置将clipping rectangle剪切到控件的客户端rect,则此类样式将带来更好的性能

如果您看到控件/窗口大小调整的闪烁,请考虑不要使用CSH-HeReDead和CSVVReDRAW。相反,手动使WM_大小中控件的相关部分无效。这通常只允许使控件的较小部分无效

如果您看到控件滚动时闪烁,请不要使整个客户端无效,而是使用ScrollWindow并仅使显示新滚动内容的小区域无效

如果以上所有操作都失败,则使用双缓冲


如果您的线条恰好超出了DC图形的边界,那么Win32/GDI+在剪裁时会非常慢。比如,比你自己的滚动慢两个数量级 剪辑功能。这里有一些实现Lang/BalsKi的C代码,我从原来的C++库中搜索出来,这是20年前的C++。应该很容易移植回来

如果您的线条可以延伸到客户端矩形之外,请调用CliplinInstitute。。。在将您的观点交给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;
}

如果您的线条恰好超出了DC图形的边界,那么Win32/GDI+在剪裁时会非常慢。例如,与滚动您自己的剪裁函数相比,最多要慢两个数量级。这里有一些实现Lang/BalsKi的C代码,我从原来的C++库中搜索出来,这是20年前的C++。应该很容易移植回来

如果您的线条可以延伸到客户端矩形之外,请调用CliplinInstitute。。。在将您的观点交给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;
}

除非hdcMem直接绑定到hdc,否则它不应该与由于最终位BLT而绘制线条的速度相关。也许还有别的事情发生?e、 g由于最终BitBlt,BitBltIt中的撕裂不应与绘制线条的速度有关,除非hdcMem直接与hdc相连。也许还有别的事情发生?e、 g在BitBlt中撕裂,但在结尾使用BitBlt。。。这不是有效的双重缓冲吗?也就是说,这个答案需要更多的扩展和/或支持。是的,我明白了。现在我无法给出更详细的答案。但几年前,我写了一个应用C++,MFC,GDI+,它的工作非常精细,线图形大约有10000个点,甚至更多,如果用户选择一个更大的时间段,但这是使用BitBlt在最后…这不是有效的双重缓冲吗?也就是说,这个答案需要更多的扩展和/或支持。是的,我明白了。现在我无法给出更详细的答案。但几年前,我编写了一个C++、MFC、GDI+应用程序,它的工作原理非常精细,如果用户选择了更大的时间段,则可以使用大约10000点甚至更多的线图形。