#756 – Making Element Stop at Edge of Window When Using Inertia

We saw previously how to stop an object at the edge of a window when using touch manipulation.  You likely also want the object to stop moving when it hits the edge of its container when moving as a result of inertia (i.e. you’ve already lifted your finger from the screen).

In the following code fragment, we’ve enabled positional inertia.  We then check to see if the object is past the container boundary.  If it is and if it’s moving as a result of inertia, we stop the inertial event by calling Complete and we move the object back within the window.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            ImageTransform = new MatrixTransform();
        }

        private MatrixTransform imageTransform;
        public MatrixTransform ImageTransform
        {
            get { return imageTransform; }
            set
            {
                if (value != imageTransform)
                {
                    imageTransform = value;
                    RaisePropertyChanged("ImageTransform");
                }
            }
        }

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Ask for manipulations to be reported relative to the canvas
            e.ManipulationContainer = canvMain;

            // Support translation and scaling
            e.Mode = ManipulationModes.Translate;
        }

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // Translation inertia - 10 in/sec^2 deceleration
            e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0);
        }

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            Matrix m = imageTransform.Matrix;

            // If element beyond edge, report back to WPF
            Vector pastEdgeVector;
            if (ElementPastBoundary(e.Source as FrameworkElement, out pastEdgeVector) &&
                e.IsInertial)
            {
                m.Translate(-1.0 * pastEdgeVector.X, -1.0 * pastEdgeVector.Y);
                imageTransform.Matrix = m;

                e.Complete();
                e.Handled = true;
                return;
            }

            // Find center of element and then transform to get current location of center
            FrameworkElement fe = e.Source as FrameworkElement;
            Point center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
            center = m.Transform(center);

            // Update matrix to reflect translation and rotation
            ManipulationDelta md = e.DeltaManipulation;
            m.Translate(md.Translation.X, md.Translation.Y);

            imageTransform.Matrix = m;
            RaisePropertyChanged("ImageTransform");

            e.Handled = true;
        }

        private bool ElementPastBoundary(FrameworkElement fe, out Vector pastEdgeVector)
        {
            bool pastEdge = false;

            pastEdgeVector = new Vector();

            FrameworkElement feParent = fe.Parent as FrameworkElement;
            if (feParent != null)
            {
                Rect feRect = fe.TransformToAncestor(feParent).TransformBounds(
                    new Rect(0.0, 0.0, fe.ActualWidth, fe.ActualHeight));

                if (feRect.Right > feParent.ActualWidth)
                    pastEdgeVector.X = feRect.Right - feParent.ActualWidth;

                if (feRect.Left < 0)
                    pastEdgeVector.X = feRect.Left;

                if (feRect.Bottom > feParent.ActualHeight)
                    pastEdgeVector.Y = feRect.Bottom - feParent.ActualHeight;

                if (feRect.Top < 0)
                    pastEdgeVector.Y = feRect.Top;

                if ((pastEdgeVector.X != 0) || (pastEdgeVector.Y != 0))
                    pastEdge = true;
            }

            return pastEdge;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

#754 – Implementing Inertia for Expansion during Touch Manipulation

You can set the TranslationBehavior.DesiredDeceleration in the ManipulationInertiaStarting event to allow inertia when translating using touch manipulation.  This allows an element to continue moving a little bit after you lift your finger off the element while doing translation manipulation.

You can also enable inertia for expansion (i.e. scaling) during touch manipulation.  An element will then continue expanding or contracting when you lift your fingers from the screen while doing expansion using touch.  You do this by setting the ExpansionBehavior.DesiredDeceleration property.

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // 10 in/sec^2 deceleration
            e.TranslationBehavior.DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0);

            // 960 DIPS/sec^2 deceleration
            Console.WriteLine(string.Format("Init Expansion Velocity = {0}", e.ExpansionBehavior.InitialVelocity));
            e.ExpansionBehavior.DesiredDeceleration = 960.0 / (1000.0 * 1000.0);
        }

Doing this is perhaps a little less useful than specifying inertia during translation.  Inertia as part of expansion is a little less intuitive.

#753 – Scale vs. Expansion in ManipulationDelta Events

When handling a ManipulationDelta event during touch manipulation, you often care about the ManipulationDelta.Scale property, which indicates the updated scale of an element, relative to its previous size (e.g. 0.5 = 1/2 size).

You can also access a ManipulationDelta.Expansion property, which tells you the actual number of device independent units (1/96th in) that the element is changing, relative to its last known size.

The example below dumps out both scale and expansion values as we scale with touch.

        private Vector totalScale = new Vector(1.0, 1.0);
        private Vector totalExpansion = new Vector(0.0, 0.0);

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            ManipulationDelta md = e.DeltaManipulation;

            totalScale.X *= md.Scale.X;
            totalScale.Y *= md.Scale.Y;

            totalExpansion.X += md.Expansion.X;
            totalExpansion.Y += md.Expansion.Y;

            Console.WriteLine(string.Format(
                "Scale: {0},{1}.  Expansion: {2},{3}",
                md.Scale.X, md.Scale.Y, md.Expansion.X, md.Expansion.Y));
            Console.WriteLine(string.Format(
                "  Total Scale: {0},{1}.  Total Expansion: {2},{3}",
                totalScale.X, totalScale.Y, totalExpansion.X, totalExpansion.Y));
        }

753-001

#749 – Handling the TouchEnter and TouchLeave Events

When handling the raw touch events, if you touch a user interface element and then lift your finger off the element, you’ll see the events:

  • TouchDown
  • TouchUp

