Mouse Movement

4 minutes to read

In this article

When the mouse moves, Windows posts a WM_MOUSEMOVE message. By default, WM_MOUSEMOVE goes to the window that contains the cursor. You can override this behavior by capturing the mouse, which is described in the next section.

The WM_MOUSEMOVE message contains the same parameters as the messages for mouse clicks. The lowest 16 bits of lParam contain the x-coordinate, and the next 16 bits contain the y-coordinate. Use the GET_X_LPARAM and GET_Y_LPARAM macros to unpack the coordinates from lParam. The wParam parameter contains a bitwise OR of flags, indicating the state of the other mouse buttons plus the SHIFT and CTRL keys. The following code gets the mouse coordinates from lParam.

int xPos = GET_X_LPARAM(lParam); int yPos = GET_Y_LPARAM(lParam);

Remember that these coordinates are in pixels, not device-independent pixels (DIPs). Later in this topic, we will look at code that converts between the two units.

A window can also receive a WM_MOUSEMOVE message if the position of the cursor changes relative to the window. For example, if the cursor is positioned over a window, and the user hides the window, the window receives WM_MOUSEMOVE messages even if the mouse did not move. One consequence of this behavior is that the mouse coordinates might not change between WM_MOUSEMOVE messages.

Capturing Mouse Movement Outside the Window

By default, a window stops receiving WM_MOUSEMOVE messages if the mouse moves past the edge of the client area. But for some operations, you might need to track the mouse position beyond this point. For example, a drawing program might enable the user to drag the selection rectangle beyond the edge of the window, as shown in the following diagram.

To receive mouse-move messages past the edge of the window, call the SetCapture function. After this function is called, the window will continue to receive WM_MOUSEMOVE messages for as long as the user holds at least one mouse button down, even if the mouse moves outside the window. The capture window must be the foreground window, and only one window can be the capture window at a time. To release mouse capture, call the ReleaseCapture function.

You would typically use SetCapture and ReleaseCapture in the following way.

When the user presses the left mouse button, call SetCapture to start capturing the mouse. Respond to mouse-move messages. When the user releases the left mouse button, call ReleaseCapture.

Example: Drawing Circles

Let's extend the Circle program from Module 3 by enabling the user to draw a circle with the mouse. Start with the Direct2D Circle Sample program. We will modify the code in this sample to add simple drawing. First, add a new member variable to the MainWindow class.

D2D1_POINT_2F ptMouse;

This variable stores the mouse-down position while the user is dragging the mouse. In the MainWindow constructor, initialize the ellipse and ptMouse variables.

MainWindow() : pFactory(NULL), pRenderTarget(NULL), pBrush(NULL), ellipse(D2D1::Ellipse(D2D1::Point2F(), 0, 0)), ptMouse(D2D1::Point2F()) { }

Remove the body of the MainWindow::CalculateLayout method; it's not required for this example.

void CalculateLayout() { }

Next, declare message handlers for the left-button down, left-button up, and mouse-move messages.

void OnLButtonDown(int pixelX, int pixelY, DWORD flags); void OnLButtonUp(); void OnMouseMove(int pixelX, int pixelY, DWORD flags);

Mouse coordinates are given in physical pixels, but Direct2D expects device-independent pixels (DIPs). To handle high-DPI settings correctly, you must translate the pixel coordinates into DIPs. For more discussion about DPI, see DPI and Device-Independent Pixels. The following code shows a helper class that converts pixels into DIPs.

class DPIScale { static float scaleX; static float scaleY; public: static void Initialize(ID2D1Factory *pFactory) { FLOAT dpiX, dpiY; pFactory->GetDesktopDpi(&dpiX, &dpiY); scaleX = dpiX/96.0f; scaleY = dpiY/96.0f; } template <typen