#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));
        }
    }
Advertisement

#755 – Implementing Rotational Inertia during Touch Manipulation

In the same way that you can support inertia as a result of touch manipulation during translation and expansion, you can also set up rotational inertia.  When the user rotates an element using touch, the element has some initial rotational velocity (in deg/ms) when they lift their fingers off the screen.  You can then specify a desired value for a rotational deceleration (deg/ms^2).

As with translation and expansion, you specify the desired rotational deceleration in a handler for the ManipulationInertiaStarting event.  In the example below, we display the initial rotational velocity to the console and then specify a deceleration of 100 degrees/sec^2.  (Reduce velocity by 100 deg/sec each second).

        private void Image_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {
            // Rotational inertia - 100 deg/sec^2 deceleration
            Console.WriteLine(string.Format("Initial rotational velocity = {0} deg/sec",
                e.RotationBehavior.InitialVelocity * 1000.0));
            e.RotationBehavior.DesiredDeceleration = 100.0 / (1000.0 * 1000.0);
        }

755-001

#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

#751 – Indicating which Touch Manipulation Modes You Support

When you support touch manipulation events, you can choose which types of touch manipulation to support for an element.  The modes include:

  • Translation (in X, Y, or both)
  • Rotation
  • Scaling

You indicate which modes you want to support within the ManipulationStarting event handler, setting the ManipulationStartingEventArgs.Mode property.  You can set this property to some combination of :

  • ManipulationModes.None
  • ManipulationModes.TranslateX
  • ManipulationModes.TranslateY
  • ManipulationModes.Translate
  • ManipulationModes.Rotate
  • ManipulationModes.Scale
  • ManipulationModes.All

When you enable a mode, the ManipulationDelta event will fire when a user manipulates the element using touch, and will include data for that style of manipulation.  When a manipulation mode isn’t supported, the ManipulationDelta event will not fire for that mode.  It may fire for other manipulation actions, but will not include data for modes that are disabled.

The mode values listed above can be combined using the OR (|) operator.

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

#750 – Using Touch Manipulation to Translate in Just One Dimension

You can set the IsManipulationEnabled property and handle the ManipulationDelta event for an element, to support translating (moving) the element using touch.

By default, when you read the ManipulationDelta.Translation property, it will contain translation values for both X and Y.  If you want to allow moving the element only horizontally or vertically, you can handle the ManipulationStarting event and set the ManipulationStartingEventArgs.Mode property to either TranslateX or TranslateY.

The example below shows how we could limit translation to be only horizontal.  You could do the same thing by ignoring the Y component of the Translation property in the ManipulationDelta event handler.

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

            // Allow only horizontal translation
            e.Mode = ManipulationModes.TranslateX;
        }

#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);
        }

#746 – Specifying Inertial Deceleration

In WPF, you can use inertia so that objects will continue moving on the screen after you lift your finger off the screen.

Calculation of inertial behavior requires both an initial velocity and a deceleration.  WPF knows the initial velocity of an object, based on how fast you are moving it on the screen.  The deceleration value is something that you specify.

Initial velocity values are typically in the range of around 0-4 DIPs/ms (DIPs per millisecond), or 0-42 in/sec.

Deceleration is expressed in DIPs/ms^2 (DIPs per millisecond squared).  If we want to decelerate to 0 within about 1/2 sec, we can use values in the range of  0-0.008 DIPs/ms^2.  (4 / 500).  This is equivalent to 83 ft/sec^2.

If you start with a deceleration value in in/sec^2, you can convert to DIPs/ms^2 using the formula:

x’ = x * 96 / (1000 * 1000)

You can experiment with different deceleration values to get the exact deceleration behavior that you want in your application.

#745 – The Basics of Inertia

Inertia is the idea that an object will resist a change in motion.  For touch manipulation in WPF, inertia means that objects can continue moving a little bit after you lift your finger from the screen.

Inertial behavior depends on two things–the initial velocity of the object a specified deceleration value.

The initial velocity is the speed at which the object is moving across the screen when you let go of it.  The deceleration is the rate at which the initial velocity should be decreased, until it eventually reaches zero.  In other words–how quickly does the object slow down?

The deceleration value has units that are DIPs (device independent pixels) per ms^2 (millisecond squared, or “per millisecond per millisecond”).  I.e. DIPs/ms^2.  In other words, if the object’s initial velocity is expressed in DIPs/ms, how much should that velocity decrease every millisecond?

#744 – Keeping an Element within Window During Touch Manipulation

You can use the ManipulationDelta event handler to translate a user interface element in response to the user touching and dragging it.  In the previous code, there was nothing preventing the user from sliding the element off of the screen.

We can make the element stop when it hits a window boundary by checking its bounds against the bounds of its parent.  Below is the update code for the ManipulationDelta event handler that does the checking.  (See earlier example for the full code sample).

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

            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);

            // Check to see if element is at one of the edges of the window
            FrameworkElement feParent = fe.Parent as FrameworkElement;
            bool atEdge = false;
            if (feParent != null)
            {
                Rect feRect = fe.TransformToAncestor(feParent).TransformBounds(
                    new Rect(0.0, 0.0, fe.ActualWidth, fe.ActualHeight));
                atEdge = (feRect.Right + trans.X) > feParent.ActualWidth ||
                    (feRect.Bottom + trans.Y) > feParent.ActualHeight ||
                    (feRect.Left + trans.X) < 0 ||
                    (feRect.Top + trans.Y) < 0;
            }

            // Update matrix to reflect translation
            if (!atEdge)
                m.Translate(trans.X, trans.Y);

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

            e.Handled = true;
        }