MFC dialog box OnTimer not called

nicolet
nicolet used Ask the Experts™
on
VS 2008 MFC, a popup dialog, after a button press, I call a DLL function for a lengthy task with known time 3 minutes, I SetTimer() before I start the length task, and want to show the CProgressCtl during this 3 minues. So in the OnTimer(), I will step and move the ProgressCtl, however the OnTimer was never called.
Comment
Watch Question

Do more with

Expert Office
EXPERT OFFICE® is a registered trademark of EXPERTS EXCHANGE®

Author

Commented:
Point increases
Hi nicolet,

this comes from the fact that the lengthy operation blocks the message loop since both run in the same thread. So messages aren't handled as long as the main thread has work to do.

There are three possibilities:

1. Create a new thread for the lengthy operation.
2. Either you can 'split' the lengthy operation into lot small packets of work. Then you could call these even from a OnTimer function or in OnIdle.
3. Within the lengthy operation implement your own message loop which processes outstanting messages repeatedly.

ZOPPO

Commented:
Remember to add ON_WM_TIMER macro in your message map:
BEGIN_MESSAGE_MAP(CYourDlg, CDialog)
	ON_WM_TIMER()
END_MESSAGE_MAP()

Open in new window

Starting with Angular 5

Learn the essential features and functions of the popular JavaScript framework for building mobile, desktop and web applications.

To add to Zoppo's comment #24901504


Assuming you current code is like

void MyDialog::OnTimer(UINT eventId)
{
     if (eventId == 99)
     {
          // lengthy code begins here
          ...
          // update of member variables after lengthy code
          ...
     }
}

implementation for (1)

Put lengthy code into a function:

// arguments should be any member of MyDlg which might change while the lengthy code was running asynchronously
void MyDlg::lengthyCode(arguments)
{
     // put here lengthy code but don't update members
     ...
     // fill a structure (created with new) with all updates to members
     struct MyUpdates * ptrUpd = new MyUpdates;
     ptrUpd->myResult = ...;
     ...
     PostMessage(WM_MY_UPDATE_AFTER_LENGTHY_CODE, 0, (LPARAM)ptrUpd);
}

The WM_MY_UPDATE_AFTER_LENGTHY_CODE was a const e. g. defined in dialog header by

  enum { WM_MY_UPDATE_AFTER_LENGTHY_CODE = WM_USER + 1234 };


Then have a handler for the WM_MY_UPDATE_AFTER_LENGTHY_CODE

BEGIN_MESSAGE_MAP (MyDlg, CDialog)

     ...
     ON_MESSAGE( WM_MY_UPDATE_AFTER_LENGTHY_CODE, handleUpdateAfterLengthyCode)
END_MESSAGE_MAP

void  handleUpdateAfterLengthyCode(WPARAM wParam, LPARAM lParam)
{
     struct MyUpdate * ptrUpd = (MyParam*)lParam;
     // make the updates to member variables of MyDlg
     myResult = ptrUpd->myResult;
     ...
     delete ptrUpd;   // free the pointer
     myThreadHandle = NULL;  // reset handle
     UpdateData(FALSE);  // update screen
}

add a static member function to MyDlg like

class MyDlg : public CDialog
{
     ....
public:
     static void threadFunc(void* ptr)
     {
          MyDlg* ptrThis = (MyDlg*)ptr;
          ptrThis->lengthyCode(myInput1, myInput2);

     }
};


Finally change OnTimer to

void MyDialog::OnTimer(UINT eventId)
{
     if (eventId == 99)
     {
         if (myThreadHandle != 0) return;
         myThreadHandle = _beginthread(threadFunc, 0, this);  // invoke thread
     }
}



implementation for (3)

void MyDialog::OnTimer(UINT eventId)
{
     if (eventId == 99)
     {
          // lengthy code begins here
          ...
               // somewhere within lengthy code e. g. within a loop where one single cycle of the loop isn't so lengthy:
               Msg msg = { 0 };
               while (PeekMessage(&msg, 0, 0, 0, PM_REMOVE)
               {
         if (msg.message != WM_KICKIDLE && !PreTranslateMessage(&msg))
         {
                      ::TranslateMessage(&msg);
                      ::DispatchMessage(&msg);
         }
               }

          ...
     }
}

Note, though alternative (3) looks much easier, it is much more dangerous. If one of the messages handled early would quit the dialog or interfere with that made in the lengthy code it probably would crash. Though all happens in the main thread the handlers nevertheless were called asynchronously what makes it dangerous to rely on any member input or update any member as that could corrupt data integrity. The thread solution is much more reliable than running extra message loops.

Author

Commented:
The DolengthFunc() is a function from DLL, In this Dialog, when the "DO it" button is pressed, it will call the DoLEngthFunc() which is a API from DLL, I know the length task will take 3 minutes, and it should not be canceled during the DoLengthFunc(), and it won;t end unless there is an error; nor will it sendany message to report % done.

I wouold like to display a progressCtl to show that it is working on the length task, and showing how much is done.So I have a ProgressCtl setup before I call DoLengthFunc(), and Also started the timer by SetTImer(), and I will Step the progressCtl every 10 seconds.  and I call DoLengthFunc(). However in the OnTimer() function I have the Step function for the ProgressCtl, but the OnTImer was neverr been called.

Another way to inform user for the length work is showing a "Working ..." pop up dialog, before call DOLEngthFunc(), and destroy the "Working..." pop up after DoLengthFunc(). Should I use AfxMessageBox()? my  question is how do I use AfxMessageBOx without any Button, and destroy the AfxMessageBox when DOLEngthFunc is done.

Thank you.
>>>> However in the OnTimer() function I have the Step function for the ProgressCtl, but the OnTImer was neverr been called.

As already told by Zoppo, if the DoLengthFunc was called from message loop - and all On... functions are handler functions called from message loop - no further messages could be processed, but the screen freezes for all 3 minutes.

The best is to create a thread and call the doLengthFunc there. Then the OnTimer can show the progress as it was called periodically from main message loop while the doLengthFunc runs asynchrounously in the thread. Make a little new dialog (no system menu) which contains only a progress bar. Before creating the thread, create a new instance of that dialog (in OnTimer) and store the pointer of that dialog in a member of your dialog class. Use ShowWindow(SW_SHOW); to visualize the progressbar.  Then, whenever OnTimer was called after invoking the thread you could update the progress bar. And when the thread ends you could end the progress bar by calling myPtrProgressDlg->EndDialog(IDOK); in the handler of the WM_MY_UPDATE_AFTER_LENGTHY_CODE as described above.
 

Do more with

Expert Office
Submit tech questions to Ask the Experts™ at any time to receive solutions, advice, and new ideas from leading industry professionals.

Start 7-Day Free Trial