Visio has the ability to refine the options for snapping shapes to just grids, but this does not necessarily mean that shapes will automatically position themselves on a grid, or that the size is a multiple of a grid. What if you want some shapes to always be positioned on a fixed grid, and the width and height to always be a multiple on the horizontal and vertical grids respectively? What if you want the grid to start at the top left rather then the default bottom left? Fortunately, the ShapeSheet of the page and shapes can provide this ability, without the need for any external code.
You can set the page to have Fixed horizontal and vertical grids, each with different minimum spacing, if necessary. You can set the grid and ruler origins using the Ruler & Grid dialog, but it will be always relative to the bottom left corner of the page. So this can be a problem if you change the print margins or the page size, if you really want the grid origin to remain in another position, like top left.
You can just leave the Grid for snapping, and even change its strength to Strong, and even enter a larger number manually. This can help with snapping to the grids, but it is not enforced to be exclusive to the grids.
It is necessary to enter formulas into the ShapeSheet of the page to enter the formulas that cannot be entered using the dialogs.
The formula for the XRulerOrigin and XGridOrigin page ShapeSheet cells ensure that they are set relative to the left of the page:
=GUARD(PageLeftMargin)
The formula for the YRulerOrigin and YGridOrigin page ShapeSheet cells ensure that they are set relative to the top edge of the page:
=GUARD(PageHeight-PageTopMargin)
I also added a AllowOffGrid Boolean Shape Data row to the page so that the autosnapping can be disabled, if necessary.
The next task is to prepare a shape so that it will automatically snap to the horizontal and vertical grids, whenever it is moved or resized. This can be done by adding just four User-defined Cells into its ShapeSheet. In each of the following trigger formulas, nothing happens if the page AllowOffGrid Shape Data row is True, or the shape is already snapped to the relevant grid. If a nudge or resize is required, then it checks whether to move or resize to the nearest increment of the grid spacing.
User.XGridTrigger
=IF( OR(ThePage!Prop.AllowOffGrid,MODULUS(PinX-ThePage!XGridOrigin,ThePage!XGridSpacing)=0), 0, IF( MODULUS(PinX-ThePage!XGridOrigin,ThePage!XGridSpacing)>=0.5*ThePage!XGridSpacing, SETF(GetRef(PinX),ThePage!XGridOrigin +CEILING((PinX-ThePage!XGridOrigin)/ThePage!XGridSpacing)*ThePage!XGridSpacing), SETF(GetRef(PinX),ThePage!XGridOrigin +FLOOR((PinX-ThePage!XGridOrigin)/ThePage!XGridSpacing)*ThePage!XGridSpacing) ) )
User.YGridTrigger
=IF( OR(ThePage!Prop.AllowOffGrid,MODULUS(ThePage!YGridOrigin-PinY,ThePage!YGridSpacing)=0), 0, IF( MODULUS(ThePage!YGridOrigin-PinY,ThePage!YGridSpacing)>=0.5*ThePage!YGridSpacing, SETF(GetRef(PinY),ThePage!YGridOrigin -CEILING((ThePage!YGridOrigin-PinY)/ThePage!YGridSpacing)*ThePage!YGridSpacing), SETF(GetRef(PinY),ThePage!YGridOrigin -FLOOR((ThePage!YGridOrigin-PinY)/ThePage!YGridSpacing)*ThePage!YGridSpacing) ) )
User.WidthTrigger
=IF( OR(ThePage!Prop.AllowOffGrid,MODULUS(Width,ThePage!XGridSpacing)=0), 0, IF( MODULUS(Width,ThePage!XGridSpacing)>=0.5*ThePage!XGridSpacing, SETF(GetRef(Width),CEILING(Width/ThePage!XGridSpacing)*ThePage!XGridSpacing), SETF(GetRef(Width),FLOOR(Width/ThePage!XGridSpacing)*ThePage!XGridSpacing) ) )
User.HeightTrigger
=IF( OR(ThePage!Prop.AllowOffGrid,MODULUS(Height,ThePage!YGridSpacing)=0), 0, IF( MODULUS(Height,ThePage!YGridSpacing)>=0.5*ThePage!YGridSpacing, SETF(GetRef(Height),CEILING(Height/ThePage!YGridSpacing)*ThePage!YGridSpacing), SETF(GetRef(Height),FLOOR(Height/ThePage!YGridSpacing)*ThePage!YGridSpacing) ) )
That is all that is required to make the shape respect the grids!
By the way, the inserted custom formulas in my shape text are:
="Left Edge = "&((PinX-ThePage!PageLeftMargin)/ThePage!XGridSpacing) &" grids / Top Edge = "&((ThePage!YGridOrigin-PinY)/ThePage!YGridSpacing)&" grids" ="Width = "&(Width/ThePage!XGridSpacing) &" grids * Height = "&(Height/ThePage!YGridSpacing)&" grids"
The sample document can be downloaded from here .