Exercise 2 – Multi-touch on Windows 7

The goal of this exercise is to familiarize you with Multi-touch support as provided by Silverlight 4 under Windows 7. Multi-touch denotes a set of interaction techniques which allow Silverlight users to control the graphical user interface with more than one finger. Whilst a normal touch screen application allows you to drag or select visual elements with your finger, a multi-touch application allows you to do something such as resize a visual element, by stretching or squeezing it with two fingers. This functionality leverages Windows 7 multi-touch APIs and requires supported hardware. It works by subscribing to the frame reported event of the static Touch class – the event argument provides information about the collection of detected touch points. Within this collection, the API will have marked the most pressed touch point as the primary touch point in the collection. Each touch point exposes various properties, such as timestamp (for applying comparative temporal logic between detected touch points), an action (which can be move, down, up), a source device and others.

Task 1 – Enabling Multi-touch support

In order to provide multi-touch support, the application must be aware of touch events reported by the Silverlight engine. We will add those events at two levels: at the level of a single photo (to make the photo active, much like clicking with the mouse) and at the level of the application surface (to move/rotate the photo objects).

  1. Close the browser window hosting the Silverlight application if it is still running, and go back to the Visual Studio development environment.
  2. First we will add touch support to the Photo object. Open Photo.xaml.cs and locate the “IntializePhoto” function.
  3. Add subscription to touch events by adding the following code at the end of the InitializePhoto function body:

    C#

    Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
  4. Create the event handler function:

    C#

    void Touch_FrameReported(object sender, TouchFrameEventArgs e) { }
  5. A Touch event is a bubbled Routed Event (thus it will travel up the visual tree from its originating element to the root of the control). This means that events will bubble up and be received at the level of Canvases and Rectangles in the control itself, and not at the level of the Photo control. In order to ease on operation with the photo control we need to know which Photo was touched rather than which element of its visual tree. In addition, this event will arrive to all photo objects under the pressed point, an effect which is not desired, requiring a work around to suppress it. To detect the control instance, use a helper function to detect touch events emanating from the real Photo control from the provided child. Add the following code to the Photo class:

    C#

    private Photo GetContainer(DependencyObject theObj) { DependencyObject obj = VisualTreeHelper.GetParent(theObj); if (obj is Photo) return obj as Photo; else if (null != obj) return GetContainer(obj); else return null; }
  6. Get back to the “Touch_FrameReported” function. The touch-points reported by the function arguments are provided in multiple ways. We will use a helper function to extract the primary touch point from all the touch points (in a collection). Note: it is possible to understand from the selected element in a touch point’s collection if it is a primary point or not, but in this lab we will not iterate over the collection in order to do this.
  7. Add the following code at the beginning of “Touch_FrameReported” function body:

    C#

    TouchPointCollection points = e.GetTouchPoints(null); TouchPoint primaryPoint = e.GetPrimaryTouchPoint(null);
  8. In order to deactivate mouse events while touching the screen (mouse events are not needed in a touch scenario), add the following lines after the previous code snippet in the “Touch_FrameReported” method:

    C#

    if (null != primaryPoint && primaryPoint.Action == TouchAction.Down)
  9. e.SuspendMousePromotionUntilTouchUp();
  10. Remember that a touch event is a routed event? Add the following code snippet at the end of the “Touch_FrameReported” function to get the Photo control from the primary touch point location, and apply some logic only if the event reported from the same instance of the control:

    C#

    Photo photo = null; if (null != primaryPoint) photo = GetContainer(primaryPoint.TouchDevice.DirectlyOver); else photo = GetContainer(points[0].TouchDevice.DirectlyOver); if (this == photo) { }
  11. In the case of the Photo instance, we are interested only on the “touchdown” action in order to “select” the photo instance. Add the following code, which will report the selection of the current Photo to the parent surface (much like in case of a mouse click event in a mouse-only scenario). Add the code snippet to the “if” block of the previous snippet:

    C#

    TouchPoint thePoint = null != primaryPoint ? primaryPoint : points[0]; switch (thePoint.Action) { case TouchAction.Down: _parent.SetActivePhoto(this, ActionType.Touching, new Point(translateTransform.X + rotateTransform.CenterX, translateTransform.Y + rotateTransform.CenterY), thePoint.Position); break; case TouchAction.Up: break; case TouchAction.Move: break; }
  12. Open MainPage.xaml.cs. Locate the MainPage constructor and subscribe to the Touch.FrameReported event – add the following code snippet at the end of the constructor code:

    C#

    Touch.FrameReported += new TouchFrameEventHandler(Touch_FrameReported);
  13. Create the event handler function and collect touch points and the primary point (using the same code as in Photo class) – add the following code to the MainPage.xaml.cs:

    C#

    void Touch_FrameReported(object sender, TouchFrameEventArgs e) { TouchPointCollection points = e.GetTouchPoints(null); TouchPoint primaryPoint = e.GetPrimaryTouchPoint(null); if (null != primaryPoint && primaryPoint.Action == TouchAction.Down) e.SuspendMousePromotionUntilTouchUp(); }
  14. Now add the following code to move and rotate the selected photo. Add following snippet into the handler function body after the “if” in the previous snippet:

    C#

    if (null != _activePhoto && null != primaryPoint) { // Perform the appropriate transform on the active photo var position = primaryPoint.Position; switch (_actionType) { case ActionType.Touching: if (points.Count == 1 && primaryPoint.Action == TouchAction.Move) { // Move it by the amount of the mouse move _activePhoto.Translate(position.X - _lastPosition.X, position.Y - _lastPosition.Y); } else if (points.Count > 1 && primaryPoint.Action == TouchAction.Move) { // Rotate it according to the angle the mouse moved // around the photo's center var radiansToDegrees = 360 / (2 * Math.PI); var lastAngle = Math.Atan2(_lastPosition.Y - _photoCenter.Y, _lastPosition.X - _photoCenter.X) * radiansToDegrees; var currentAngle = Math.Atan2(position.Y - _photoCenter.Y, position.X - _photoCenter.X) * radiansToDegrees; _activePhoto.Rotate(currentAngle - lastAngle); // Scale it according to the distance the mouse // moved relative to the photo's center var lastLength = Math.Sqrt(Math.Pow(_lastPosition.Y - _photoCenter.Y, 2) + Math.Pow(_lastPosition.X - _photoCenter.X, 2)); var currentLength = Math.Sqrt(Math.Pow(position.Y - _photoCenter.Y, 2) + Math.Pow(position.X - _photoCenter.X, 2)); _activePhoto.Scale(currentLength / lastLength); } break; } _lastPosition = position; }
  15. Compile and run the application. Check the touch features of the application if you're on a touch-enabled device.

    Figure 9

    Finished Application