17.2 Tracking the Mouse in a Window

Tracking mouse movements in a window while the mouse button is pressed involves three different window messages. You must first handle the message that signals that the mouse button has been pressed, WM_LBUTTONDOWN or WM_RBUTTONDOWN, as described in the previous section. You can then handle WM_MOUSEMOVE messages until you get a WM_LBUTTONUP or WM_RBUTTONUP message.

The following list describes the steps necessary to track the mouse in a window. The list assumes that you have derived a window class from an existing Foundation window class, as described in the previous section on handling mouse clicks in a window.

1.Call SetCapture in OnLButtonDown (or OnRButtonDown)

2.Handle mouse movements in OnMouseMove

3.Call ReleaseCapture in OnLButtonUp (or OnRButtonDown)

The following example shows the code necessary for a simple doodling window. This window allows the user to draw freehand lines in the window by pressing the left mouse button and holding it down. The code shows how to capture the mouse and how to create a device context for drawing. It also shows how to create a pen drawing tool as described in the section “Graphic Objects” on page 346.

The class declaration for the doodling window is shown as follows. Notice that the class is derived from CFrameWnd and that it declares message-handler functions to handle the three mouse messages listed in the first part of this section. Notice also that the class declares several member variables to help with the drawing in the window.

class CMainWnd : public CFrameWnd

{

protected:

BOOL m_bTracking;

CClientDC* m_pDCDoodle;

CPen* m_pPenDoodle;

HPEN m_hPenSaved;

int m_nPenWidth;

public:

CMainWindow();

// WM_ message handlers

afx_msg void OnLButtonDown( UINT nFlags, CPoint p );

afx_msg void OnLButtonUp( UINT nFlags, CPoint p );

afx_msg void OnMouseMove( UINT nFlags, CPoint p );

DECLARE_MESSAGE_MAP()

};

As described elsewhere, you must define a message map for the window class. The following definition is from the .CPP file for the doodle window class.

BEGIN_MESSAGE_MAP( CMainWnd, CFrameWnd )

ON_WM_LBUTTONDOWN()

ON_WM_MOUSEMOVE()

ON_WM_LBUTTONUP()

END_MESSAGE_MAP()

In the OnLButtonDown message-handler function, you call SetCapture to tell Windows that you want to retain exclusive control of the mouse while tracking its movement. This prevents another window from getting mouse messages while you are trying to track the mouse. You will call ReleaseCapture when the user releases the mouse button and you stop tracking.

You also create a device context and a drawing pen for the window in response to the WM_LBUTTONDOWN message. You then call SelectObject to install your pen in the device context. SelectObject returns a pointer to a CPen object that represents the previous pen settings for the device context.

As mentioned in the section “Selecting a Drawing Object into a Device Context,” on page 348, the object returned by SelectObject is a temporary object that is liable to be deleted the next time the application is idle. Since idle time can occur while tracking the mouse (when the user stops moving the mouse), you cannot depend on the object returned from SelectObject in the mouse down message handler to still be around when the mouse up message is handled.

To avoid relying on the temporary Foundation CPen object returned by SelectObject, you call GetSafeHandle to extract the HPEN handle from the CPen returned by SelectObject. The HPEN returned by GetSafeHandle is a permanent Windows graphical device interface object that will not be automatically deleted.

Finally, after selecting the newly created pen into the device context, you call MoveTo to establish the anchor point for the tracking.

The code for OnLButtonDown is shown as follows:

void CMainWnd::OnLButtonDown( UINT nFlags, CPoint p )

{

m_bTracking = TRUE;

SetCapture();

// Create the Device Context for this window

m_pDCDoodle = new CClientDC( this );

// Create a new pen to draw in the window

m_pPenDoodle = new CPen( PS_SOLID, m_penWidth, RGB( 0, 0, 0) );

// Select the pen into the Device Context

CPen* pPenTemp = m_pDCDoodle->SelectObject( m_pPenDoodle );

// extract an HPEN from CPen to save for later restoration

m_hPenSaved = (HPEN)pPenTemp->GetSafeHandle();

// establish anchor point for doodling

m_pDCDoodle->MoveTo( p );

}

The message-handler function OnMouseMove is called when your window receives a WM_MOUSEMOVE message. If you are tracking, as indicated by the Boolean member variable m_bTracking, you call the LineTo function to draw a line from the last anchor point to the current mouse location. Drawing the line also establishes a new anchor point. The code for OnMouseMove is shown here:

void CMainWnd::OnMouseMove( UINT nFlags, CPoint p )

{

if( m_bTracking )

{

m_pDCDoodle->LineTo( p );

}

}

Finally, the OnLButtonUp message-handler function is called when the
window receives a WM_LBUTTONUP message. In this function, you call
ReleaseCapture to give other windows access to mouse messages. You also use the HPEN saved in the OnLButtonDown function to create a CPen object that you then use to select the original pen characteristics back into the device context of the window, thus restoring the original drawing environment.

Finally, you delete the doodling pen and device context that you created in OnLButtonDown, as follows:

void CMainWindow::OnLButtonUp( UINT nFlags, CPoint p )

{

if( m_bTracking )

{

m_bTracking = FALSE;

ReleaseCapture();

// Create a CPen from an HPEN

m_pDCDoodle->SelectObject( CPen::FromHandle( m_hPenSaved ));

delete m_pPenDoodle;

delete m_pDCDoodle;

}

}