Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementing WPF Snap Grid

Tags:

wpf

xaml

I am trying to implement a snap grid using WPF and a canvas. I am thinking my math is off because the UIElement won't snap to the grid in the background. Below is the xaml I use to create the grid and the method I use to try and snap the UIElement to the closest grid line. The method used is fired as soon as the mouse button up event is fired. If this not the correct 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);
    }   
like image 474
user337816 Avatar asked Aug 18 '10 03:08

user337816


2 Answers

Your problem is your function doesn't actually do anything. You divide by the grid size then multiply by the grid size, so in effect you're doing nothing (2 * 16 / 16 = 2). What you need to use is the modulus % operator and adjust the x/y position based on the distance from your grid size.

Here is a working function that snaps 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's 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 );
}
like image 184
Adam Sills Avatar answered Oct 05 '22 20:10

Adam Sills


Canvas.GetLeft(element) will return a double, so even if gridWidth is an integer that's going to do double arithmetic and the division and multiplication will more or less cancel out. 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 result of the division to an integer and return a multiple of gridWidth.

like image 42
Quartermeister Avatar answered Oct 05 '22 20:10

Quartermeister