实现WPF Snap Grid

时间:2010-08-18 03:19:40

标签: wpf xaml

我正在尝试使用WPF和画布实现捕捉网格。我在想我的数学是关闭的,因为UIElement不会在后台捕捉到网格。下面是我用来创建网格的xaml以及我用来尝试将UIElement捕捉到最近网格线的方法。一旦触发鼠标按钮事件,就会触发使用的方法。如果这不是正确的WPF方法,有人能指出我正确的方向吗?

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>

方法

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

3 个答案:

答案 0 :(得分:7)

你的问题是你的功能实际上并没有做任何事情。你除以网格大小然后乘以网格大小,所以实际上你什么都不做(2 * 16/16 = 2)。您需要使用的是模%运算符,并根据与网格大小的距离调整x / y位置。

这是一个工作函数,如果更靠近左/顶部网格线或向右/向下,则向左/向上捕捉:

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

答案 1 :(得分:1)

Canvas.GetLeft(element)将返回一个double,因此即使gridWidth是一个整数,它将进行双重算术,并且除法和乘法会或多或少地抵消掉。我想你想做一个:

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;

这些会将除法结果舍入为整数并返回gridWidth的倍数。

答案 2 :(得分:0)

我们的想法是确保任何物体的位置都限制在一定数量的范围内;即,最终位置应该是圆形的并且落在该组内。

该集合包含任意数字的所有因子j; j控制“snap”的强度,并确定哪些数字出现在集合中。同样,新位置必须只包含出现在集合中的数字。

例如,假设对象的原始位置为(5, 0),我们希望将其移至(16, 23)。让我们继续并快速点击 5 :这意味着对象的位置可能只包含 5 因子。原来的位置已经落在这个位置,但新的位置没有。

为了模拟“捕捉”,新位置必须落在(15,20)(20, 25)上。找到最近的因子 16 23 ,将为您提供正确的观点。在大多数情况下,有必要对结果进行舍入。

示例

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

不言而喻,在调整对象大小时也可以使用此算法,以确保对象的位置和大小“快照”。