WPF 4.0’s manipulation events certainly made things easier to write an application that supports multitouch gestures. After you start playing with these gestures, however, you’ve found yourself disappointed.
You want more. There’s something missing. It’s just not like it used to be. “It’s not you, Manipulation events,” you say. “No…it’s me.” But then? A spark! You find out something new about them! Your relationship is saved! “Why, Manipulation events, I never knew you could handle…inertia!”
Having a long-term relationship with APIs aside, you’ve certainly landed on something interesting. WPF 4.0’s Manipulation events can also be used to handle inertia, which allows your UI to look a little more natural and fun.
For those of you who didn’t pay attention in 4th grade science, inertia is Newton’s Second Law of Motion. This law states that objects in motion tend to stay in motion, unless acted upon by an outside force. In other words: Ugg move stuff. Ugg let go. Stuff still move. Ugg hungry.
The idea behind inertia in WPF’s Manipulation events is to make objects that are being manipulated behave as a user would expect. When a user spins a card on a table, he can let go and it will continue spinning until it decelerates to a stop. Adding inertia to your manipulable objects makes users giddy to see things on a computer imitate the physical world.
Let’s start with the same Window as I used in the manipulation post.
In order to handle inertia, we need to create an event handler for our new inertia event, ManipulationInertiaStarting. This goes right along with your ManipulationDelta and ManipulationStarting events.
<Window x:Class="NewTouchTest.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525" ManipulationStarting="Window_ManipulationStarting" ManipulationDelta="HandleManipulation" ManipulationInertiaStarting="HandleInertia"> |
The rest of the XAML is the same.
<Grid x:Name="AppGrid"> <Rectangle Fill="Blue" Height="100" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" x:Name="ManRect1" IsManipulationEnabled="True"> <Rectangle.RenderTransform> <MatrixTransform> <MatrixTransform.Matrix> <Matrix OffsetX="250" OffsetY="200"/> </MatrixTransform.Matrix> </MatrixTransform> </Rectangle.RenderTransform> </Rectangle> <Rectangle Fill="Red" Height="100" Width="200" VerticalAlignment="Top" HorizontalAlignment="Left" x:Name="ManRect2" IsManipulationEnabled="True"> <Rectangle.RenderTransform> <MatrixTransform> <MatrixTransform.Matrix> <Matrix OffsetX="50" OffsetY="50"/> </MatrixTransform.Matrix> </MatrixTransform> </Rectangle.RenderTransform> </Rectangle> </Grid> </Window> |
For the code behind, we once again see our old friends.
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } private void Window_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { e.ManipulationContainer = this; e.Handled = true; } |
Once the user stops performing the gesture, ManipulationInertiaStarting is fired. Our event handler, HandleInertia, is actually a very simple method. It is used to set the values of deceleration for the various manipulation components.
You can set deceleration for each of the transformations supported by manipulation: translation, scaling, and rotation. Don’t get too worried about the numbers we have here (I pulled this from the inertia explanation on MSDN originally, I think). You don’t have to be so specific to take into account your DPI to ensure you have the exact right deceleration in physical terms. These values work pretty well, though.
private void HandleInertia(object sender, ManipulationInertiaStartingEventArgs e) { // Decrease the velocity of the Rectangle's movement by // 10 inches per second every second. // (10 inches * 96 pixels per inch / 1000ms^2) e.TranslationBehavior.DesiredDeceleration = 10 * 96.0 / (1000.0 * 1000.0); // Decrease the velocity of the Rectangle's resizing by // 0.1 inches per second every second. // (0.1 inches * 96 pixels per inch / (1000ms^2) e.ExpansionBehavior.DesiredDeceleration = 0.1 * 96 / 1000.0 * 1000.0; // Decrease the velocity of the Rectangle's rotation rate by // 2 rotations per second every second. // (2 * 360 degrees / (1000ms^2) e.RotationBehavior.DesiredDeceleration = 720 / (1000.0 * 1000.0); e.Handled = true; } |
Once it has set these deceleration values, it once again fires the ManipulationDelta event – if you recall, this is the event whose handler applies all of the transformations. It populates its ManipulationDeltaEventArgs with the previous values, decreased by our deceleration values. It continues to fire the event with diminishing values, causing the object to slowly come to a stop.
Since we are just reusing our already-defined ManipulationDelta handler, inertia is an incredibly easy addition to make to your manipulable objects.
private void HandleManipulation(object sender, ManipulationDeltaEventArgs e) { Rectangle rectToManipulate = e.OriginalSource as Rectangle; Rect shapeBounds = rectToManipulate.RenderTransform.TransformBounds(new Rect(rectToManipulate.RenderSize)); Rect containingRect = new Rect(((FrameworkElement)this).RenderSize); ManipulationDelta manipDelta = e.DeltaManipulation; |
The only change we have to make to our handler is to check to make sure our object doesn’t fly away. This is a simple solution where, if the object goes out of the window, it completes the inertia and provides a bounce effect to give feedback to the user it has reached the edge of the screen. ***Correction: the e.Complete() method now appears to cancel the ReportBoundaryFeedback method (I wrote this application while everything was in beta). You can have the bounce effect without the e.Complete(), but your rectangle then flies out of the window. Let me know if you have a simple solution for allowing both to happen, as I likely won’t put any effort into it…*** You could easily change the behavior here to make the object more realistically react to its bounds if you like.
// 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)) { // if both are uncommented, e.Complete() overrides e.ReportBoundaryFeedback() // comment out for a bounce, uncomment to stop the rectangle e.Complete(); // comment out to stop the rectangle, uncomment for a bounce // e.ReportBoundaryFeedback(bounceDelta); } Matrix rectsMatrix = ((MatrixTransform)rectToManipulate.RenderTransform).Matrix; Point rectManipOrigin = rectsMatrix.Transform(new Point(rectToManipulate.ActualWidth / 2, rectToManipulate.ActualHeight / 2)); // Rotate the Rectangle. rectsMatrix.RotateAt(manipDelta.Rotation, rectManipOrigin.X, rectManipOrigin.Y); // Resize the Rectangle. Keep it square // so use only the X value of Scale. rectsMatrix.ScaleAt(manipDelta.Scale.X, manipDelta.Scale.Y, rectManipOrigin.X, rectManipOrigin.Y); // Move the Rectangle. rectsMatrix.Translate(manipDelta.Translation.X, manipDelta.Translation.Y); // Apply the changes to the Rectangle. rectToManipulate.RenderTransform = (MatrixTransform)(new MatrixTransform(rectsMatrix).GetAsFrozen()); e.Handled = true; } } |
That concludes my series on WPF 4.0 multitouch. Let me know in the comments what kinds of UI elements you’ve touchified with these new events.