一些控件似乎没有随机绘制

时间:2019-05-07 14:34:57

标签: mfc

我正试图为自己编写一个小MFC应用程序,以测试我正在训练的一些AI。

因此,我添加了一个图片控件和一个静态控件,在其中我可以在主窗口的OnPaint()方法中自由绘制内容。

仅绘制一次我的应用程序似乎可以工作,但是现在我添加了一个循环,该循环在停止之前多次执行OnPaint()。

在此循环中,其他一些控件没有显示,例如我的所有按钮都消失了,有些滑块甚至不见了,但有时却在那儿。

我的代码如下:

void CKiUebung1Dlg::OnBnClickedButtongo()
{
    m_bisGoing = true;
    OnPaint();
    if(m_fDiagramData.size() <= 0)
    {
        m_fDiagramData.push_back((float)rand() / RAND_MAX);
        InvalidateRect(NULL, TRUE);
    }
    OnPaint();
    for(int i(9); i >= 0; --i)
    {
        std::this_thread::sleep_for(std::chrono::milliseconds(1000));
        m_fDiagramData.push_back((float)rand() / RAND_MAX);
        InvalidateRect(NULL, TRUE);
        OnPaint();
    }
    m_bisGoing = false;
    OnPaint();
}

void CKiUebung1Dlg::OnPaint()
{
    if(IsIconic())
    {
        CPaintDC dc(this); // Gerätekontext zum Zeichnen

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

        // Symbol in Clientrechteck zentrieren
        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;

        // Symbol zeichnen
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
    {
        constexpr const int border = 5;
        CPaintDC dc(&m_cDiagram);
        CRect l_cPos;
        m_cDiagram.GetClientRect(&l_cPos);
        const int width(l_cPos.Width() - border * 2 - 2), height(l_cPos.Height() - border * 2 - 12);
        const int numPoints(m_fDiagramData.size());
        POINT* points(new POINT[numPoints]);
        for(int i(numPoints - 1); i >= 0; --i)
        {
            const int
                x((float)i / (numPoints - 1) * width + border + 1),
                y(height - m_fDiagramData[i] * height + border + 9);
            points[i] = { x,y };
        }
        dc.Polyline(points, numPoints);

        static CString going(_T(" "));
        if(m_bisGoing) { going += _T("."); if(going.GetLength() > 300) going = _T(" ."); }
        else going = _T(" ");
        float fprog(0); if(m_fDiagramData.size() > 0) fprog = m_fDiagramData.back();
        CString prog; prog.Format(_T("Progress %03.2f%%"), fprog * 100); if(m_bisGoing) prog += going;
        m_cDiagram.SetWindowTextW(prog);

        m_cDiagram.RedrawWindow();

        delete[] points;
    }
}

这是循环未运行时的外观: This is how it looks when the loop isn't running

这是循环运行时的外观: This is how it looks when the loop is running

2 个答案:

答案 0 :(得分:3)

CWnd::OnPaint是对WM_PAINT消息的响应,不应直接调用。

WM_PAINT调用CWnd::OnPaint,后者调用CPaintDC dc(this),后者依次调用BeginPaint / EndPaint API。消息+响应的顺序应保持不变。

因此,CPaintDC dc(this)必须在OnPaint内出现一次,并且只能出现一次,而不能出现在其他任何地方。如下重写OnPaint

void CMyDialog::OnPaint()
{
    CDialogEx::OnPaint(); //this will call CPaintDC dc(this);

    //optional: 
    CClientDC dc(this); //CClientDC can be used anywhere in a valid window
    //use dc for drawing
}

//or
void CMyDialog::OnPaint()
{
    CPaintDC dc(this); 
    //use dc for drawing
}

您也不需要过时的if (IsIconic()) {...}条件。

要强制窗口重新绘制自身,请调用Invalidate()(与InvalidateRect(NULL, TRUE)相同)

InvalidateRect(NULL, TRUE)是重新绘制窗口的请求。系统将查看此请求,并在有机会时向该窗口发送WM_PAINT消息。因此,对InvalidateRect的调用可能无法处理您期望它在顺序程序中工作的方式。例如,第二次连续调用InvalidateRect不会产生任何效果。窗口已被标记为要更新。

 for(int i(9); i >= 0; --i)
 {
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    m_fDiagramData.push_back((float)rand() / RAND_MAX);
    InvalidateRect(NULL, TRUE);
    OnPaint();
 }

OnPaint()应该从上面的代码中删除。仍然不能在单个线程中进行动画处理(至少不能以这种方式)。程序忙于循环,它无法处理WM_PAINT和其他消息。

因此,您需要一个附加线程,或仅使用SetTimer,然后对ON_WM_TIMER() / OnTimer进行动画处理。示例:

int counter = 0;

BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_WM_PAINT()
    ON_WM_TIMER()
    ...
END_MESSAGE_MAP()

void CMyDialog::OnPaint()
{
    CPaintDC dc(this);
    CString s;
    s.Format(L"%02d", counter);
    dc.TextOut(0, 0, s);
}

void CMyDialog::animate()
{
    counter = 0;
    SetTimer(1, 1000, NULL);
}

void CMyDialog::OnTimer(UINT_PTR n)
{
    if(n == 1)
    {
        Invalidate(); //force repaint
        counter++;
        if(counter == 10)
            KillTimer(1);
    }
}

答案 1 :(得分:2)

您似乎很难理解无效/绘画的工作原理。 您首先应该阅读的文档是: Painting and Drawing

尽管许多开发人员建议仅在WM_PAINT处理中进行绘制(在MFC中为OnPaint()),但这并不总是最好的解决方案,因为此消息的优先级较低,因此绘制可能不会立即进行(具有”震荡”的感觉,您可能会获得“闪烁”的效果。

相反,有时我会推荐混合使用绘画和绘画:

  • WM_PAINT处理中使用绘画。这应该绘制整个客户区域(或者,如果您想要更“优化”的实现,则仅绘制其中的无效部分)。请注意,除了以编程方式使其无效之外,由于移动,调整窗口大小,取消隐藏窗口等原因,也可能由于使部分或全部客户区域无效而收到WM_PAINT消息。因此,为了响应WM_PAINT消息,您应该执行一次完整的重新绘制,即要显示的所有项目。
  • 在应用程序忙时(不等待收到“异步” WM_PAINT消息的情况下,使用绘图来立即显示您想要的更改)。请注意,这些也应在WM_PAINT处理中,因此,您必须编写一些绘图/绘画例程,并以HDC(或CDC*)作为参数(以及其他参数)参数),然后从OnPaint()函数(在此处传递ClientDC)和所需的其他绘制动作(传递通过调用CDC*获得的GetDC())中调用它们。

所以,让我分享我(很久以前)编写的应用程序的经验。这是一个图像显示/操作(除其他外)应用程序,以自定义格式处理图像,并使用一个相当“慢”的特殊库,因为它仅提供了在设备上下文中显示图像的功能(这包括可能的裁剪,调整,调整大小等操作,这是CPU成本高昂的操作)。这是一张图片:

enter image description here

您可以看到用户正在执行选择。应用程序必须显示图像,并可能显示在其顶部的选择矩形,这当然是OnPaint()所做的。一种“简单”(尽管从技术上讲是“正确”)实现是响应每个鼠标移动消息(选择时)来调用Invalidate()InvalidateRect()。这将导致完全重画(“确定”),但由于图像库速度慢而导致性能问题:如果在使无效(请求立即刷新)后也调用UpdateWindow(),则性能会变慢(必须重新处理/重新显示图像),如果没有,刷新将在稍后(明显)时间进行。这是通过响应WM_MOUSEMOVE消息而采用drawign(不作画)来解决的:在该处无效,而是仅绘制选择矩形(还原先前选择消息修改的部分后-我仅备份/还原这四个)框架的两侧,而不是整个矩形)。结果,尽管库运行缓慢,该应用程序仍具有响应能力并且操作流畅,即使在跟踪选择内容时(即使切换到另一个应用程序然后又返回到该应用程序,也可以正确显示图像和选择内容(虚线))

