开发者

Implementing WPF Snap Grid

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 th开发者_如何学运维e 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);
    }   


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 );
}


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.


The idea is to make sure the position of any object is limited to a certain number set; i.e., the final position should be rounded and fall within this set.

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

For example, say the original position of an object is (5, 0) and we wish to move it to (16, 23). Let's go ahead and give a snap of 5: This means the position of the object may only consist of factors of 5. The original position already falls in this set, but the new position does not.

In order to simulate "snapping", the new position must fall on either (15,20) or (20, 25). Finding the nearest factor of 16 and 23, will give you the right point. In most cases, it is necessary 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 be used when resizing the object, as well, in order to ensure both the object's position AND size "snap".

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