【问题标题】:MFC application: animating graphics cause program to not respondMFC 应用程序:动画图形导致程序无响应
【发布时间】:2015-07-16 18:41:59
【问题描述】:

我目前正在尝试创建一个 MFC 程序,它显示一种涉及对象的动画,这些对象的位置以图形方式转换为小板上的位置,如矩形、椭圆等。这是一个 32 位程序。

我的想法是,当用户按下按钮时,他会以特定的时间速率看到模拟。但是,我的代码使它只有在有人不断点击按钮以推进模拟时才会运行。

当我用另一个按钮做同样的事情时,图形通常会流畅地动画。但是,我的窗口屏幕上的(未响应)提示最终会出现在 Windows 7 中(在非特定时间),导致图形冻结,直到模拟完成。

如何防止图形窗口冻结?

相关代码:

void Csmart_parking_guiDlg::OnPaint()
{
    if (IsIconic())
    {
        CPaintDC dc(this); // device context for painting

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

        // Center icon in client rectangle
        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;

        // Draw the icon
        dc.DrawIcon(x, y, m_hIcon);
    }
    else
    {
        CDialogEx::OnPaint();
    }
    DrawGrid();
}


void Csmart_parking_guiDlg::DrawGrid() {
    CRect gridBase;
    gridDrawSurface->GetWindowRect(&gridBase);
    this->ScreenToClient(&gridBase);
    CPoint bottomRight = gridBase.TopLeft();
    int rectSize = 400;
    bottomRight += CPoint(rectSize, rectSize);
    gridBase.BottomRight() = bottomRight;
    gridBase.NormalizeRect();
    gridDraw->Rectangle(gridBase);
    int baseRectWidth = gridBase.Width();
    int baseRectHeight = gridBase.Height();
    double proportion = (baseRectWidth / world->getGridSize());
    // Draw destinations. Set boolean to check if all are drawn yet
        CBrush brushDest(RGB(165, 42, 42));
        gridBrush = gridDraw->SelectObject(&brushDest);
        CPen penBlack;
        penBlack.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
        gridPen = gridDraw->SelectObject(&penBlack);
        vector <Location> destLoc = world->getDestLocations();
        int xCenter;
        int yCenter;
        for (size_t ii = 0; ii < destLoc.size(); ii++) { // draw brown circle
            xCenter = (int)round(gridBase.TopLeft().x + destLoc[ii].x*proportion);
            yCenter = (int)round(gridBase.TopLeft().y + destLoc[ii].y*proportion);
            gridDraw->Rectangle(xCenter - 4, yCenter-4, xCenter+4, yCenter+4);
        }
        gridDraw->SelectObject(gridBrush);
        destDrawn = true;
    // Draw lots.
        CBrush brushLot(RGB(35, 62, 148));
        gridDraw->SetTextColor(RGB(35, 62, 148));
        // gridDraw->SetBkMode(TRANSPARENT);
        gridBrush = gridDraw->SelectObject(&brushLot);
        vector<Location> lotLoc = world->getLotLocations();
        vector<int> lotSpots;
        vector<Lot *> allLots = world->getAllLots();
        for (size_t ii = 0; ii < allLots.size(); ii++) {
            lotSpots.push_back(allLots[ii]->getOpenSpots());
        }
        for (size_t ii = 0; ii < lotLoc.size(); ii++) { // draw blue circle and number
            xCenter = (int)round(gridBase.TopLeft().x + lotLoc[ii].x*proportion);
            yCenter = (int)round(gridBase.TopLeft().y + lotLoc[ii].y*proportion);
            gridDraw->Ellipse(xCenter-3, yCenter-3, xCenter+3, yCenter+3);
            CString echoNum;
            echoNum.Format(_T("%d"), lotSpots[ii]);
            gridDraw->TextOutW(xCenter + 4, yCenter + 1, echoNum);
        }
        lotDrawn = true;
        gridDraw->SelectObject(gridBrush);
        gridDraw->SelectObject(gridPen);
    // Draw drivers
    vector<Location> driverLoc = world->getDriverLocations(); // get all drivers currently visible on screen
    for (size_t ii = 0; ii < driverLoc.size(); ii++) { // draw red dot
        xCenter = (int)round(gridBase.TopLeft().x + driverLoc[ii].x*proportion);
        yCenter = (int)round(gridBase.TopLeft().y + driverLoc[ii].y*proportion);
        gridDraw->Rectangle(xCenter - 1, yCenter - 1, xCenter + 1, yCenter + 1);
    }
}