关于实现的一些注释和建议(存在很多问题)

  • 正如其他成员所指出的,您不得自己致电OnPaint()。特别是Invalidate()之后的那些调用绝对没有意义。如果要立即更新,请致电UpdateWindow()
  • 我想在OnPaint()内执行计算不是很好,我指的是这些点的计算(尽管在您的情况下,计算是微不足道的)。 OnPaint()应该只显示在代码另一部分中计算出的数据。
  • 此外,设置m_cDiagram文本并从OnPaint()内部重新绘制也不行(可能会导致其他绘制请求)。最好将它们移到OnBnClickedButtongo()中。
  • 您不需要使整个工作区无效(特别是擦除)来使某些控件重新绘制,而只需使那些控件无效。请记住,sleep_for()函数处于阻塞状态,并且在循环运行时不会发送和处理WM_PAINT消息。
  • 顺便说一句,请考虑使用非阻塞方法,例如使用@Barmak Shemirani建议的计时器。或者,可以通过自己运行消息循环来编写“非填充sleep()”(获取CWinApp::Run()中的部分代码并对其进行修改)。
  • 由于您有一个对话框并创建了单独的控件来显示数据,因此使用OnPaint()并不是一个好的实现,因为它会影响(绘制)整个客户区。对于CViewCScrollView之类的类(或通常自定义绘制CWnd的类)来说,它最有用。您将图形绘制在对话框的表面上,并且必须执行计算以获取m_cDiagram中的坐标(顺便说一句,您可以先使用GetWindowRect(),然后再使用ScreenToClient()),但是最好使用所有者绘制的控件(用于绘制/绘制图形),这并不难,您只需要响应绘制请求(就像在OnPaint()中一样),就可以在设备上绘制设备上下文。仅控制,不在对话框上;坐标相对于控件的客户区域,从(0,0)开始。

希望这会有所帮助