Surface Developer Tip: Track extra input data
I’d like to discuss a facet of the Surface SDK which is not very well known but widely used internally for all kinds of things. Imagine you are tasked with determining the traveled by the mouse between when the left button is pressed and when it’s released. Pretty straightforward...
Point initialPosition; protected override void OnMouseDown(MouseButtonEventArgs e) { initialPosition = e.GetPosition(this); } protected override void OnMouseUp(MouseButtonEventArgs e) { Point currentPosition = e.GetPosition(this); double distance = (currentPosition - initialPosition).Length; Debug.WriteLine( distance); }
Now let’s look at porting this to work with Surface input. We’ll just replace the OnMouseDown & OnMouseUp with OnContactDown & OnContactUp. This compiles fine and runs. Touch with one finger and it will even do the right thing. We’re not done though… what happens when you touch with two fingers? We only have one initialPosition field so our calculation in OnContactUp is really just figuring out the distance between the finger being lifted and where the most recent finger went down… not the distance traveled by that particular contact.
There are a number of ways you can solve this, but we put some helper methods in the Surface SDK specifically to simplify scenarios like this: Contact.SetUserData( key, value) and Contact.GetUserData( key ). Modifying our sample app to leverage this is easy…
private static readonly object initialPositionKey = new Object(); protected override void OnContactDown(ContactEventArgs e) { // ... Point currentPosition = e.GetPosition(this); e.Contact.SetUserData(initialPositionKey, currentPosition); } protected override void OnContactUp(ContactEventArgs e) { // ... Point currentPosition = e.GetPosition(this); Point initialPosition = (Point)e.Contact.GetUserData(initialPositionKey); double distance = (currentPosition - initialPosition).Length; Debug.WriteLine(string.Format("Contact #{0}: {1}", e.Contact.Id, distance)); }
First, we’ve replaced the initialPosition property with a key. We’ll use this key to get/set the initial position on individual contact. This can be anything, it just needs to be unique from other keys used within the app.
Second, in OnContactDown we’re now storing the initial position on a per-contact basis by using Contact.SetUserData. We don’t put any constraints on the data stored using this mechanism – it can be of any type and any size.
Finally, in OnContactUp when we need to get at the initial position, we simply pull it back out from the per-contact storage with Contact.GetUserData and cast it back to a Point.
Not only does this keep your code nice and simple by not having to create a bunch of Dictionary objects on your own, it also prevents against memory leaks because we automatically clean up all the per-contact data when contacts go away (done after the app has had a chance to process the ContactUp event).
-Robert