Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Child control handling touch event affects multi-point manipulation

Tags:

wpf

I have a UserControl that must respond to TouchUp events and this sits within a Viewbox which needs to be panned and scaled with pinch manipulation. Touch events on the control are handled fine. However pinch manipulations only scale the ViewPort if both pinch points are contained entirely within either the user control or the Viewport space around it. If the pinch straddles the user control boundary then the ManipulationDelta loses one of the points and reports a scale of (1,1).

If I remove IsManipulationEnabled="True" from the control handling the TouchUp event then the scaling works but the touch event doesn’t fire.

What can I do to retain the manipulation across the ViewPort whilst also handling the touch event in the user control?

Screen Grab

Test Solution

<Window x:Class="TouchTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Touch Test" 
        Height="400" 
        Width="700"
        ManipulationDelta="OnManipulationDelta"
        ManipulationStarting="OnManipulationStarting">

    <Grid Background="Transparent" 
          IsManipulationEnabled="True">

        <Viewbox x:Name="Viewbox"
                 Stretch="Uniform">

            <Viewbox.RenderTransform>
                <MatrixTransform/>
            </Viewbox.RenderTransform>

            <Grid Width="800" 
                  Height="800" 
                  Background="LightGreen"
                  IsManipulationEnabled="True"
                  TouchUp="OnTouchUp">

                <TextBlock x:Name="TimeTextBlock" 
                           FontSize="100"
                           TextAlignment="Center"
                           VerticalAlignment="Center"/>

            </Grid>

        </Viewbox>

        <TextBlock x:Name="ScaleTextBlock" 
                   FontSize="10"
                   HorizontalAlignment="Right"
                   VerticalAlignment="Bottom"/>

    </Grid>
</Window>

Handlers in code-behind:

private void OnTouchUp(object sender, TouchEventArgs e)
{
    TimeTextBlock.Text = DateTime.Now.ToString("H:mm:ss.fff");
}

private void OnManipulationStarting(object sender, ManipulationStartingEventArgs e)
{
    e.ManipulationContainer = this;
}

private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
    if (Viewbox == null)
    {
        return;
    }

    ManipulationDelta delta = e.DeltaManipulation;

    ScaleTextBlock.Text = $"Delta Scale: {delta.Scale}";

    MatrixTransform transform = Viewbox.RenderTransform as MatrixTransform;

    if (transform == null)
    {
        return;
    }

    Matrix matrix = transform.Matrix;

    Point position = ((FrameworkElement)e.ManipulationContainer).TranslatePoint(e.ManipulationOrigin, Viewbox);

    position = matrix.Transform(position);

    matrix = MatrixTransformations.ScaleAtPoint(matrix, delta.Scale.X, delta.Scale.Y, position);
    matrix = MatrixTransformations.PreventNegativeScaling(matrix);
    matrix = MatrixTransformations.Translate(matrix, delta.Translation);
    matrix = MatrixTransformations.ConstrainOffset(Viewbox.RenderSize, matrix);

    transform.Matrix = matrix;
}

Supporting class:

public static class MatrixTransformations
{
    /// <summary>
    /// Prevent the transformation from being offset beyond the given size rectangle.
    /// </summary>
    /// <param name="size"></param>
    /// <param name="matrix"></param>
    /// <returns></returns>
    public static Matrix ConstrainOffset(Size size, Matrix matrix)
    {
        double distanceBetweenViewRightEdgeAndActualWindowRight = size.Width * matrix.M11 - size.Width + matrix.OffsetX;
        double distanceBetweenViewBottomEdgeAndActualWindowBottom = size.Height * matrix.M22 - size.Height + matrix.OffsetY;

        if (distanceBetweenViewRightEdgeAndActualWindowRight < 0)
        {
            // Moved in the x-axis too far left. Snap back to limit
            matrix.OffsetX -= distanceBetweenViewRightEdgeAndActualWindowRight;
        }

        if (distanceBetweenViewBottomEdgeAndActualWindowBottom < 0)
        {
            // Moved in the x-axis too far left. Snap back to limit
            matrix.OffsetY -= distanceBetweenViewBottomEdgeAndActualWindowBottom;
        }

        // Prevent positive offset
        matrix.OffsetX = Math.Min(0.0, matrix.OffsetX);
        matrix.OffsetY = Math.Min(0.0, matrix.OffsetY);

        return matrix;
    }

    /// <summary>
    /// Prevent the transformation from performing a negative scale.
    /// </summary>
    /// <param name="matrix"></param>
    /// <returns></returns>
    public static Matrix PreventNegativeScaling(Matrix matrix)
    {
        matrix.M11 = Math.Max(1.0, matrix.M11);
        matrix.M22 = Math.Max(1.0, matrix.M22);

        return matrix;
    }

    /// <summary>
    /// Translate the matrix by the given vector to providing panning.
    /// </summary>
    /// <param name="matrix"></param>
    /// <param name="vector"></param>
    /// <returns></returns>
    public static Matrix Translate(Matrix matrix, Vector vector)
    {
        matrix.Translate(vector.X, vector.Y);
        return matrix;
    }

    /// <summary>
    /// Scale the matrix by the given X/Y factors centered at the given point.
    /// </summary>
    /// <param name="matrix"></param>
    /// <param name="scaleX"></param>
    /// <param name="scaleY"></param>
    /// <param name="point"></param>
    /// <returns></returns>
    public static Matrix ScaleAtPoint(Matrix matrix, double scaleX, double scaleY, Point point)
    {
        matrix.ScaleAt(scaleX, scaleY, point.X, point.Y);
        return matrix;
    }
}
like image 555
Andy Reed Avatar asked Mar 16 '18 14:03

Andy Reed


1 Answers

So, I'm not a wpf programmer. But have a suggestion/workaround which could possibly work for you.

You could code the thing as follows:

  • set IsManipulationEnabled="True" (in this case OnTouchUp isn't fired for the grid colored in LightGreen)

  • Set OnTouchUp to fire on either Viewbox x:Name="Viewbox" or the Grid above this Viewbox (rather than for the 800x800 Grid)

  • So now OnTouchUp would be fired whenever you touch anywhere in the Viewbox (not just inside the LightGreen area)

  • When OnTouchUp is now fired, just check if the co-ordinates are in the region of LightGreen box. If YES-> update the time, if no, leave the time as it is.

I understand this is a workaround. Still posted an answer, in case it could prove useful.

like image 124
Vishal Avatar answered Sep 24 '22 00:09

Vishal