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?
<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; } }
1 Answers
Answers 1
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 eitherViewbox x:Name="Viewbox"
or theGrid
above thisViewbox
(rather than for the800x800 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.
0 comments:
Post a Comment