Click here to Skip to main content
15,881,803 members
Please Sign up or sign in to vote.
3.67/5 (3 votes)
See more:
I have a function in my app which has to wait for a mouse click or an escape keypress before it returns. The app has to 'stay alive' until I get a result, ie the ribbon controls need to be able to update, and I need to do some of my own GDI drawing with each mouse movement etc.

I have achieved this by using a message loop. Simplified code:

bool cancel = false;
bool clicked = false;

BOOL waitForClick (CPoint &clickedPosition)
{
    while (theApp.PumpMessage())
    {
        theApp.OnIdle(0);
        if (bCancelled) // this is set elsewhere in the app code, when WM_KEYDOWN is received with an escape keypress
        {
            cancel = true;
            break;
        }

        if (bClicked) // this is set elsewhere in the app code, when ON_WM_LBUTTONDOWN is received
        {
            clicked = true;
            break;
        }
    }

    if (cancel)
    {
        return FALSE;
    }
    if (clicked)
    {
        clickedPosition = gPosition;
        return TRUE;
    }
    return FALSE;
}


This seems to work OK but having researched this I have read a lot about how I should be using threads instead of message loops.

However, even if I spawn a thread to wait for a click, I still have to wait for that thread to finish and tell my function that it can return, right?

I also found that I can't stop this loop from elsewhere in my code. For example, suppose the user decides to run a different command, by hitting another ribbon button. I need to cancel the command currently running (which is the loop above). I tried making the loop check a global bool is true every time through the loop, and set that bool to false if the other command starts, but of course the loop isn't passed through until the code for that other command has finished.

Another problem is that in an MDI environment, if I start the command, switch documents, start the command in that document, and then switch back to the first document, I now have the loop running in the second document, hogging all the messages.

So in summary, I don't think a message loop is the right answer. But I don't get how threading could do it either, in this case, mainly because the function can not return until it has a click or an escape.


I would guess that there is an elegant solution to this, probably using threading, which so far has escaped me (no pun intended). Is anyone able to give me a nudge in the right direction please?
Posted

Threads and message pumps? This sounds like over engineering to me. Surely you need a finite state machine within the object handling the line drawing.

Messages can be handled as normal. Draw out a structure of states with links between them showing the event types that can move you from one to the other. As long as you can handle the relevant messages that will occur for each state transition you dont need to worry about message pump or threading in this.

I think your trying to over engineer here and adding a threading complexity issue to this will just make the whole thing much less managle. Of course I only have a cursory knowledge of exactly what your trying to achieve here.
 
Share this answer
 
"However, even if I spawn a thread to wait for a click, I still have to wait for that thread to finish and tell my function that it can return, right?"


The function waiting to return should also be in that separate thread which is waiting for the click/escape. Then when the click/escape has been detected and the thread is about to terminate, you'll have to inform the main thread.
You could do this for example by sending a WM_COMMAND message with a unique ID in wParam to the message loop which is interpreting commands from your ribbon.

Now if you receive another command from your ribbon before you got the message that the thread is finished, you can set your bCancelled variable.
 
Share this answer
 
Comments
ShilpiP 6-Dec-10 0:55am    
You are Genius :) +5.
morrisecl 6-Dec-10 3:46am    
Thanks very much for your thoughts.

So, considering this workflow:

1. User hits 'draw a line' ribbon button, so we are now in CMainFrm::OnCommandDrawLine()
2. OnCommandDrawLine() calls waitForClick(), which as you suggest starts up a new thread.
3. Unless it was escaped, call waitForClick() again to get the other end of a line
4. Draw a line between the 2 click positions
5. Return from OnCommandDrawLine()

If waitForClick() is in a separate thread as you suggest, how do we get from step 2 to 3? It has to wait for the thread to finish, whilst keeping the app alive. Surely WaitforSingleObject will lock it up?
Dalek Dave 6-Dec-10 4:22am    
Good Answer.
Rajesh Anuhya 8-Dec-10 3:39am    
Very Good Answer :) +5
OnCommandDrawLine() will create a thread and return immediately. So the main thread is still active and able to set the bClicked and bCancelled messages, as well as process all your command messages from the ribbon.

In the thread created by OnCommandDrawLine, you can now simply add a loop to wait for the first click and then store the click coordinates. Now with another such loop, wait for the second click and draw the line when you receive it.
Since the main thread isn't waiting on anything, it's perfectly fine for the second thread to be waiting for a change in bClicked and bCancelled in a while loop.

// Wait for the first click and allow for a cancel command.
while(!bClicked)
        if(bCancelled)
            return FALSE;

// At this point, the first click has been received.
// Store the mouse coordinates somewhere, for example in
//  clickedPosition1.

// Make sure not to process the first click twice.
bClicked = 0;

// Wait for the second click.
while(!bClicked)
        if(bCancelled)
            return FALSE;

// At this point, two clicks have been received
//  and no cancel command has been given.
// Now you can draw a line using the stored coordinates.
 
Share this answer
 
v5
Comments
morrisecl 6-Dec-10 16:08pm    
OK I get it, so the whole command is in fact in a separate thread. That makes sense now; I knew I had a fundamental flaw in my thinking with this.
Thanks very much for taking the time to help me.
I am all in favour of using multithreading when you want to do more than one thing simultaneously but as far as I understand it you just want to do one thing. That is: wait for a keypress, updating the display while waiting. This is a very common thing to do. I have done it many times in Windows using some code like this:

C
for(;;)
{
  MSG msg;
  // Do any redrawing, but nothing else
  while (::PeekMessage(&msg, NULL, WM_PAINT, WM_PAINT, PM_REMOVE))
  {
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
  }
  // Check if any key has been pressed
  if (::PeekMessage(&msg, NULL, WM_KEYDOWN, WM_KEYDOWN, PM_REMOVE))
  {
    int cc = msg.wParam;
    // Windows does not like to miss key down events (needed to match key up events)
    ::TranslateMessage(&msg);
    ::DispatchMessage(&msg);
    // Remove any characters resulting from the keypress
    while (::PeekMessage(&msg, NULL, WM_CHAR, WM_CHAR, PM_REMOVE))
        ;
    if (cc == VK_ESCAPE || cc == VK_SPACE)
        break;
  }
}
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900