Tutorial 2: A Multi-touch Map Application

In this tutorial, we will introduce several advanced topics in uTableSDK, such as multi-touch handler, gesture engine, control replantation, inter-object communication, etc. All these issues will be demonstrated in a multi-touch map application. This application is built based on GMap.NET (http://greatmaps.codeplex.com/), and has the following features:

  • Translate and zoom the map
  • Add, remove and relocate markers
  • Go to specified place on the map
  • Basic map configuration such as changing map type.

The following photos are preview of the final application:

image

 

image

 

1. Create the uTable project

Assumes you have successfully installed uTableSDK on your computer. Now follow the three steps to create an uTable project for our map application:

     a) Open Visual Studio 2008 or Visual Studio 2010

     b) Select File-> New-> Project. In the “New Project” dialog, select Visual C#/UTable entry in the “Project types” column, and select UTableApplication in the “Templates” column.

     c) Give a name to the project “uMap”, and click the OK button.

 

2. Replant other WPF control to our project 

We will firstly create an uTable control called UMapControl, which is the main part of our application. This control encapsulates all the functionalities related to the map such as translating and zooming, and also provides APIs for configuration. However, we will not develop it from the ground. GMap.NET(http://greatmaps.codeplex.com/) is an open source project, and provides a WPF control called GMapControl that enables maps from Google, Yahoo! and Bing. Yet the user interface of GMap.NET is designed for mouse, so what we will do is enable multi-touch on this control.

Generally, any WPF control can be used directly in an uTable project. However, if you want this control to receive multi-touch input, you must create a new control, and let this new control inherit from the old control class and UTable.Objects.Controls.IControl interface. The IControl interface provides several functions and events for you to handle multi-touch and other uTable events. Don’t worry. You don’t need to implement this interface by yourselves. We provide a template so that you can just copy the implementation from this template to your class. (In fact, the implementation just redirects the request to a real class called ControlBaseImpl. This is a standard way to deal with the multi-inheritance issue in C#.)

In our case, please follow the four steps to create UMapControl.

  • Add references to GMap.NET.Core.dll, GMap.NET.WindowsPresentation.dll, and System.Data.SQLite.DLL. (You can get these dlls from from the GMap.NET home website)
  • Add a new class called “UMapControl”, and let this class inherit from GMapControl and UTable.Objects.Controls.IControl.
  • Copy all the code from uTableSDK/utility/ IControlImplementation.cs to UMapControl class.
  • Add a constructor to the class, and call the function InitializeIControlPart() on the first line.

The current UMapControl looks like the following code snapshot. Now what left is just handling the multi-touch inputs on this control.

public class UMapControl : GMap.NET.WindowsPresentation.GMapControl, UTable.Objects.Controls. IControl
{
    public UMapControl()
    {
        // Initialize IControl members
        InitializeIControlPart();
    }

    #region IControl Implementation
       // code copied from uTableSDK/utility/ IControlImplementation.cs
    #endregion
}

 

3. Handle multi-touch inputs using multi-touch handlers

In the first tutorial, we introduced how to handle multi-touch inputs by directly listening to the UTable.Objects.IInputNode.InputReceived event. However, for most cases, how we handle the multi-touch inputs are very similar. For example, we just need some standard gestures extracted from the inputs, such as tap and zoom. To help developers more easily deal with these common input patterns, we invent the concept of Multi-touch Handler.

A multi-touch handler inherits from UTable.Objects.Handlers.MultiTouchEventHandlerClass. It is attached to an uTable control and handles the multi-touch inputs on that control. Typically the handler will raise some events when it detects some input patterns such as a gesture. Therefore, developers can just listening to these special events instead of directly handling the raw multi-touch inputs. In UTable.Object.Handlers namespace, we provide several useful multi-touch handlers:

  • DetectContactsHandler: raise events when any fingers are put down on the control or all fingers are removed from the control. This handler is used by UTable.Objects.Controls. UButton. (Become pressed when any finger is put down, and return to normal when all fingers are removed)
  • SingleFingerHandler: raise the input events only when there is exactly one finger on the control. This handler works like a filter and helps reduce the complexity of multi-touch.
  • LocalGestureEngine: raise events when it detects some standard gestures on the control. The current gesture set is: {Translate, Tap, Hold, Zoom, Rotate, AnchorMove}

When you decide to use a multi-touch handler to handle inputs on an uTable control, following the three steps below:

  • Create an instance of the handler
  • Listen to events of the handler
  • Add this handler to the MultiTouchProcessor member of control

For example, the following code shows how UButton uses DetectContactsHandler to handle the multi-touch inputs.

public UButton()
{
         InitializeIControlPart();
          // Create an instace of the handler
         DetectContactsHandler handler = new DetectContactsHandler();
         // listen to the events of the handler
         handler.NewContact += new ContactEventHandler(handler_NewContact);
         handler.NoContact += new ContactEventHandler(handler_NoContact);
         // Add this handler to the MultiTouchProcessor of the current control
         MultiTouchProcessor.Handlers.Add(handler);
}

void handler_NoContact(FingerEventArgs args)
{
        this.IsPressed = false;
        // raise the click events if needed
}

void handler_NewContact(FingerEventArgs args)
{
        this.IsPressed = true;
}

 

3.1. Use LocalGestureEngine to detect gestures

After a detailed introduction of multi-touch handler, let’s return to the map application. In our UMapControl, we will use the UTable.Objects.Handlers.Gestures.LocalGestureEngine to detect gestures on it. Currently, uTableSDK supports detects the following six gestures:

  • One finger gesture:
    • Tap: Raised when one finger down, and up without moving exceeding certain distance and certain time
    • Hold: Raised when one finger down and hold for certain time without moving exceed certain distance
    • Translate: Raised when one finger is moving
  • Two fingers gesture:
    • Zoom: Raised when two fingers are moving in the same or opposite direction
    • Rotate: Raised when two fingers are rotating around certain center point
  • Three fingers gesture:
    • AnchorMove: Raised when two fingers are holding, and the third finger is moving

In UMapControl, we just use four gestures. The mapping from gesture to command is listed below:

  • Tap -> add a marker.
  • Translate -> move the map.
  • Zoom -> zoom in or zoom out the map.
  • Hold -> pop up a menu

The following code shows how to handle gestures on UMapControl class:

public UMapControl()
{
    // Initialize IControl members
    InitializeIControlPart();

    LocalGestureEngine engine = new LocalGestureEngine(this, this);
    // Configure the gesture engine
    // The max distance that different contacts can be fused into a gesture
    engine.GestureMaxDistance = 2000;
    engine.CaptureFingers = false;  // tell the engine do not capture the fingers it received

    // Create the gesture recognizers, gestures with different finger count is detected in different
    // gesture recognizer
    OneContactGestureRecognizer recognizer1 = new OneContactGestureRecognizer();
    TwoContactsGestureRecognizer recognizer2 = new TwoContactsGestureRecognizer();

     // Enable the gestures we need
    recognizer1.EnabledGestures.Add(OneContactGestureType.Tap);
    recognizer1.EnabledGestures.Add(OneContactGestureType.Translate);
    recognizer1.EnabledGestures.Add(OneContactGestureType.Hold);
    recognizer2.EnabledGestures.Add(TwoContactsGestureType.Zoom);
     // Configure the gesture recognizer
    recognizer1.MinHoldTime = TimeSpan.FromMilliseconds(1000);
    recognizer1.TrackVelocity = true;  // track the velocity
    recognizer1.MinVelocityIntervalTime = 300;  // the time interval used to calculate the velocity

    // Add the gesture recognizers to the gesture engine
    engine.RegisterGesture(recognizer1);
    engine.RegisterGesture(recognizer2);
   
    // Listen to the GestureDetected events
    engine.GestureDetected += new
    UTable.Objects.Handlers.Gestures.GestureDetectedEventHandler(OnGestureDetected);
   
    // Add the gesture engine to the MultiTouchProcessor
    this.MultiTouchProcessor.Handlers.Add(engine);
}

// This function will be called when any enabled gesture is detected
private void OnGestureDetected(GestureRecognizer sender, GestureDetectedEventArgs args)
{
    // Handle different gestures in different routines
    if (args.DetectedGesture is HoldGesture)
    {
        HandleHoldGesture(args.DetectedGesture as HoldGesture);
    }
    else if (args.DetectedGesture is TranslateGesture)
    {
        HandlePanGesture(args.DetectedGesture as TranslateGesture);
    }
    else if (args.DetectedGesture is ZoomGesture)
    {
        HandleZoomGesture(args.DetectedGesture as ZoomGesture);
    }
    else if (args.DetectedGesture is TapGesture)
    {
        HandleTapGesture(args.DetectedGesture as TapGesture);
    }
}

// Handle the zoom gesture
private void HandleZoomGesture(ZoomGesture gesture)
{
      Zoom += (gesture.ZoomFactor - 1) * 5;
}

//  The handling of other gestures are ingored. You can check the code of this example

 

The last thing to be noted in this section, LocalGestureEngine is not only a multi-touch handler but also a flexible framework that supports adding customized gestures. If you want to create some special gesture such as draw a line or circle, you can implement the UTable.Objects.Handlers.Gestures. GestureRecognizer interface, and add it to LocalGestureEngine by calling its RegisterGesture function

 

4. Create more uTable objects as config dialogs

In a standard Windows application, we usually use dialog to warm users or collect information from users. uTableSDK also provides a similar mechanism. It allows you to create and show a UObject within another UObject, just like the child window – window relationship. Furthermore, it allows you to mark the child UObject as modal, which means the display of the child UObject can block its parent.

In our map application, we need both types of child UObjects:

  • When user click the “Goto” menu item, a modal dialog is shown to let user input the place. When user click the OK button on the dialog, the dialog closed and UMapControl receive the result from the the dialog.
  • When user click the “Setting” menu item, a normal dialog is shown. Whenever users change the setting on that dialog, it will send an update message back to its parent, which will update the map dynamically.

The following code shows where we should add the dialog related code. When user holding on the map for a second, we create and show an element menu. The following three sub sections will demonstrate how the real work is done.

protected void HandleHoldGesture(HoldGesture gesture)
{
     // Show the menu when hold on the map for a certain time
     ShowMenu(gesture.Position, GetFingerId(gesture.Info.Contacts[0]));
}

private void ShowMenu(Point pos, int fingerId)
{
    // Create the menu
    UElementMenu menu = new UElementMenu();
    // … configure the menu
     menu.CommandReceived += new 
                                             ElementMenuCommandReceivedEventHandler(OnCommandReceived);
}

// This function will be called when we click a menu item on the pop-up menu
private void OnCommandReceived(UElementMenu sender,
                                                               ElementMenuCommandReceivedEventArgs args)
{
    switch (args.Command)
    {
        case "Goto":
        // Create a modal dialog here
            break;
        case "Setting":
            // Create a normal dialog here
            break;
    }
}

 

4.1. Create a modal UObject for “Goto Dialog”

First, let’s create a new UObject class called “GotoDialog”:

  1. Right click the project item in Solution Explorer view, select Add/New item tab
  2. In the Visual C#/UTable category, choose UTableUObject template
  3. Set name GotoDialog.xaml, and click Add button

image

 

Second, edit the SettingDialog.xml file to customize the view of the dialog:

image

 

<u:UObject x:Class="UMap.GotoDialog"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:u="clr-namespace:UTable.Objects.Controls;assembly=UTable.Core"
    Height="140" Width="300" MenuDisplay="Hide">
    <Grid>
        <TextBlock Text="Go to: " Foreground="White" FontSize="20" Height="35"  Width="69"  Margin="12,24,0,0" />
        <u:UTextBox Height="35" Margin="74,23,12,0" FontSize="20" x:Name="GotoBox"/>
        <u:UButton Height="46" Margin="12,0,0,17" Width="112" x:Name="OKButton"        
        Click="OKButton_Click">OK</u:UButton>
        <u:UButton Height="46" Margin="0,0,11,17" VerticalAlignment="Bottom" Width="112"
        x:Name="CancelButton" Click="CancelButton_Click">
    Cancel
       </u:UButton>
    </Grid>
</u:UObject>

 

Third, edit the GotoDialog.xaml.cs file to add the interaction logic:

    a) Create a model class for GotoDialog. This model stores the data behind the view

// In GotoDialog.xaml.cs

// Define the  model class for the dialog
public class GotoDialogModel
{
    public bool ShouldGoto = false;

    public String GotoPlace = String.Empty;
}

 

   b) Override the InitializeModel(object model) function

