Recently I have written a number of applications, all of which supported the ability to drag objects inside an MFC view. This is boilerplate code, not particularly difficult to write, but it is difficult to get correct and fully functional. I decided to write a supporting class, to handle the job of monitoring what the user did with the mouse. If possible the supporting class was to be completely self-sufficient. I would have liked to achieve a situation where it could be included in a project with only a couple of lines of code. In practice, my solution did not achieve this, but I managed to get close.
The issues to be addressed are:
- the supporting object needs to monitor button down, button up, and mouse movement messages
- the order of these messages allows the object to determine if the user's gesture (what the user is doing with the mouse) is a drag operation. Drag is defined as the sequence: button down in the view; mouse movements, button up.
- the view needs to capture mouse input at strategic points. (See sidebar Mouse Capture)
At its most basic, a drag operation is indicated by the sequence of mouse actions; button down, mouse movement, button up. The complicating factors are:
- after the initial button down operation the user may move the mouse outside the screen area occupied by the view window.
- if the mouse button is now released, outside the view area, the view needs to know about it. In this implementation this is considered to be a correctly specified drag operation even though the target point is not visible in the view window.
- the mouse may come into the view window with a button already pressed. This may be because the user is in the middle of a drag operation involving another window. If that other window has captured the mouse, this situation is of no concern. The mouse will be moving over the surface of our window but Windows will not send us any mouse movement messages. They will go to the other window. If that other window has not captured the mouse then we will want to reject the situation, even if the mouse button is released over our view, because the drag operation did not start in the visible portion of our view window. For more information on how this works see the sidebar mouse capture.
- there are two buttons on the mouse. Each of these should be considered separately from the point of view of drag operations. This allows an application
to treat left drag and right drag operations as being completely separate. The implementation discussed here supports the situation where the user left clicks
and drags an object. Before that drag operation is completed the user may right click and drag another object. At this point the mouse is moving with both
buttons pressed. The user may choose to terminate the right drag operation by releasing the right mouse button, and then later terminate the left drag
operation by releasing the left mouse button. This implementation will recognise two separate drag operations.
How to use it:
Get the code from the download page (opens in new window) and add it to your project.
Include the header file, in your view class's implementation file. The include must be after the inclusion of afxwin.h, or the include of something that includes afxwin.h.
Declare a private object of type CMouseFollower in your view class.
Use ClassWizard to override the WindowProc virtual function for your view class. Make it look like the following example. In the latest versions of the product you will just have to type in the middle line. If you are working in 16 bit Windows, ClassWizard is not aware of the WindowProc function and you will have to type the whole thing, including adding it to the header file, yourself.
LRESULT CMouseFollowerView::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
// TODO: Add your specialized code here and/or call the base class
mf.WindowProc(m_hWnd, message, wParam, lParam) ;
return CView::WindowProc(message, wParam, lParam);
assuming you called your object mf. Be sure to call the base class function after your inserted line.
The basic functionality of the mouse follower is now fully enabled. It is now up to you to use it. Inside your
OnMouseMovehandler you can use the CMouseFollower :: QueryLStatus() function to find out if the left mouse button is being dragged. This function returns one of the status values
CMouseFollower :: ButtonStatus :: NoDrag or
CMouseFollower :: ButtonStatus :: Drag
If you find it is being dragged, then the
CMouseFollower ::QueryPositionAtLDragStart () function will tell you where the mouse was when the left button went down to initiate the drag operation. You then implement whatever functionality you require in your
MouseMovehandler to provide visual feedback to your user as the drag operation progresses.
The end of the drag operation has to be detected in your
MouseUphandler. You can use the
CMouseFollower :: QueryPositionAtLDragEnd()function to find out
where the mouse was at the end of the drag operation.
There are matching …Rdrag… functions if you are interested in the right mouse button.
Having to detect the end of the drag operation in the
MouseUphandler is a bit of a pain, and runs counter to one of my original goals. I wanted to avoid,
overt, communication between the mouse handler functions in the CView derived class. This is achieved by having the
CMouseFollowerobject send your view a
user defined message at the end of a drag operation. To use this notification requires you to do a little more work. Windows allows the programmer to define his
own messages so long as he stays away from the range that Windows uses. Windows supplies the macro WM_USER to define the start of the range available for
programmer-defined messages. There are two messages
WM_USER_RDROP defined in MouFollo.h as
#define WM_USER_LDROP (WM_USER + 1)
#define WM_USER_RDROP (WM_USER + 2)
you can change the numbers if you are already using user defined messages.
When the user finishes a drag operation with the left button your view is sent a
WM_USER_LDROPmessage. To handle one of these messages requires a little more work on your part. You will need to add the following two lines to your view class's message map. Make sure you get them outside the territory occupied by class wizard.
In the header file you will require prototypes for the two handler functions as follows.
virtual afx_msg LONG OnMouseLeftDrop( UINT, LONG );
virtual afx_msg LONG
OnMouseRightDrop( UINT, LONG );
Now you will have the full functionality. Inside these two handler functions you will not need special code to detect if a drag operation has started. Be aware that you will still get the
MouseUpmessage. The user-defined messages are in addition, not in substitution.
How it works
Most of the code is simple and self-explanatory. The
CMouseFollower class has member functions that are called when various events of interest happen. Inside
CMouseFollowerclass there is a nested, helper, class,
CMouseButton which models the interesting events for a mouse button. To support a two button mouse
there are two
CMouseButtonobjects embedded inside the
CMouseFollower class. I decided not to offer support for three button mice simply because MFC does not
offer it either. The extension would be trivial for
CMouseFollower. Getting the information from MFC a little harder.
The tricky part is the
WindowProc override. It is necessary for the
CMouseFollower object to be informed of various interesting events with the mouse. It would have been perfectly practical to require the user to wire in function calls to all of the mouse events. My self-sufficiency goal meant I wanted something more automatic. I thought there might be a way in which a view's message map could be dynamically modified. I thought I would be able to insert entries that would route the events to functions inside the
CMouseFollower object. I found nothing. My solution is to override the
WindowProc function. The
WindowProc function is as close as MFC allows you to writing a standard SDK window procedure. The technique I use uses the Message Cracker macros from the SDK. This is a little known technique that allows source code portability between 16 and 32 bit Windows. The basic strategy involves the use of a set of macros, supplied by Microsoft. These macros, different between 16 and 32 bit Windows, break apart the values encoded inside the two values passed to a standard window procedure. Once broken apart, they are passed as arguments into a function whose name you supply. Although they were not originally intended for use in a C++ program, because they are "just" macros the expanded code can still work in C++.
CMouseFollower::WindowProc looks for the mouse messages of interest, movement, button down, and button up, and passes them on to member functions of
CMouseFollower class. This technique allows the
CMouseFollower object to see the message traffic into the view. It then hooks into the messages of interest
without preventing them from going on into the message map.
Sidebar: Mouse Capture
If a window chooses to "capture the mouse" then all future mouse input will be routed to that window until the capture is released. This is different to the normal behaviour. The Windows default behaviour is that mouse input will be routed to whichever window is under the mouse. Mouse capture is a necessary technique for a window that implements drag and drop. In the most general case, a program like Windows Explorer permits the user to drag a file from the Explorer window and drop it in another application, for example Notepad. The basis of this kind of function is that the Explorer window captures the mouse when the user begins the drag operation. As the user drags the file around on the screen all of the mouse movement messages go to the original Explorer window. Explorer can then use API functions to identify the window under the mouse's current position and can send that window a message to identify if it would be acceptable to drop files here or not.
To implement drag and drop inside an application you must still capture the mouse. Your application will need to know if your user drags one of your user interface objects outside your view. You can only do this if you capture the mouse at the button down message. Your application will also not want to be fooled if the mouse should enter your view with the button already down. Whatever is happening here it is certainly not a drag and drop that involves your user interface objects.
All of these problems can be avoided by a properly implemented scheme of mouse capture. The only downside to mouse capture is that it is essential for the capturing window to release the mouse. Until the capturing window releases the mouse no one else can get any mouse messages.