Quickstart: Capturing ink data (XAML)
This Quickstart walks you through capturing ink data from an input digitizer.
Note The code used in this topic is taken from a fully functional Microsoft Visual Studio 2013 C# project. While the project is not available for download, the full XAML and C# files can be found in Capturing ink data complete code.
Objective: After completing this Quickstart you will understand how to use the ink platform to detect and capture input from a pointer device (mouse, pen/stylus, or touch) in a Windows Store app using C++, C#, or Visual Basic.
Prerequisites
This topic assumes that you can create a basic Windows Store app using C++, C#, or Visual Basic. For instructions on creating your first Windows Store app, see Building your first Windows Store app using C++, C#, or Visual Basic.
We assume that you can create a basic Windows Store app using C++, C#, or Visual Basic.
To complete this tutorial, you need to:
- Install Windows 8
- Install Microsoft Visual Studio.
- Get a developer license. For instructions, see Develop using Visual Studio 2013.
- Create your first Windows Store app using C# or Visual Basic.
- Learn about events with Events and routed events overview
Instructions
1. Create a new C# "Blank App" project in Visual Studio and add a "Blank Page"
For this example, we name the new project "CaptureInkData"
Add the new page and call it "InkPage".
Change the root page for the app from "MainPage.xaml" to "InkPage.xaml". The root page is specified in the OnLaunched
event handler of "App.xaml.cs".
Here's the autogenerated Navigate method:
rootFrame.Navigate(typeof(MainPage), e.Arguments);
Here's the Navigate method using the new page:
rootFrame.Navigate(typeof(InkPage), e.Arguments);
Now, delete the autogenerated "MainPage.xaml" and "MainPage.xaml.cs" files from the project.
2. Specify required namespaces
Add the namespace references to "InkPage.xaml.cs" that are required for the ink functionality in our example. This includes "Windows.UI.Input.Inking", "Windows.UI.Xaml.Shapes", and a few other namespaces.
"Windows.UI.Input.Inking" supports the ink system and "Windows.UI.Xaml.Shapes" supports basic shape rendering, including the line used here.
Note These are in addition to the namespace references specified by default when you create a new page.
using Windows.UI.Input.Inking;
using Windows.Devices.Input;
using Windows.Storage;
using Windows.Storage.Pickers;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.UI.Input;
using Windows.UI.Xaml.Shapes;
3. Set up a drawing surface in your UI
To support ink in your Windows Store app using C++, C#, or Visual Basic, you need to add a Canvas element. A Canvas is a UI element that acts as a surface for dynamically drawing, rendering, and manipulating graphical elements in a Windows Store app using C++, C#, or Visual Basic.
Here we declare a Canvas element in XAML with a name of InkCanvas
that is used to reference the element in the code-behind.
<!-- Inking area -->
<Grid x:Name="inkPanel" Grid.Row="1" Margin="0,0,0,61">
<Canvas x:Name="InkCanvas" Background="White" Margin="62,0,62,10" />
</Grid>
4. Create an ink manager
Initialize an InkManager object that will process and manipulate the ink-related data obtained from the pointer input.
InkManager _inkManager = new Windows.UI.Input.Inking.InkManager();
5. Attach input event listeners to the drawing surface
Using the name of the Canvas element, attach the following PointerEventHandler listeners for pointer device input. (The event handler functions identified in this example are discussed later in this Quickstart.)
- PointerPressed fires when a user presses down on the digitizer surface with a pen or finger, or they click the left button on a mouse.
- PointerMoved fires when the pointer associated with the PointerPressed event moves across the Canvas.
- PointerReleased fires when the user lifts the pen or finger from the digitizer surface or releases the left mouse button.
- PointerExited fires when a pointer leaves the drawing surface. We handle this event in the same way as PointerReleased.
InkCanvas.PointerPressed += new PointerEventHandler(InkCanvas_PointerPressed);
InkCanvas.PointerMoved += new PointerEventHandler(InkCanvas_PointerMoved);
InkCanvas.PointerReleased += new PointerEventHandler(InkCanvas_PointerReleased);
InkCanvas.PointerExited += new PointerEventHandler(InkCanvas_PointerReleased);
6. Define the event handler functions
In this section, we define the event handlers to be associated with the event listeners that you added in the previous step.
PointerPressed is the event that is used to initiate ink capture.
In this example, the GetCurrentPoint method gets information about the pointer location, including the coordinates at which to begin displaying the ink data. (Capturing ink and displaying it are two separate actions.) The location coordinates are relative to the Canvas.
Next, the code checks the device type associated with the pointer input. If the pointer input is coming from the pen/stylus or the mouse (left button only), the pointer information is passed to the ink manager for processing by calling the ProcessPointerDown method.
Note This example filters the pointer input (using the Pointer.PointerDeviceType property) so that ink capture is performed for pen/stylus input and mouse input only when the left button is pressed. Touch input is reserved for manipulating the UI of the app.
This example uses some global variables, including:
_inkManager
(InkManager), which processes the ink data._penID
(uint), which stores the PointerId of the input pointer associated with this event. We'll discuss the need for this later._previousContactPt
(Point), which stores pointer data and is used in aDistance
function to calculate whether to draw a line segment (see Capturing ink data complete code).
public void InkCanvas_PointerPressed(object sender, PointerRoutedEventArgs e) { // Get information about the pointer location. PointerPoint pt = e.GetCurrentPoint(InkCanvas); _previousContactPt = pt.Position; // Accept input only from a pen or mouse with the left button pressed. PointerDeviceType pointerDevType = e.Pointer.PointerDeviceType; if (pointerDevType == PointerDeviceType.Pen || pointerDevType == PointerDeviceType.Mouse && pt.Properties.IsLeftButtonPressed) { // Pass the pointer information to the InkManager. _inkManager.ProcessPointerDown(pt); _penID = pt.PointerId; e.Handled = true; } else if (pointerDevType == PointerDeviceType.Touch) { // Process touch input } }
Ink data is captured when a PointerMoved event occurs.
In the following example, the global variable,
_penId
, is used to ensure that the PointerId for this event is identical to that of the associated PointerPressed event. If it's not, the input is ignored and no ink data is captured. This is useful, for example, to filter input from a mouse that is moved accidentally during a pen stroke.A Line object is used to draw on the Canvas as the mouse moves. The PointerMoved event is then processed through
_inkManager
by passing the pointer data (GetCurrentPoint) of the event to the ProcessPointerUpdate method.Note
STROKETHICKNESS
(const double) is initialized to an arbitrary value of 5 when the app is launched.public void InkCanvas_PointerMoved(object sender, PointerRoutedEventArgs e) { if (e.Pointer.PointerId == _penID) { PointerPoint pt = e.GetCurrentPoint(InkCanvas); // Render a red line on the canvas as the pointer moves. // Distance() is an application-defined function that tests // whether the pointer has moved far enough to justify // drawing a new line. Point currentContactPt = pt.Position; if (Distance(currentContactPt, _previousContactPt) > 2) { Line line = new Line() { X1 = _previousContactPt.X, Y1 = _previousContactPt.Y, X2 = currentContactPt.X, Y2 = currentContactPt.Y, StrokeThickness = STROKETHICKNESS, Stroke = new SolidColorBrush(Windows.UI.Colors.Red) }; _previousContactPt = currentContactPt; // Draw the line on the canvas by adding the Line object as // a child of the Canvas object. InkCanvas.Children.Add(line); // Pass the pointer information to the InkManager. _inkManager.ProcessPointerUpdate(pt); } } else if (e.Pointer.PointerId == _touchID) { // Process touch input } e.Handled = true; }
Ink data capture is complete when a PointerReleased event occurs.
As in the previous example, this function uses the global variable,
_penId
, to ensure that the PointerId for this event is identical to that of the associated PointerPressed and PointerMoved events. If it's not, the input is ignored and no ink data is captured.The PointerReleased event is processed through
_inkManager
by passing the pointer data (GetCurrentPoint) of the event to the ProcessPointerUp method.Note The
RenderAllStrokes
function is explained later.public void InkCanvas_PointerReleased(object sender, PointerRoutedEventArgs e) { if (e.Pointer.PointerId == _penID) { PointerPoint pt = e.GetCurrentPoint(InkCanvas); // Pass the pointer information to the InkManager. _inkManager.ProcessPointerUp(pt); } else if (e.Pointer.PointerId == _touchID) { // Process touch input } _touchID = 0; _penID = 0; // Call an application-defined function to render the ink strokes. RenderAllStrokes(); e.Handled = true; }
7. Render the ink stroke
The RenderAllStrokes
function in this example is optional and is called to process the ink data and render the raw stroke segments on the Canvas element as smooth curves.
Important For performance reasons, we do not recommend this XAML-based approach to ink rendering. We include it for demonstration purposes only. See the Inking with XAML and SwapChainPanel sample for an example of how to render ink strokes using XAML and DirectX instead.
private void RenderAllStrokes()
{
// Clear the canvas.
InkCanvas.Children.Clear();
// Get the InkStroke objects.
IReadOnlyList<InkStroke> inkStrokes = _inkManager.GetStrokes();
// Process each stroke.
foreach (InkStroke inkStroke in inkStrokes)
{
PathGeometry pathGeometry = new PathGeometry();
PathFigureCollection pathFigures = new PathFigureCollection();
PathFigure pathFigure = new PathFigure();
PathSegmentCollection pathSegments = new PathSegmentCollection();
// Create a path and define its attributes.
Windows.UI.Xaml.Shapes.Path path = new Windows.UI.Xaml.Shapes.Path();
path.Stroke = new SolidColorBrush(Colors.Red);
path.StrokeThickness = STROKETHICKNESS;
// Get the stroke segments.
IReadOnlyList<InkStrokeRenderingSegment> segments;
segments = inkStroke.GetRenderingSegments();
// Process each stroke segment.
bool first = true;
foreach (InkStrokeRenderingSegment segment in segments)
{
// The first segment is the starting point for the path.
if (first)
{
pathFigure.StartPoint = segment.BezierControlPoint1;
first = false;
}
// Copy each ink segment into a bezier segment.
BezierSegment bezSegment = new BezierSegment();
bezSegment.Point1 = segment.BezierControlPoint1;
bezSegment.Point2 = segment.BezierControlPoint2;
bezSegment.Point3 = segment.Position;
// Add the bezier segment to the path.
pathSegments.Add(bezSegment);
}
// Build the path geometerty object.
pathFigure.Segments = pathSegments;
pathFigures.Add(pathFigure);
pathGeometry.Figures = pathFigures;
// Assign the path geometry object as the path data.
path.Data = pathGeometry;
// Render the path by adding it as a child of the Canvas object.
InkCanvas.Children.Add(path);
}
}
Summary
You now have a basic idea of how to capture ink data with your app.
Note
The full XAML and C# files demonstrated in this quickstart can be found in Capturing ink data complete code.
Related topics
Conceptual
Responding to pen and stylus interactions
Reference
Samples
Input: Device capabilities sample