These two events do not capture, however, cases when you slide your finger onto or off of the element.  For this situation, you can  handle the TouchEnter and TouchLeave events, which fire when a touch contact enters or leaves the boundaries of the element.

If you then touch an element and then lift your finger off the element, you’ll see:

  • TouchEnter
  • TouchDown
  • TouchUp
  • TouchLeave

If you slide your finger onto the element and then lift your finger off the element, you’ll see:

  • TouchEnter
  • TouchUp
  • TouchLeave

Touching an element and then sliding off it leads to:

  • TouchEnter
  • TouchDown
  • TouchLeave

Finally, sliding onto and then off an element leads to:

  • TouchEnter
  • TouchLeave

#748 – Getting the Size of a Contact Point during Raw Touch

In the handlers for the various raw touch events, you can get information about the size of the actual touch contact (the area where your finger is touching the screen).  You get the size from the TouchPoint.Bounds property, which contains the touch position and its size.

Note that the touch contact will not have a non-zero size on every device.  In some cases, the size may be reported if this feature is not supported.

Here’s an example of drawing an ellipse at a size based on the size of the touch contact.

    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            TouchEllipses = new Dictionary<int, Ellipse>();
        }

        private Dictionary<int, Ellipse> TouchEllipses;

        private void Canvas_TouchDown(object sender, TouchEventArgs e)
        {
            canvMain.CaptureTouch(e.TouchDevice);
            TouchPoint tp = e.GetTouchPoint(canvMain);

            Ellipse el = new Ellipse();
            el.Stroke = Brushes.Black;
            el.Fill = Brushes.Black;

            el.Width = tp.Bounds.Width > 0 ? tp.Bounds.Width : 50;
            el.Height = tp.Bounds.Height > 0 ? tp.Bounds.Height : 50;

            Canvas.SetLeft(el, tp.Position.X - (el.Width / 2));
            Canvas.SetTop(el, tp.Position.Y - (el.Height / 2));

            canvMain.Children.Add(el);
            TouchEllipses.Add(e.TouchDevice.Id, el);

            e.Handled = true;
        }

        private void Canvas_TouchUp(object sender, TouchEventArgs e)
        {
            canvMain.Children.Remove(TouchEllipses[e.TouchDevice.Id]);
            TouchEllipses.Remove(e.TouchDevice.Id);

            e.Handled = true;
        }
    }

#747 – Implementing Inertia during Touch Manipulation

We’ve talked about how to calculate a value for inertial deceleration.  Once you know what deceleration value that you want, you can implement inertia during touch manipulation by handling the ManipulationInertiaStarting event.

The ManipulationInertiaStarting event will fire after the user lifts their finger off of the screen.  In the event handler, if you specify a deceleration value, the inertia will be modeled and the object will continue to move after the user lifts their finger.

The example below specifies a deceleration value of 40 in/sec^2.  It also dumps out the initial velocity of the object being translated, for informational purposes.

Note that we are setting up inertia for translation only.  We could also specify different deceleration values for rotation and scaling to get touch-based inertia while rotating or scaling.

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            e.TranslationBehavior.DesiredDeceleration = 40.0 * 96.0 / (1000.0 * 1000.0);
            Trace.WriteLine(e.TranslationBehavior.InitialVelocity);
        }

#742 – Using Touch Manipulation Events to Rotate an Element

In addition to using the touch manipulation events to handle translation of an element, we can use the same mechanisms to allow a user to rotate an element using touch.

We can do both translation and rotation in the same event handler.  The ManipulationDelta object gives us both a translation (vector) and a rotation (angle).  Both are automatically incorporated into the ManipulationDelta object, based on how the user is touching the screen.  The user can translate by sliding one finger around and can rotate by placing two fingers on the object and rotating it.

We transform the element by calling two different functions of the underlying Matrix, for both translation and rotation.

Here’s the XAML, containing a single Image control that we’ll interact with.

    <Canvas Name="canvMain" Background="Transparent">
        <Image Source="JamesII.jpg" Width="100"
               IsManipulationEnabled="True"
               RenderTransform="{Binding ImageTransform}"
               ManipulationStarting="Image_ManipulationStarting" ManipulationDelta="Image_ManipulationDelta"/>
    </Canvas>

Here is the source code, with the updated ManipulationDelta event handler.

    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = this;

            ImageTransform = new MatrixTransform();
        }

        private MatrixTransform imageTransform;
        public MatrixTransform ImageTransform
        {
            get { return imageTransform; }
            set
            {
                if (value != imageTransform)
                {
                    imageTransform = value;
                    RaisePropertyChanged("ImageTransform");
                }
            }
        }

        private void Image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
        {
            // Ask for manipulations to be reported relative to the canvas
            e.ManipulationContainer = canvMain;
        }

        private void Image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
        {
            ManipulationDelta md = e.DeltaManipulation;
            Vector trans = md.Translation;
            double rotate = md.Rotation;

            Matrix m = imageTransform.Matrix;

            // Find center of element and then transform to get current location of center
            FrameworkElement fe = e.Source as FrameworkElement;
            Point center = new Point(fe.ActualWidth / 2, fe.ActualHeight / 2);
            center = m.Transform(center);

            // Update matrix to reflect translation/rotation
            m.Translate(trans.X, trans.Y);
            m.RotateAt(rotate, center.X, center.Y);

            imageTransform.Matrix = m;
            RaisePropertyChanged("ImageTransform");

            e.Handled = true;
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void RaisePropertyChanged(string prop)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(prop));
        }
    }

742-001