The Lesson of GetIntermediatePoints
November 28, 2012
New York, NY
The following code shows some simple finger-tracking (or mouse tracking or pen tracking) code for Windows 8. The MainPage.xaml file instantiates a Grid named contentGrid, and the code displays pointer input with a Polyline element. The Polyline element is created during the OnPointerPressed override, a Point is added to this Polyline during the OnPointerMoved override, and it's completed in OnPointerReleased. (For more details, consult the Touch chapter of my book Programming Windows, 6th edition.)
This program is called GetCurrentPoint because that's the method it uses to obtain the touch points to construct the polyline:
public sealed partial class MainPage : Page
{
Dictionary<uint, Polyline> pointerDictionary = new Dictionary<uint, Polyline>();
public MainPage()
{
this.InitializeComponent();
}
protected override void OnPointerPressed(PointerRoutedEventArgs args)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
Point point = args.GetCurrentPoint(this).Position;
// Create Polyline
Polyline polyline = new Polyline
{
Stroke = this.Resources["ApplicationForegroundThemeBrush"] as Brush,
StrokeThickness = 4
};
polyline.Points.Add(point);
// Add to Grid and dictionary
contentGrid.Children.Add(polyline);
pointerDictionary.Add(id, polyline);
base.OnPointerPressed(args);
}
protected override void OnPointerMoved(PointerRoutedEventArgs args)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
Point point = args.GetCurrentPoint(this).Position;
// If ID is in dictionary, add the point to the Polyline
if (pointerDictionary.ContainsKey(id))
pointerDictionary[id].Points.Add(point);
base.OnPointerMoved(args);
}
protected override void OnPointerReleased(PointerRoutedEventArgs args)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
// If ID is in dictionary, remove it
if (pointerDictionary.ContainsKey(id))
pointerDictionary.Remove(id);
base.OnPointerReleased(args);
}
}
I said this code is "simple." One of the "simple" things about it is that it doesn't capture the pointer, and that means that you can crash the program using the mouse. Here's a screenshot when running the program on the Microsoft Surface:
There is an alternative to calling GetCurrentPoint during the OnPointerMoved override. You can instead call GetIntermediatePoints. Whereas GetCurrentPoint returns a single PointerPoint object, GetIntermediatePoints returns an IList<PointerPoint> object. This is a collection of points that have been accumulated since the last PointerMoved event.
Here's an alternative OnPointerMoved override in a project called GetIntermediatePoints that shows how this alternative method might be used:
protected override void OnPointerMoved(PointerRoutedEventArgs args)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
// If ID is in dictionary, add the points to the Polyline
if (pointerDictionary.ContainsKey(id))
{
foreach (PointerPoint pointerPoint in args.GetIntermediatePoints(this))
{
pointerDictionary[id].Points.Add(pointerPoint.Position);
}
}
base.OnPointerMoved(args);
}
Notice the foreach statement that enumerates over the object returned from GetIntermediatePoints. Virtually any programmer looking at the documentation of GetIntermediatePoints would write very similar code. Indeed, I have seen some sample code that used a foreach with GetIntermediatePoints just as I've done. In theory, this approach provides more points and hence a better rendition of the touch input.
However, when I was working with Windows 8 on the so-called Build Tablet distributed to the attendees at the 2011 Build conference (a variation of the Samsung 700T), I couldn't see any difference between the two approaches. GetIntermediatePoints always returned a collection of exactly one PointerPoint object, which was the same as the PointerPoint object returned from GetCurrentPoint. Under the philosophy that you should use the simplest code whenever possible — it's a variation of Occam's Razor, I believe — I decided to use GetCurrentPoint in my finger-tracking examples rather than GetIntermediatePoints.
When I got my hands on a Microsoft Surface, I re-examined the issue, and I discovered that on the Surface, GetIntermediatePoints actually does return a collection that often contains more than one PointerPoint object, and suddenly the use of that method became justified
However, when you run that GetIntermediatePoints program on the Microsoft Surface and quickly try to draw an oval with a finger, this is what you get:
That doesn't look right at all! Fortunately, there is a very simple explanation: On the Microsoft Surface, GetIntermediatePoints does indeed return a collection with multiple PointerPoint objects, but they are in reverse order.
Of course, once you know that the collection is in reverse order, it's easy to fix. The GetIntermediatePointsReversed program uses the LINQ Reverse operator to reverse the collection, but you can use a simple for loop instead:
protected override void OnPointerMoved(PointerRoutedEventArgs args)
{
// Get information from event arguments
uint id = args.Pointer.PointerId;
// If ID is in dictionary, add the points to the Polyline
if (pointerDictionary.ContainsKey(id))
{
foreach (PointerPoint pointerPoint in args.GetIntermediatePoints(this).Reverse())
{
pointerDictionary[id].Points.Add(pointerPoint.Position);
}
}
base.OnPointerMoved(args);
}
With this program, quickly drawing an oval results in something much more reasonable:
You can download all three projects here
I feel vindicated that my original instinct was correct: I resisted using GetIntermediatePoints when my experience revealed that the collection never had more than one point. Only when I discovered a case where GetIntermediatePoints returned multiple points did it become possible to understand how it worked, and it turned out that it worked contrary to intuition and expectations.
The lesson is: Don't use the fancy stuff unless you can test the fancy stuff.
Programming Windows, 6th Edition
For one price, get the Release Preview ebook |