MFC应用程序:动画图形导致程序无法响应

时间:2015-07-16 18:41:59

标签: c++ graphics windows-7 mfc

我目前正在尝试创建一个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
    }
}

2 个答案:

答案 0 :(得分:2)

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

答案 1 :(得分:1)

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

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

所以使用消息。

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

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

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

令人讨厌的部分是你必须在每个消息处理程序之间保存所有状态,而不是仅使用局部变量。如果你有权访问MSVC2015,我相信它们有协同程序可以让你编写一个带有挂起和简历的函数,所以它就像你的C ++函数一样。

这是OnBnClickedBSimend。让它自己发送一条消息来绘制一个框架。写一个frame-draw-message-handler,把它挂起来。使用OnBnClickedBSimend的现有正文,将while替换为if,并在其末尾向自己发送另一条消息以绘制另一帧。

或者,您可以执行一些循环(使用计时器,即绘制帧的时间不超过0.05秒)更新,然后向自己发送另一条消息,让其余的应用程序处理消息。

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