Register   |  Log In
 MFC Drag and Drop
Minimise

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 :: NoDragor 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_LDROPand
WM_USER_RDROPdefined 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.

ON_MESSAGE(WM_USER_LDROP, OnMouseLeftDrop)
ON_MESSAGE(WM_USER_RDROP,
OnMouseRightDrop)

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 CMouseFollowerclass has
member functions that are called when various events of interest happen. Inside
the CMouseFollowerclass there is a nested, helper, class, CMouseButtonwhich
models the interesting events for a mouse button. To support a two button mouse
there are two CMouseButtonobjects embedded inside the CMouseFollowerclass. 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 WindowProcoverride. It is necessary for the
CMouseFollowerobject 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 the 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
CMouseFollowerobject. I found nothing. My solution is to override the
WindowProcfunction. The WindowProcfunction 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 .

The CMouseFollower::WindowProclooks for the mouse messages of interest,
movement, button down, and Button up, and passes them on to member functions of
the CMouseFollowerclass. This technique allows the CMouseFollowerobject 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.


Dynamisys is based near Swindon, Wiltshire, in the South West, and works with customers located throughout the UK. or telephone us on (+44) 1793 731225.

Learn more...
Next page(s)...

This page

Previous Page(s)...