The stylus is mightier than the sword - Inking in your UWP

One of my favorite features of Edge and OneNote is inking. I love the fact that I can annotate web pages and images. In this blog post I want to show you a step-by-step on how you too can achieve the same in your UWP apps.

First let's start by showing you the XAML I need to create an Ink Canvas.

<Grid>
    <InkCanvas x:Name="InkCanvas" />
</Grid>

And that's it! No really, if you start scribbling on your touch screen - with a stylus, not a real pen :) you will see that your UWP app will show pen strokes.

What about if your users don't have a stylus, or a even a touch screen? Well you may want them to use either their finger or a mouse, so in your MainPage.xaml.cs override the OnNavigatedTo with the following code.

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    this.InkCanvas.InkPresenter.InputDeviceTypes = 
        CoreInputDeviceTypes.Pen | 
        CoreInputDeviceTypes.Touch | 
        CoreInputDeviceTypes.Mouse;
}

Now let's create an app bar so users can access our add additional features. In the MainPage.xaml add the following XAML before the root Grid.

<Page.TopAppBar>
    <AppBar IsOpen="True" Background="#E84A5F">
        <StackPanel Orientation="Horizontal">
            <AppBarButton x:Name="HighlighterButtoner" 
                          Icon="Edit" Label="Highlighter" 
                          Click="HighlighterButtoner_Click" />
            <AppBarButton x:Name="PenButton" 
                          Icon="Edit" Label="Pen" 
                          Click="PenButton_Click" />
            <AppBarButton x:Name="EraserButton" 
                          Icon="Delete" Label="Eraser" 
                          Click="EraserButton_Click" />
            <AppBarButton x:Name="RecognizeButton" 
                          Icon="Font" Label="Recognize" 
                          Click="RecognizeButton_Click" />
            <AppBarButton x:Name="SaveButton" 
                          Icon="Save" Label="Save" 
                          Click="SaveButton_Click" />
            <AppBarButton x:Name="LoadButton" 
                          Icon="OpenFile" Label="Load" 
                          Click="LoadButton_Click" />
        </StackPanel>
    </AppBar>
</Page.TopAppBar>

You can see that I use an AppBar and then add a series of AppBarButtons. The AppBarButtons conveniently have an Icon and Label property so it saves a lot of time, the result will look like this:

Each AppBarButton has a Click event handler, so let's go through each one from left to right.

Highlighter

private void HighlighterButtoner_Click(object sender, RoutedEventArgs e)
{
    var drawingAttributes = new InkDrawingAttributes
    {
        DrawAsHighlighter = true,
        PenTip = PenTipShape.Rectangle,
        Size = new Size(4, 10),
        Color = Colors.Yellow
    };

    this.InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}

To change the pen style simply create an InkDrawingAttributes, set the various properties on it and then update the default by calling UpdateDefaultDrawingAttributes on the InkPresenter belonging to the InkCanvas.

For the Highlighter, one of the important properties that I need to set is the DrawAsHighlighter=true, this makes sure that I get a slight transparency when I color over objects in the canvas. If you get a chance then you should play around with the properties, you can create some funky styles, like a calligraphy pen.

highlighter.PNG

Pen

private void PenButton_Click(object sender, RoutedEventArgs e)
{
    var drawingAttributes = new InkDrawingAttributes
    {
        DrawAsHighlighter = false
    };

    this.InkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttributes);
}

Similar to the highlighter, except this time I set the DrawAsHighlighter=false.

Eraser

private void EraserButton_Click(object sender, RoutedEventArgs e)
{
    this.InkCanvas.InkPresenter.InputProcessingConfiguration.Mode = 
                InkInputProcessingMode.Erasing;
}

Erasing is slightly different from the pen. I set the input processing configuration mode to InkInputProcessingMode.Erasing; and now when touch elements onto he canvas they disappear.

Recognize

private async void RecognizeButton_Click(object sender, RoutedEventArgs e)
{
    var inkRecognizer = new InkRecognizerContainer();
    if (null != inkRecognizer)
    {
        var recognitionResults = 
                    await inkRecognizer.RecognizeAsync(
                        this.InkCanvas.InkPresenter.StrokeContainer, 
                        InkRecognitionTarget.All);

        string recognizedText = 
                    string.Join(" ", 
                    recognitionResults.Select(i => i.GetTextCandidates()[0]));

        var messageDialog = new MessageDialog(recognizedText);
        await messageDialog.ShowAsync();
    }
}

Even if your handwriting is as poor as mine, the ink recognizer does a great job of understanding what has been written. The first thing I do is create an InkRecognizerContainer and call the RecognizeAsync method passing in the StrokeContainer. You can set it to recognize the selected items in the StrokeContainer, but the purpose of this post I just say all.

As there could be many words on the canvas and so the results from RecognizeAsync returns a collection of InkRecognitionResults, which I just join to form a sentence. I call the GetTextCandidates method and select the first item from the list of candidates, there are times where you may have many candidates - especially if the recognizer had a tough time trying to understand the text.

Save

private async void SaveButton_Click(object sender, RoutedEventArgs e)
{
    var fileSave = new FileSavePicker();
    fileSave.FileTypeChoices.Add(
        "Gif with embedded ISF", new List { ".gif" }
        );

    var storageFile = await fileSave.PickSaveFileAsync();

    if (storageFile != null)
    {
        using (var stream = await storageFile.OpenAsync(FileAccessMode.ReadWrite))
        {
            await this.InkCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
        }
    }
}

One of the coolest features of the ink canvas is the ability to save and load the strokes in a gif with embedded ISF. The code simply saves the contents of the StrokeContainer to storage file.

Load

private async void LoadButton_Click(object sender, RoutedEventArgs e)
{
    var fileOpen = new FileOpenPicker();
    fileOpen.FileTypeFilter.Add(".gif");

    var storageFile = await fileOpen.PickSingleFileAsync();

    if (storageFile != null)
    {
        using (var stream = await storageFile.OpenReadAsync())
        {
            this.InkCanvas.InkPresenter.StrokeContainer.Clear();
            await this.InkCanvas.InkPresenter.StrokeContainer.LoadAsync(stream);
        }
    }
}

Of course there is no point being able to save the ink strokes if you aren't able to load them so above is the code that.

In this short blog post I covered how to add inking to your application, change the pen styles, erase ink, recognize ink, load and save ink.

The code for this can be found at: https://github.com/shenchauhan/blog/tree/master/Inking

Channel 9 video: https://channel9.msdn.com/Shows/Inside-Windows-Platform/Leverage-Inking-in-your-UWP-Apps

Happy coding!