// In GotoDialog.xaml.cs

// Interaction logic for GotoDialog.xaml
public partial class GotoDialog : UObject
{
    // The dialog holds a reference to its dialog
    private GotoDialogModel model;
   
    // Initialize the model of this dialog using the one passed in UTableHelper.ShowModalObject()
    public override void InitializeModel(object model)
    {
        if (model is GotoDialogModel)
        {
            this.model = model as GotoDialogModel;
            GotoBox.Text = this.model.GotoPlace;
        }
    }
    // other codes for the GotoDialog
}

 

   c ) Implement OKButton_click and CancelButton_click function. Note that the dialog instance holds a reference to the model passed by its parent, so it directly modifies this model.

// In GotoDialog.xaml.cs

private void OKButton_Click(object sender, RoutedEventArgs e)
{
    model.GotoPlace = GotoBox.Text;
    model.ShouldGoto = true;
    Close();
}

private void CancelButton_Click(object sender, RoutedEventArgs e)
{
    model.ShouldGoto = false;
    Close();
}

 

Four, back to UMapControl class, show the Goto Dialog when clicking on the Goto menu item, and gain the result from the dialog:

// In UMapControl.cs

private void OnCommandReceived(UElementMenu sender, ElementMenuCommandReceivedEventArgs args)
{
    switch (args.Command)
    {
        case "Goto":
            {
                // Create a modal dialog
                GotoDialogModel model = new GotoDialogModel();
                // This method will not return until the model object is closed
                UTableHelper.ShowModalObject(typeof(GotoDialog), model);
                // We can get the result of the dialog by checking the model we passed to it
                if (model.ShouldGoto)
                {
         String place = model.GotoPlace;
         // Goto the place on the map …
                }
            }
            break;
            // code for handling other commands

 

4.2. Create a normal UObject for “Setting Dialog”

To create a normal child UObject is very similar with creating a modal UObject. They are only two differences:

   a) The first difference is how the object is created. As we create and show a modal object using UTableHelper.ShowModalObject function, the normal object is created by UTableHelper.CreateObject function.

// In UMapControl.cs

private void OnCommandReceived(UElementMenu sender, ElementMenuCommandReceivedEventArgs args)
{
    switch (args.Command)
    {
            case "Setting":
                {
                    // Create a normal dialog
                    // After the dialog is created, this dialog and current dialog can run in parallel,
                    // you can interact with the two objects simultaneously.
                    IObject obj = UTableHelper.CreateObject(typeof(SettingDialog),
                                UTableHelper.CurrentObject);
                    // Initialize the model of the dialog using the current map's data
                    obj.SendMessage(new InitializeModelMessage(new SettingDialogModel(this.MapType)));
                   
                    // The dialog created can send update messages to the owner object of this control;
                }
                break;
            // code for handling other commands

 

   b) The second difference is how the child UObject communicates with the parent UObject.

  1. For a modal UObject, it and its parent all holds a reference to its modal. Therefore, the modal UObject can just modify the modal when the user is interacting with it, and after it is closed, the parent UObject can get the result from the model reference.
  2. However, for a normal UObject, if we want update the parent UObject simultaneously when the user is interacting with the child UObject, there must be some mechanism for communication between UObjects. In fact, UObject class provides two pairs of methods for this:
    1. UObject.SendMessage(object msg) and UObject.OnMessageReceived(object msg) methods: these two methods are used to send message from one UObject to another UObject.
    2. UObject.QueryData(object flag) and UObject.OnDataQuerying(object flag) methods: these two methods are used to query data from one UObject to anther UObject.

             For our setting dialog, we need to change the map types dynamically when user set a new value in the map type combo box:

image

 

          Therefore, for the SettingDialog class, it should send message to its owner when the combo box value changed:

// In SettingDialog.xaml.cs

private void TypeBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if (model != null)
    {
        model.Type = (MapType)Enum.Parse(typeof(MapType),
                        MapTypes[TypeBox.SelectedIndex]);
        model.ClearAllMarks = false;
      
        // Send the model to its owner for updating
        this.Owner.SendMessage(model);
    }
}

 

       Then when the map object received the message, it can update the map control.

       Note that it’s the UMapObject received the message not the MapControl since only UObject can communicates with each other. So the UMapObject is responsible for redirecting the message to the control:

// In UMapObject.cs

public partial class UMapObject : UObject
{
        public override void OnMessageReceived(object message)
        {
            // Get the update message from the SettingDialog
            if (message is SettingDialogModel)
            {
               // Redirect the message to the UMapControl
                MainMap.UpdateMap(message as SettingDialogModel);
                return;
            }

            base.OnMessageReceived(message);
        }
}

 

5. Further reading 

This tutorial ignores many implementation details, so please check the source code of this map application to understand how the whole things work.

Download source code here: uMap

Last edited Dec 1, 2010 at 1:57 PM by anwing, version 4

Comments

No comments yet.