1

I have a Canvas as the base of my app, and it has several controls which the user can move, rotate and scale using their respective ManipulationDelta's.

The problem is that the user can accidentally carry the control out of the Canvas or screen boundary while moving the control around with their finger.

How do I restrict the movement of these controls within the Canvas ?

Arctic Vowel
  • 1,492
  • 1
  • 22
  • 34

1 Answers1

2

You could check the ToolWindow control and the FlickBehavior in WinRT XAML Toolkit for inspiration.

It's pretty basic overall. If you don't do inertia it might be something like this:

if (x < 0)
    x = 0;
if (x > _canvas.ActualWidth - this.AssociatedObject.ActualWidth)
    x = _canvas.ActualWidth - this.AssociatedObject.ActualWidth;
if (y < 0)
    y = 0;
if (y > _canvas.ActualHeight - this.AssociatedObject.ActualHeight)
    y = _canvas.ActualHeight - this.AssociatedObject.ActualHeight;

Once you rotate or scale - you need to do some matrix transform operations to get the bounding rectangle. Fortunately the platform has some methods to make it easy for you - namely the TransformToVisual() and TransformPoint() methods. I also have a helper class with en extension method that makes it even easier - check out VisualTreeHelperExtensions.GetBoundingRect().

/// <summary>
/// Gets the bounding rectangle of a given element
/// relative to a given other element or visual root
/// if relativeTo is null or not specified.
/// </summary>
/// <remarks>
/// Note that the bounding box is calculated based on the corners of the element relative to itself,
/// so e.g. a bounding box of a rotated ellipse will be larger than necessary and in general
/// bounding boxes of elements with transforms applied to them will often be calculated incorrectly.
/// </remarks>
/// <param name="dob">The starting element.</param>
/// <param name="relativeTo">The relative to element.</param>
/// <returns></returns>
/// <exception cref="System.InvalidOperationException">Element not in visual tree.</exception>
public static Rect GetBoundingRect(this FrameworkElement dob, FrameworkElement relativeTo = null)
{
    if (DesignMode.DesignModeEnabled)
    {
        return Rect.Empty;
    }

    if (relativeTo == null)
    {
        relativeTo = Window.Current.Content as FrameworkElement;
    }

    if (relativeTo == null)
    {
        throw new InvalidOperationException("Element not in visual tree.");
    }

    if (dob == relativeTo)
    {
        return new Rect(0, 0, relativeTo.ActualWidth, relativeTo.ActualHeight);
    }

    var ancestors = dob.GetAncestors().ToArray();

    if (!ancestors.Contains(relativeTo))
    {
        throw new InvalidOperationException("Element not in visual tree.");
    }

    var topLeft =
        dob
            .TransformToVisual(relativeTo)
            .TransformPoint(new Point());
    var topRight =
        dob
            .TransformToVisual(relativeTo)
            .TransformPoint(
                new Point(
                    dob.ActualWidth,
                    0));
    var bottomLeft =
        dob
            .TransformToVisual(relativeTo)
            .TransformPoint(
                new Point(
                    0,
                    dob.ActualHeight));
    var bottomRight =
        dob
            .TransformToVisual(relativeTo)
            .TransformPoint(
                new Point(
                    dob.ActualWidth,
                    dob.ActualHeight));

    var minX = new[] { topLeft.X, topRight.X, bottomLeft.X, bottomRight.X }.Min();
    var maxX = new[] { topLeft.X, topRight.X, bottomLeft.X, bottomRight.X }.Max();
    var minY = new[] { topLeft.Y, topRight.Y, bottomLeft.Y, bottomRight.Y }.Min();
    var maxY = new[] { topLeft.Y, topRight.Y, bottomLeft.Y, bottomRight.Y }.Max();

    return new Rect(minX, minY, maxX - minX, maxY - minY);
}

Once you get the bounding rect - you can use its dimensions instead of ActualWidth/ActualHeight and X&Y of the manipulated object to determine where it can or cannot go.

Filip Skakun
  • 31,624
  • 6
  • 74
  • 100
  • Yes that works when the controls are aligned with the screen boundaries. But when the controls are rotated by an angle, adding `ActualWidth` to `x` where x is the top-left point does not give the top-right point. – Arctic Vowel Sep 26 '14 at 05:16