Custom Mouse Pointers in MFC: A Visual C++ Window Guide
This article elucidates the methodology for altering the mouse pointer within a window in Microsoft Foundation Class (MFC) applications, utilizing Visual C++. The techniques described herein are specifically applicable to unmanaged Visual C++ code, offering developers precise control over cursor behavior within their applications. Understanding how to customize mouse pointers is crucial for enhancing user experience and providing intuitive visual feedback in Windows applications.
Summary¶
In the Windows operating environment, every window is instantiated based on a window class. This class definition encompasses various attributes of the windows derived from it, notably including the default mouse pointer, also known as the cursor. However, application developers often encounter scenarios where modifying the standard pointer associated with specific windows becomes necessary to align with the application’s functionality and user interface design. This article will explore three distinct methods available within the MFC framework that empower developers to dynamically display different mouse pointers at different times, catering to diverse application needs and user interactions.
Situations Where MFC Applications Display Different Pointers¶
There are several common scenarios in which an MFC application might benefit from displaying varied mouse pointers to improve user experience and provide contextual feedback. These situations often arise from the need to communicate the application’s state or the expected user interaction in a clear and intuitive manner.
-
Enhanced User Interface Clarity: The default arrow pointer might not always be the most suitable visual cue for certain application functionalities. For instance, within a text editor application like Notepad, an I-beam pointer is significantly more intuitive for text selection and manipulation than the standard arrow. Employing a more contextually relevant pointer from the outset can significantly enhance the user’s understanding of the application’s purpose and interaction model. This might involve a persistent pointer change throughout the application’s execution.
-
Indicating Processing States: When an application undertakes a time-consuming operation, such as disk input/output (I/O) or complex calculations, it is crucial to provide visual feedback to the user to indicate that the application is actively working and not unresponsive. In such cases, transitioning the mouse pointer to an hourglass (or wait cursor) is a widely recognized and effective way to communicate processing activity. This visual cue reassures the user that the application is functioning correctly and encourages patience during potentially longer operations. This type of pointer change is typically temporary, reverting to the standard pointer once the operation is complete.
Three Methods to Change Mouse Pointer in a Window¶
MFC provides developers with a versatile set of tools to customize mouse pointer behavior. Here are three primary methods to dynamically alter the mouse pointer within an MFC application window, each catering to different scenarios and levels of customization:
-
Method 1: Overriding
CWnd::OnSetCursor()This method involves intercepting the
WM_SETCURSORmessage, which is sent to a window whenever the mouse cursor moves within its boundaries or when mouse button state changes occur. By overriding theCWnd::OnSetCursor()function within your window class, you gain fine-grained control over the cursor displayed within that window. Inside your overridden function, you can utilize the Windows API functionSetCursor()to explicitly set the desired cursor. This approach is particularly effective for conditional cursor changes based on application state or mouse position. -
Method 2: Registering a Custom Window Class
This technique offers a more permanent and class-level approach to cursor customization. It entails registering a unique window class that is specifically configured with the desired mouse pointer as its default. This is achieved by using functions like
AfxRegisterClass()orAfxRegisterWndClass()to define a new window class structure, including specifying the desired cursor handle. Subsequently, when creating your window, you override theCWnd::PreCreateWindow()function and modify theCREATESTRUCTstructure to specify the newly registered window class name. This ensures that all windows created using this custom class will inherently use the specified mouse pointer as their default. For more in-depth information on window class registration within MFC, consult the Window Class Registration section in MFC Technical Note 1. -
Method 3: Utilizing
CCmdTarget::BeginWaitCursor()andCCmdTarget::EndWaitCursor()For the specific purpose of displaying the standard hourglass pointer (wait cursor) during lengthy operations, MFC provides a convenient pair of functions:
CCmdTarget::BeginWaitCursor()andCCmdTarget::EndWaitCursor(). CallingBeginWaitCursor()will immediately change the mouse pointer to the hourglass, providing visual feedback to the user that a background operation is in progress. Crucially, this method is designed to function correctly even if the cursor moves outside the window during the operation. However, it is important to note that this mechanism is primarily intended for operations that complete within the scope of a single message handler. If the operation spans across multiple messages or involves user interaction, you might need to supplement this method withOnSetCursoroverriding to ensure the hourglass remains displayed consistently, especially when theWM_SETCURSORmessage is triggered due to mouse movement beforeEndWaitCursor()is called. CallingEndWaitCursor()reverts the mouse pointer back to the default cursor associated with the window class.
The following code examples will demonstrate the practical implementation of these three methods to modify the mouse pointer of a CView derived class window within an MFC application. It is assumed that m_ChangeCursor is a boolean member variable of the CMyView class, acting as a flag to indicate whether a custom pointer type should be displayed.
Code for Method 1: Overriding CWnd::OnSetCursor()¶
To alter the mouse pointer for a CMyView object using the first method, you need to override the CWnd::OnSetCursor() function. The Class Wizard in Visual Studio provides a streamlined way to establish the message map function CMyView::OnSetCursor() for the Windows WM_SETCURSOR message. After using the Class Wizard to create the function skeleton, populate the function body with the following code snippet:
BOOL CMyView::OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message)
{
if (m_ChangeCursor)
{
::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_WAIT));
return TRUE;
}
return CView::OnSetCursor(pWnd, nHitTest, message);
}
Explanation:
- The
OnSetCursorfunction is called whenever the cursor is moved within the window or when mouse button states change. - The
if (m_ChangeCursor)statement checks the boolean flagm_ChangeCursor. If this flag isTRUE, it indicates that a custom cursor should be displayed. ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_WAIT));is the core of the cursor change.AfxGetApp()retrieves a pointer to the application object.LoadStandardCursor(IDC_WAIT)loads the standard hourglass cursor (identified by theIDC_WAITconstant) from Windows system resources.::SetCursor()is the Windows API function that actually sets the system cursor to the loaded hourglass cursor. The::prefix ensures that the globalSetCursorfunction is called, not a class member function if one existed.
return TRUE;indicates that the message has been handled, and no further processing is needed. This prevents the base classCView::OnSetCursor()from being called, which would potentially reset the cursor to the default class cursor.- If
m_ChangeCursorisFALSE, the function falls through toreturn CView::OnSetCursor(pWnd, nHitTest, message);, allowing the default cursor handling to occur.
Code for Method 2: Registering a Custom Window Class¶
Method 2 involves registering a custom window class that incorporates the desired mouse pointer by default. This can be achieved using either the AfxRegisterClass() or AfxRegisterWndClass() functions provided by MFC. After registering the custom class, you must ensure that your view window is created based on this newly registered class. For comprehensive details on window class registration within the MFC framework, refer to Window Class Registration in MFC Technical Note 1.
The following code demonstrates how to register a custom window class with an hourglass cursor and apply it during window creation within the PreCreateWindow function:
BOOL CMyView::PreCreateWindow(CREATESTRUCT &cs)
{
cs.lpszClass = AfxRegisterWndClass(
CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW, // use any window styles
AfxGetApp()->LoadStandardCursor(IDC_WAIT),
(HBRUSH)(COLOR_WINDOW + 1)); // background brush
return CView::PreCreateWindow(cs);
}
Explanation:
PreCreateWindow(CREATESTRUCT &cs)is a virtual function inCWndthat is called just before the window is created. It provides an opportunity to modify theCREATESTRUCTstructure, which contains parameters for window creation.cs.lpszClass = AfxRegisterWndClass(...)is the key line where the custom window class is registered and assigned to thelpszClassmember of theCREATESTRUCT. This member specifies the window class name to be used for creating the window.AfxRegisterWndClass(...)registers a new window class and returns a pointer to the class name string.CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW: These are example window styles. You can use any combination of window styles appropriate for your application.AfxGetApp()->LoadStandardCursor(IDC_WAIT): This loads the standard hourglass cursor, as in Method 1. This cursor will be the default cursor for all windows created based on this registered class.(HBRUSH)(COLOR_WINDOW + 1): This specifies the background brush for the window.COLOR_WINDOW + 1is a standard system color for window backgrounds.
return CView::PreCreateWindow(cs);calls the base class implementation ofPreCreateWindowto perform any default window creation setup.
By using this method, any CMyView window created will inherently use the hourglass cursor as its default pointer. This is a class-level setting, affecting all instances of CMyView.
Code for Method 3: Using BeginWaitCursor() and EndWaitCursor()¶
Method 3 leverages the BeginWaitCursor() and EndWaitCursor() functions for temporarily displaying the hourglass cursor, typically during operations that might take a noticeable amount of time.
Here’s a simple example of using these functions within a method that performs a lengthy operation:
void CMyView::PerformLengthyOperation()
{
BeginWaitCursor(); // or AfxGetApp()->DoWaitCursor(1)
//...
// Code for the lengthy operation goes here
//...
EndWaitCursor(); // or AfxGetApp()->DoWaitCursor(-1)
}
Explanation:
BeginWaitCursor();is called at the beginning of the lengthy operation. This function internally sets the mouse cursor to the hourglass. The commented lineAfxGetApp()->DoWaitCursor(1)is an alternative way to achieve the same effect.//... Code for the lengthy operation goes here ...represents the section of code that performs the time-consuming task.EndWaitCursor();is called after the lengthy operation is complete. This function restores the mouse cursor to the default cursor that was active beforeBeginWaitCursor()was called. Similarly,AfxGetApp()->DoWaitCursor(-1)is the alternative way to end the wait cursor.
Handling WM_SETCURSOR with BeginWaitCursor() and EndWaitCursor() across different handlers:
If the calls to BeginWaitCursor() and EndWaitCursor() are not situated within the same message handler function (for instance, if the lengthy operation is initiated in one handler but completes asynchronously in another), you must override OnSetCursor to ensure the hourglass cursor persists correctly, especially if the mouse moves during the operation.
Here’s the modified OnSetCursor override to complement BeginWaitCursor() and EndWaitCursor() usage in such scenarios:
BOOL CMyView::OnSetCursor(CWnd *pWnd, UINT nHitTest, UINT message)
{
if (m_ChangeCursor)
{
RestoreWaitCursor();
return TRUE;
}
return CView::OnSetCursor(pWnd, nHitTest, message);
}
Explanation:
- This overridden
OnSetCursorfunction is similar to the one in Method 1, but instead of directly callingSetCursorwithIDC_WAIT, it callsRestoreWaitCursor(). RestoreWaitCursor()is an MFC function that is specifically designed to be used in conjunction withBeginWaitCursor(). It checks if a wait cursor is currently active (initiated by a previousBeginWaitCursor()call) and, if so, re-applies the hourglass cursor.- This ensures that even if a
WM_SETCURSORmessage is received due to mouse movement while the wait cursor is active, theOnSetCursorhandler will intercept the message and re-establish the hourglass cursor, maintaining consistent visual feedback throughout the lengthy operation, even if it spans across different message handlers or asynchronous operations.
By combining BeginWaitCursor() and EndWaitCursor() with this OnSetCursor override, you create a robust mechanism for displaying the hourglass cursor during extended operations, even in more complex scenarios involving asynchronous processing or user interaction during the wait period.
These three methods provide a comprehensive toolkit for customizing mouse pointers in MFC applications. Choosing the appropriate method depends on the specific requirements of your application, whether you need a persistent cursor change, a class-level default cursor, or a temporary cursor change during specific operations. Experiment with these techniques to enhance the usability and visual feedback of your MFC applications.
Feel free to share your experiences or questions in the comments below!
Post a Comment