Simple Snap-to-Grid cursor to your graphics application






4.97/5 (23 votes)
Oct 27, 2002
4 min read

118364

6979
Simple Class to add Snap-to-Grid capability to a Windows drawing program.
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 choosingWM_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 yourOnLButtonDown(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 yourOnMouseMove(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, addm_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) { // TODO: Add your message handler code here and/or call default CDC* pDC = GetDC(); CRect rcClient; GetClientRect(&rcClient); // Get First Snap position for drawing circle m_SnapCursor.GetFirstSnapPos(&point); // MUST ADD FOR SNAPCURSOR m_OriginPoint.x = point.x; // the rest of the code m_OriginPoint.y = point.y; ... CView::OnLButtonDown(nFlags, point); }
Code Showing Step 7.
void CSceneView::OnMouseMove(UINT nFlags, CPoint point) { // TODO: Add your message handler code here and/or call default int old_mode; CDC* pDC = GetDC(); // Display Cursor and get snapped point value m_SnapCursor.Draw(pDC, &point); // MUST ADD FOR SNAPCURSOR ... // The rest of the code CView::OnMouseMove(nFlags, point); }
Code Showing Step 8.
BOOL CSceneView::OnEraseBkgnd(CDC* pDC) { // TODO: Add your message handler code here and/or call default // Reset or re-initialize snapcursor mechanism ("flip-flop") m_SnapCursor.Reset(); // MUST ADD FOR SNAPCURSOR ... // The rest of the code }
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 tom_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
// Placed in "ButtonDown" method to capture first snapped point void GetFirstSnapPos(CPoint *point); // Placed in "MouseMove" method to convert points and draw cursor void Draw(CDC *pDC, CPoint *point); // Placed in "OnEraseBkgnd" to put SnapCursor in initial state when Window is redrawn void Reset();
Optional Methods for Custom Settings
// Turn Snap on or off with TRUE or FALSE void Enable(BOOL bOn); // Set the snap increment to values other than 10 void SetSnapIncrement(int nIncrement); // Set offset from 0,0 void SetSnapOffset(int nX_offset, int nY_offset);