WPF Snap Grid Implementation

I am trying to implement a binding grid using WPF and canvas. I think my math is off because UIElement will not snap to the grid in the background. Below I use xaml to create a grid and the method I use to try to snap a UIElement to the nearest grid line. The used method is launched as soon as an event with a mouse button occurs. If this is not the right approach for WPF, can someone point me in the right direction?

Xaml

<Border x:Name="dragBorder" BorderBrush="Black" BorderThickness="1" Margin="5" CornerRadius="3"> <Canvas x:Name="dragCanvas" AllowDragging="true" AllowDragOutOfView="False" Height="{Binding ElementName=dragBorder, Path=ActualHeight}" Width="{Binding ElementName=dragBorder, Path=ActualWidth}"> <Canvas.Background> <VisualBrush TileMode="Tile" Viewport="0,0,16,16" ViewportUnits="Absolute" Viewbox="0,0,16,16" ViewboxUnits="Absolute"> <VisualBrush.Visual> <Ellipse Fill="#FF000000" Width="2" Height="2" /> </VisualBrush.Visual> </VisualBrush> </Canvas.Background> </Canvas> </Border> 

Method

 private void SnapToGrid(UIElement element) { double xSnap = (Canvas.GetLeft(element) / gridWidth) * gridWidth; double ySnap = (Canvas.GetTop(element) / gridWidth) * gridWidth; Canvas.SetLeft(element, xSnap); Canvas.SetTop(element, ySnap); double tempX = Canvas.GetLeft(element); double tempY = Canvas.GetTop(element); } 
+4
source share
3 answers

Your problem is that your function does virtually nothing. You divide by the size of the grid, then multiply by the size of the grid so that you are not doing anything (2 * 16/16 = 2). What you need to use is the % module operator and adjust the x / y position based on the distance from your grid size.

Here is a working function that anchors the left / top, if closer to the left / top grid line or right / down otherwise:

 private void SnapToGrid( UIElement element ) { double xSnap = Canvas.GetLeft( element ) % GRID_SIZE; double ySnap = Canvas.GetTop( element ) % GRID_SIZE; // If it less than half the grid size, snap left/up // (by subtracting the remainder), // otherwise move it the remaining distance of the grid size right/down // (by adding the remaining distance to the next grid point). if( xSnap <= GRID_SIZE / 2.0 ) xSnap *= -1; else xSnap = GRID_SIZE - xSnap; if( ySnap <= GRID_SIZE / 2.0 ) ySnap *= -1; else ySnap = GRID_SIZE - ySnap; xSnap += Canvas.GetLeft( element ); ySnap += Canvas.GetTop( element ); Canvas.SetLeft( element, xSnap ); Canvas.SetTop( element, ySnap ); } 
+7
source

Canvas.GetLeft(element) returns double, so even if gridWidth is an integer that will do double arithmetic, and division and multiplication will be more or less canceled. I think you want to do one of:

 double xSnap = Math.Floor(Canvas.GetLeft(element) / gridWidth) * gridWidth; double xSnap = Math.Ceiling(Canvas.GetLeft(element) / gridWidth) * gridWidth; double xSnap = Math.Round(Canvas.GetLeft(element) / gridWidth) * gridWidth; 

Those will round the division result to an integer and return a multiple of gridWidth.

+1
source

The idea is to make sure that the position of any object is limited to a specific set of numbers; those. the final position should be rounded and fall into this set.

The set consists of all factors for an arbitrary number, j ; j controls the strength of the "binding" and determines which numbers are displayed in the set. The new position, again, should consist only of the numbers that appear in the set.

For example, let's say that the initial position of the object is (5, 0) , and we want to move it to (16, 23) . Let it go ahead and give a snap 5 . This means that the position of the object can consist only of factors 5 . The starting position already falls into this set, but not in the new position.

To simulate a β€œsnap”, the new position must fall on (15,20) or (20, 25) . Finding the closest odds of 16 and 23 will give you the correct point. In most cases, you need to round the result.

Example

 //Get original point of object relative to container element var original_point = e.GetPosition(sender); //Calculate new x and y position based on mouse //var new_x = ... //var new_y = ... //New position must be multiple of 8 var snap = 8; //Get nearest factor of result position new_x = original_point.X.NearestFactor(snap); new_y = original_point.Y.NearestFactor(snap); public static double NearestFactor(this double Value, double Factor) { return Math.Round((Value / Factor), MidpointRounding.AwayFromZero) * Factor; } 

It goes without saying that this algorithm can also be used when resizing an object to ensure both the position of the object and the size of the "snap".

0
source

All Articles