
Introduction
I have been developing a graphics application, which uses controls to create
and place objects on the drawing. I needed an accurate way of placing these
objects with a mouse. I remembered that my 3DS MAX program has a snap-to-grid
feature. I searched the web and found lots of questions on how to do it, some
suggestions, but no code examples. It struck me that this would be a good first
article to place on Code Project. As I got this feature to work, I generated an
example project, which would be as simple as possible and would help me package
SnapCursor class so it could be dropped into any project and used with a minimum
of effort.
Features
Once added, SnapCursor works much like the snap-to-grid option on my 3DS MAX
graphics program. When enabled, a second, crosshair cursor follows the regular
mouse cursor, which is not replaced or eliminated. In the demo project, you can
draw circles starting from center, then dragging to the radius that you want. As
the regular cursor moves smoothly across the screen, the second cursor jumps in
quantum amounts. When the left mouse button is depressed, the center of the
circle is placed at the SnapCursor point. The circle drags to the desired size
as the cursor jumps. When released, the circle is painted in a final color and
the program is ready for the next circle. With the SnapCursor class you can do
the following:
- Set the increment amount
- Set cursor offset (used in conjunction with increment)
- Turn the cursor on or off.
Adding SnapCursor to your Project
Develop your project so that your mouse drawing functions in the view, such
as OnLButtonDown()
, OnMouseMove()
, OnLButtonUp()
are in place and working without SnapCursor.
- Develop your project so that your mouse drawing functions in the view,
such as
OnLButtonDown()
, OnMouseMove()
, OnLButtonUp()
are in place and working without SnapCursor.
- Once satisfied that your draw program is working, add an
OnEraseBkgnd()
message handler by using your class wizard and choosing WM_ERASEBKGND
message for the view. Use this method to fill the client area with a
background color and grid.
- Copy the SnapCursor.cpp and SnapCursor.h files into your
project folder and include them in your project by selecting menu Project->Add
To Project->Files.
- Add
#include "SnapCursor.h"
your view class header
file.
- Add the declaration
CSnapCursor m_SnapCursor;
as a member of
your view class to instantiate the SnapCursor object.
- Add
m_SnapCursor.GetFirstSnapPos(&point);
as the first
call in your OnLButtonDown(UINT nFlags, CPoint point)
. This
will convert the point.x and point.y values to the snapped to position
before using them as your first drawing point.
- Add
m_SnapCursor.Draw(pDC, &point);
as the first call
after the device context instatiation in your OnMouseMove(UINT nFlags,
CPoint point)
, view class method. This call will continually convert
point values to snapped-to values before using them and will also draw and
re-draw the snap cursor as the mouse is moved within the client area.
- In the
OnEraseBkgnd(CDC* pDC)
method, add m_SnapCursor.Reset();
.
This is necessary to make sure that the cursor drawing algorithm is in the
proper state if the window is resized or repainted. Otherwise, old cursor
positions may re-appear if the the window is resized and the background is
repainted.
Code Showing Step 6.
void CSceneView::OnLButtonDown(UINT nFlags, CPoint point)
{
CDC* pDC = GetDC();
CRect rcClient;
GetClientRect(&rcClient);
m_SnapCursor.GetFirstSnapPos(&point);
m_OriginPoint.x = point.x;
m_OriginPoint.y = point.y;
...
CView::OnLButtonDown(nFlags, point);
}
Code Showing Step 7.
void CSceneView::OnMouseMove(UINT nFlags, CPoint point)
{
int old_mode;
CDC* pDC = GetDC();
m_SnapCursor.Draw(pDC, &point);
...
CView::OnMouseMove(nFlags, point);
}
Code Showing Step 8.
BOOL CSceneView::OnEraseBkgnd(CDC* pDC)
{
m_SnapCursor.Reset();
...
}
Adding Optional features
- If you wish to set the snap value to something other than 10 pixels, add
m_SnapCursor.SetSnapIncrement(value)
to
your view class constructor or add a method which allows you to change the
in the call to m_SnapCursor.SetSnapIncrement(snapvalue);.
m_SnapCursor.SetSnapOffset(X_offset, Y_offset);
.is used in
conjunction with SetSnapIncrement. It is used to align cursor snap to
various grid configurations. For example, if snap increment is set to 50,
major grid marks start at something other than 50, the cursor is not going
to line up with the major grid lines unless an offset is used. This is
tricky, because if you set an offset of 40, 40 and your increment is 5 or
10, its just going to make the cursor follow 50 units away from the Windows
cursor. SnapIncrement and SnapOffset have to be used with some thought. The
SnapOffset values default to 0, 0.
Methods
Necessary Methods
void GetFirstSnapPos(CPoint *point);
void Draw(CDC *pDC, CPoint *point);
void Reset();
Optional Methods for Custom Settings
void Enable(BOOL bOn);
void SetSnapIncrement(int nIncrement);
void SetSnapOffset(int nX_offset, int nY_offset);