In a recent post, I showed you how to react to touch events in WPF 4.0. You can use that to implement the showcase multitouch gestures: scaling, rotating, and translation. It’s not too hard. Really, I’ve done it. Just dust off your geometry and trigonometry hats and get to it.
Are you done yet? No? Too lazy? Well, how about we make this easier. As I like to say regarding programmers: if necessity is the mother of invention, laziness is most certainly the father.
Luckily for us, Windows 7 has multitouch gesture recognition built in, and WPF now supports listening for it in its upcoming 4.0 release. Here’s how you can implement these gestures in your application.
We’ll first define a window that will contain two rectangles to manipulate.
The containing control defines handlers for the ManipulationStarting and ManipulationDelta events. These events are fired when a multitouch gesture is first recognized and when it changes, respectively.
<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"> |
The IsManipulationEnabled property is set to true for each object that we plan to manipulate. This property tells WPF to watch for gestures on manipulable controls. I would guess that forcing you to explicitly define the elements that react to gestures improves the performance of gesture recognition.
<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> |
public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } |
The ManipulationStarting handler sets up the manipulation container in order to specify a frame of reference that the values will be relative to. For example, it establishes the origin (0,0) for x and y coordinates.
private void Window_ManipulationStarting(object sender, ManipulationStartingEventArgs e) { e.ManipulationContainer = this; e.Handled = true; } |
The ManipulationDelta handler is used to perform the transformations as the gesture is being performed. It will fire continuously as long as the gesture is changing.
private void HandleManipulation(object sender, ManipulationDeltaEventArgs e) { Rectangle rectToManipulate = e.OriginalSource as Rectangle; ManipulationDelta manipDelta = e.DeltaManipulation; |
First, grab the rectangle’s current transform matrix so we can use that as a baseline.
Matrix rectsMatrix = ((MatrixTransform)rectToManipulate.RenderTransform).Matrix; |
Re-establishing the base line each time is important, as the values that the ManipulationDelta sends are not absolute. Each time the handler is called, the values are relative to the previous event firing. For example, if a user gestures a total rotation of 30 degrees, the events would look something like this:
# of Events | e.DeltaManipulation.Rotation | Total Rotation | ![]() |
---|---|---|---|
1 | 5 | 5 | |
2 | 5 | 10 | |
3 | 5 | 15 | |
4 | 5 | 20 | |
5 | 5 | 25 | |
6 | 5 | 30 |
Next, we establish an origin to use for the following manipulations. This specifies the point around which the rectangle will rotate and scale. Here, we’re setting it up at the middle of the rectangle.
Point rectManipOrigin = rectsMatrix.Transform(new Point(rectToManipulate.ActualWidth / 2, rectToManipulate.ActualHeight / 2)); |
Finally, we apply the transformations to the baseline matrix and set this matrix to the sending rectangle’s RenderTransform as frozen.
rectsMatrix.RotateAt(manipDelta.Rotation, rectManipOrigin.X, rectManipOrigin.Y); rectsMatrix.ScaleAt(manipDelta.Scale.X, manipDelta.Scale.Y, rectManipOrigin.X, rectManipOrigin.Y); rectsMatrix.Translate(manipDelta.Translation.X, manipDelta.Translation.Y); rectToManipulate.RenderTransform = (MatrixTransform)(new MatrixTransform(rectsMatrix).GetAsFrozen()); e.Handled = true; } } |
See? Easy. Now, maybe you should get to that housework you’ve been putting off.
Pingback: WPF 4.0 Multitouch: Touch Points | Between the Lines
Just dropping a note to say many thanks for the multitouch posts and keep ’em coming. I’ll be playing with the multitouch features of .Net 4 in the coming weeks so this blog is about to become my new best friend and I can’t wait to put your examples into action.
So many thanks, your work is appreciated.
Thanks, Brent. Appreciate the comment. Always great to hear from people who are putting my posts to good use!
Pingback: Windows 7 and WPF 4.0 Multitouch: Inertia | Between the Lines
great work, I am trying to implement something similar but should work like the windows picture viewer, performing the basic zoom, pan and rotate on single static image. Do you have any suggestions?? please help.
Sure, just set IsManipulationEnabled to true for your Image object. Don’t forget to handle the ManipulationStarting and ManipulationDelta events.
thanks Andrew. It works good, I was trying to ZOOM, Pan and Rotate an image in WPF same way as it can be done in windows picture viewer. Your code moves the image from its location I was trying to make the image stay at its place and zoom through pinch and rotate by 90 degrees for one rotate gesture and pan it after it zooms. Basically I was trying to design something which works like the picture viewer. I have been trying to get this done but unable to make it do you have any suggestions? please help.
You can choose which manipulations to perform in the ManipulationDelta handler. For example, to only zoom (which is equivalent to scaling), use only the scaling method for the matrix:
rectsMatrix.ScaleAt(manipDelta.Scale.X, manipDelta.Scale.Y, rectManipOrigin.X, rectManipOrigin.Y);
Does anyone know how to constrain the item being manipulated to its parent? I’m trying to zoom/pan an image, but I don’t want the use to be able to drag it all over the window.