PhilSnap
PhilSnap

Reputation: 3

WPF - Touch manipulation problem when translate after a rotation

I have a problem with object transformations via Manipulation events, more precisely matrix.

Problem: After a first successfull rotation/scale/translation of my object(btw: a Rectangle), the second manipulation (a translation in this case) will move the object in the direction of its new angle. For example, if after a first rotation (new angle of 45°) I later touch the screen from right to left, the object will follow a 45° diagonal path instead of the path that I draw.

Expectation: I want my object to follow exactly the path I make on the screen regardless of its rotation.

Situation: I drop a Rectangle in a Canvas by code, this rectangle has "IsManipulationEnabled = true" and a "RenderTransformOrigin = new Point(.5, .5)" and "mySuperRectangle.ManipulationDelta += Container_ManipulationDelta;"

This is the code I use:

    private void Container_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
    {
        try
        {
            if (e.OriginalSource != null)
            {
                Rectangle image = e.OriginalSource as Rectangle;


                Point center = new Point(image.ActualWidth / 2.0, image.ActualHeight / 2.0);
                Matrix imageMatrix = ((MatrixTransform)image.RenderTransform).Matrix;
                center = imageMatrix.Transform(center);

                // Move the Rectangle.
                imageMatrix.Translate(e.DeltaManipulation.Translation.X,
                                            e.DeltaManipulation.Translation.Y);

                // Rotate the Rectangle.
                imageMatrix.RotateAt(e.DeltaManipulation.Rotation,
                                     center.X,
                                     center.Y);

                // Resize the Rectangle. Keep it square 
                // so use only the X value of Scale.
                imageMatrix.ScaleAt(e.DeltaManipulation.Scale.X,
                                    e.DeltaManipulation.Scale.X,
                                    center.X,
                                    center.Y);

                // Apply changes
                image.RenderTransform = new MatrixTransform(imageMatrix);

                Rect containingRect =
                    new Rect(((FrameworkElement)e.ManipulationContainer).RenderSize);

                Rect shapeBounds =
                    image.RenderTransform.TransformBounds(
                        new Rect(image.RenderSize));

                // Check if the rectangle is completely in the window.
                // If it is not and intertia is occuring, stop the manipulation.
                if (e.IsInertial && !containingRect.Contains(shapeBounds))
                {
                    e.Complete();
                }

                e.Handled = true;
            }
        }
        catch (Exception)
        {
           throw;
        }
    }

Any ideas ?

Thanx a lot.

Upvotes: 0

Views: 716

Answers (2)

PhilSnap
PhilSnap

Reputation: 3

Final code for me:

private void CanvasManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    var transform = rectangle.RenderTransform as MatrixTransform;
    var matrix = transform.Matrix;
    Point center = new Point(rectangle.ActualWidth / 2.0, rectangle.ActualHeight / 2.0);
    center = matrix.Transform(center);

    matrix.ScaleAt(e.DeltaManipulation.Scale.X , e.DeltaManipulation.Scale.X, center.X, center.Y);
    matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);
    matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);

    rectangle.RenderTransform = new MatrixTransform(matrix);
}

Edit: Don't use the as operator without checking the result for null. In this case, if transform is null, create a new MatrixTransform and assign it to the RenderTransform property. Afterwards, reuse the transform and just update its Matrix property.

Improved code:

private void CanvasManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    var transform = rectangle.RenderTransform as MatrixTransform;

    if (transform == null) // here 
    {
        transform = new MatrixTransform();
        rectangle.RenderTransform = transform;
    }

    var matrix = transform.Matrix;
    var center = new Point(rectangle.ActualWidth / 2.0, rectangle.ActualHeight / 2.0);
    center = matrix.Transform(center);

    matrix.ScaleAt(e.DeltaManipulation.Scale.X , e.DeltaManipulation.Scale.X, center.X, center.Y);
    matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);
    matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);

    transform.Matrix = matrix; // here
}

Upvotes: 0

Clemens
Clemens

Reputation: 128180

You should handle the manipulation event on the Canvas, because the origin of the manipulation should be relative to the fixed Canvas, instead of the moving Rectangle.

<Canvas IsManipulationEnabled="True"
        ManipulationDelta="CanvasManipulationDelta">
    <Rectangle x:Name="rectangle" Width="200" Height="200" Fill="Red">
        <Rectangle.RenderTransform>
            <MatrixTransform/>
        </Rectangle.RenderTransform>
    </Rectangle>
</Canvas>

The event handler would work like this:

private void CanvasManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    var transform = (MatrixTransform)rectangle.RenderTransform;
    var matrix = transform.Matrix;
    var center = e.ManipulationOrigin;
    var scale = (e.DeltaManipulation.Scale.X + e.DeltaManipulation.Scale.Y) / 2;

    matrix.ScaleAt(scale, scale, center.X, center.Y);
    matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);
    matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);

    transform.Matrix = matrix;
}

Upvotes: 1

Related Questions