0

I'm working on a dialog based MFC application on Visual Studio 2017 in C++.

In my app I have a window which shows text. I'm using an infinite while loop to write numbers to it when I press "Start" button, and I want to stop this process when I press "Stop" button. The window looks like this:

enter image description here

For this purpose, In the Start button handler I use a while loop for the counting and printing the counter in the window of the application. I'm checking every time I enter the while loop, if ICD_BUTTON2 (which is the stop button ID) was pressed like this:

void CEditableListControlDlg::OnBnClickedButton1()
{
     int counter = 0;
     while ((WM_COMMAND != IDC_BUTTON2)) {
            counter++;
            m_editCtrl.SetWindowTextA(std::to_string(counter).c_str());
     }
}

I didn't enter any code in the Stop button handler.

But my application stopes responding and I need to close it using the task manager. What could be the problem? Perhaps it's not the right way to check if a button clicked message was received?

Thank you.

user14092875
  • 145
  • 1
  • 1
  • 12
  • 1
    My rubber duck wants to know how `WM_COMMAND` or `IDC_BUTTON2` can possibly change their value inside the loop – 463035818_is_not_an_ai Oct 06 '20 at 10:13
  • @idclev463035818 Thank you for your comment. I thought that when I press the `Stop` button, the `WM_COMMAND` gets the value of `IDC_BUTTON2`, but I understand now it is not the case. Perhaps you can give me an advice about how can I check if the `Stop` button was pressed every time I want to enter the while loop? – user14092875 Oct 06 '20 at 11:02
  • You need to show more relevant code. – Jabberwocky Oct 06 '20 at 12:03
  • 1
    `WM_COMMAND` and `IDC_BUTTON2` are constants. You're basically writing `while (12 != 34) ...`. – Jabberwocky Oct 06 '20 at 12:04
  • @Jabberwocky Thank you for your comment. I've edited the post a little bit, the code that I presented is the code of the handlers of the two buttons `Stop` and `Start` (well the `Stop` button doesn't have anything in the hander so I didn't include any code), what else should I include? I want to check if the user clicked on the `Stop` button (the ID of this button is `IDC_BUTTON2`) and if he did, I want to exit the while loop. Perhaps you could help me understand how to do it? – user14092875 Oct 06 '20 at 12:26
  • 2
    Maybe this helps: https://stackoverflow.com/questions/7157079/how-to-use-timer-in-mfc-dialog-based-application – Jabberwocky Oct 06 '20 at 12:32
  • 1
    IMHO this nheeds to be done in a worker thread? So in the Start button you start the worker thread, passing the handle to the window so that it can post messages to update with the numbers. Then when you press Stop button you kill the worker thread ... otherwise, using `while` inside a button handler will only choke the system. Or use the timer approach as suggested in the comments. – Andrew Truckle Oct 06 '20 at 13:42
  • 1
    @AndrewTruckle a worker thread sounds like overkill to me. IMHO a timer is more appropriate here. But after all we don't know what the OP is _actually_ trying to achieve. because just incrementing a number sounds pretty pointless to me. – Jabberwocky Oct 06 '20 at 13:44

4 Answers4

4

Every GUI program has a message loop somewhere, that checks for incoming messages from the OS and reacts to them. By creating an endless loop yourself, you block that from happening - that's why you need Task Manager to shut down the program. It also blocks things like window repainting.

MFC provides an OnIdle function that gets called when the message loop has nothing to do. You can use this function to do background work, like incrementing your counter. See Microsoft's documentation Idle Loop Processing for details.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
3

What is fundamental here is that you are blocking the main thread. Even if you corrected the incorrect check on the stop button, you would not get the expected result. The reason is that when an event is triggered, a button press, the main thread needs to handle that event. Your loop is in the way and why the suggestion of using a timer. It is why you have to kill the process to end it.

If you really need to do something really intense, then a thread may be needed, but I don't think that is your objective.

//in .h

UINT_PTR timer;

//in .cpp

//initialize timer to null in constructor or CEditableListControlDlg::Create

//you may want to disable the this button while the timer is active
//but don't start another timer if active!
void CEditableListControlDlg::OnBnClickedButton1()
{
    if(timer)
        return;
    //200 millisecond events.
    timer= SetTimer( ID_TIMER, 200, NULL );
}

void CEditableListControlDlg::OnTimer( UINT nIDEvent ) 
{
    //if you have more than one timer, if(nIDEvent == ID_TIMER)...;
    counter++;
    m_editCtrl.SetWindowTextA(std::to_string(counter).c_str());
    CWnd::OnTimer( nIDEvent );
}

//you may want to disable the this button while the timer is inactive
void CEditableListControlDlg::OnBnClickedButton2()
{
    if(timer)
        KillTimer(timer);
    timer= nullptr;
}
lakeweb
  • 1,859
  • 2
  • 16
  • 21
1

I don't see how timer solves the problem of allowing UI actions while processing something in the background. Timer way would process at most one unit of work on hat timer event.

A separate thread would be a proper solution, but may be too much for this scenario.

Intermediate way is to "pump" messages in your busy while loop, see this for details: https://learn.microsoft.com/en-us/cpp/mfc/idle-loop-processing?view=vs-2019

Vlad Feinstein
  • 10,960
  • 1
  • 12
  • 27
1

To allow the main message loop to process GUI events like the Stop button click, add and call this function PumpMessages() in your while loop:

void CEditableListControlDlg::PumpMessages()
{
    // Must call Create() before using the dialog
    ASSERT(m_hWnd!=NULL);

    MSG msg;
    // Handle dialog messages
    while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
      if(!IsDialogMessage(&msg))
      {
        TranslateMessage(&msg);
        DispatchMessage(&msg);  
      }
    }
}
thomiel
  • 2,467
  • 22
  • 37