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:

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 a Distance 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.

 

Conceptual

Responding to pen and stylus interactions

Reference

Windows.UI.Input.Inking

Canvas class

InkManager class

Samples

Input: Device capabilities sample

Inking with XAML and SwapChainPanel sample

Input: Simplified ink sample