Search code examples
c++mfcmouse-cursor

changing mouse cursor to wait cursor then starting worker thread and changing back on thread completion


In an older MFC application I have to perform a network connect request to another computer which may take a number of seconds in the case of incorrect computer name before the request times out. So I am starting up a worker thread to make the initial connection so that the user interface is still responsive.

The network connect request is triggered by the user selecting a menu item which brings up a dialog to fill in the target computer information. When the user clicks the Ok button on the dialog, the network connection request is processed using the worker thread.

What I want to do is to change the mouse cursor to a wait indicator and then remove the wait indicator once the connection is actually made or the attempt times out.

What I am running into is that the mouse cursor is remaining a pointer and the mouse cursor is not changing to a wait indicator.

What I originally thought was that I could just change the mouse cursor using the BeginWaitCursor() function. However that has no effect that I can see.

Further reading indicates that I also need to have an override of the afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) method of the CScrollView class however I can't seem to find anything helpful that describes what I need to do in that method. The OnSetCursor() method seems to be called for a variety of reasons and just moving the mouse causes a breakpoint in that method to be triggered.

It looks like that in the OnSetCursor() method I should detect the current application state and based on that use the SetCursor() function to set one of the possible mouse cursor styles which have been previously loaded with LoadCursor(). See Prevent MFC application to change cursor back to default icon as well as Change cursor for the duration of a thread

However I am unsure at to whether that is how it is actually done and what the parameters that are provided with the OnSetCursor() actually mean and how to use them.

In the second of the two above SO postings it appears a global is being used to decide if the default CView::OnSetCursor() method is being called or not.


Solution

  • First declare the following global variables:

    BOOL bConnecting = FALSE; // TRUE if connecting, set by your application
    HCURSOR hOldCursor = NULL; // Cursor backup
    

    When you need to display the hourglass cursor call:

    bConnecting = TRUE;
    hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
    

    Once the connection is established (or failed) call:

    bConnecting = FALSE;
    SetCursor(hOldCursor);
    // Alternatively you can call SetCursor(LoadCursor(NULL, IDC_ARROW)); - no need to backup the cursor then
    // Or even not restore the cursor at all, it will be reset on the first WM_MOUSEMOVE message (after bConnecting is set to FALSE)
    

    You also need to override OnSetCursor():

    BOOL CMainFrame::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
    {
        if (bConnecting) return TRUE; // Prevent MFC changing the cursor
    
        // else call the default
        return CFrameWndEx::OnSetCursor(pWnd, nHitTest, message);
    }
    

    And add the ON_WM_SETCURSOR() directive to the message map for CMainFrame in order to enable the OnSetCursor() message handler.

    The "main-frame" is the parent of all windows in an MFC Application, and that's why we override OnSetCursor() for it. It affects all other windows.

    In the MFC environment you can also use the BeginWaitCursor(), RestoreWaitCursor(), and EndWaitCursor() functions. These are CCmdTarget methods and can be accessed using AfxGetApp() as well as any CWnd derived class.

    Note that using a global variable in a multi-threaded environment with both UI thread and worker threads, depending on how the global is used and accessed by the threads, you may create a race condition.