void Csmart_parking_guiDlg::OnBnClickedBSimend() // On clicking, simulation jumps to the very end.
{
    while (!world->simulationOver[world->getCurrentIteration()]) {
        run_simulation(*world);
        m_TimeDisplay = world->getTime(); // double
        m_EchoTime.Format(_T("Time: %g"), m_TimeDisplay);
        if (!world->simulationOver[world->getCurrentIteration()]) oss << world->getCurrentEvent();
        CString c_status(oss.str().c_str());
        m_EchoStatus = c_status;
        UpdateData(FALSE);
        OnPaint();
        GetDlgItem(IDC_ST_STATUS)->RedrawWindow();
        pEdit->LineScroll(pEdit->GetLineCount());
        // theApp.PumpMessage(); // this works but it makes it way too slow
        // Sleep(50); // program stops responding at times with a sleep message
    }
}

【问题讨论】:

    标签: c++ graphics windows-7 mfc


    【解决方案1】:

    您可以(并且应该)摆脱 while 循环,只需在那里调用 SetTimer 即可开始模拟。然后在每次调用 WM_TIMER 消息处理程序时执行一步模拟。你永远不应该自己调用 OnPaint。您的计时器可以调用 Invalidate 以使 Windows 调用 OnPaint。

    【讨论】:

      【解决方案2】:

      您的应用是一个消息处理应用,就像大多数 Windows 交互式应用一样。

      这意味着它需要处理消息,你不应该花太多时间处理每条消息。

      所以使用消息。

      异步向自己发送消息以推进模拟。在该消息处理程序中,检查您是否应该停止。如果你应该停下来,停下来。如果没有,则模拟时间推进(可能使用当前时间),绘制下一帧,并异步向自己发送消息以绘制下一帧。

      这允许处理其他消息(如鼠标移动)。

      您甚至可以通过设置“停止动画”标志的按钮来支持“取消动画”按钮,并在“高级动画”消息处理程序中检查它。

      烦人的部分是您必须在每个消息处理程序之间保存所有状态,而不仅仅是使用局部变量。如果你可以访问 MSVC2015,我相信他们有协程,可以让你编写一个带有暂停和恢复的函数,所以它读起来就像你的 C++ 函数。

      这进入OnBnClickedBSimend。让它向自己发送一条消息以绘制一个框架。编写一个帧绘制消息处理程序,将其连接起来。使用现有的OnBnClickedBSimend 的主体,将while 替换为if,最后再发送一条消息给您自己绘制另一个框架。

      或者,您可以执行几个循环(使用计时器,即不超过 0.05 秒用于绘制框架)更新,然后向自己抛出另一条消息,让其余应用程序处理消息。

      还有其他方法,但这是一种简单的方法。 Here 是具有多个线程访问一个对象的 Direct2d 应用程序的最佳实践:让您的画布成为 Direct2d 表面,并产生一个线程来更新它。

      【讨论】:

      • 很多很多很多不知情的意见,恐怕。 WM_MOUSEMOVE 消息是低优先级消息。只有当消息队列中没有其他消息时,才会生成这些消息。发布消息(我假设这就是您所说的 “异步发送” 的意思)确保不会生成 WM_MOUSEMOVE 消息。这个答案的其余部分同样具有误导性。恐怕,我得在这里投票。
      猜你喜欢
      • 1970-01-01
      • 2014-12-15
      • 1970-01-01
      • 1970-01-01
      • 2021-03-16
      • 1970-01-01
      • 1970-01-01
      • 2016-04-23
      • 2021-11-18
      相关资源
      最近更新 更多