How to: Analyze Ink with Analysis Hints


An T:System.Windows.Ink.AnalysisHintNode provides a hint for the T:System.Windows.Ink.InkAnalyzer to which it is attached. The hint applies to the area specified by the P:System.Windows.Ink.ContextNode.Location property of the T:System.Windows.Ink.AnalysisHintNode and provides extra context to the ink analyzer to improve recognition accuracy. The T:System.Windows.Ink.InkAnalyzer applies this context information when analyzing ink obtained from within the hint's area.

The following example is an application that uses multiple T:System.Windows.Ink.AnalysisHintNode objects on a form that accepts ink input. The application uses the P:System.Windows.Ink.AnalysisHintNode.Factoid property to provide context information for each entry on the form. The application uses background analysis to analyze the ink and clears the form of all ink five seconds after the user stops adding ink.

<Window x:Class="FormAnalyzer"
	<StackPanel Orientation="Vertical">
    <InkCanvas Name="xaml_writingCanvas" Height="500" Width="840" 
               StrokeCollected="RestartAnalysis" >
					<Style TargetType="{x:Type Label}">
						<Setter Property="FontSize" Value="20"/>
						<Setter Property="FontFamily" Value="Arial"/>

					<Style TargetType="{x:Type TextBlock}">
						<Setter Property="FontSize" Value="18"/>
						<Setter Property="VerticalAlignment" Value="Center"/>
					<ColumnDefinition Width="100"></ColumnDefinition>
					<ColumnDefinition Width="160"></ColumnDefinition>
					<ColumnDefinition Width="160"></ColumnDefinition>
					<ColumnDefinition Width="100"></ColumnDefinition>
					<ColumnDefinition Width="160"></ColumnDefinition>
					<ColumnDefinition Width="160"></ColumnDefinition>

					<RowDefinition Height="100"></RowDefinition>
					<RowDefinition Height="100"></RowDefinition>
					<RowDefinition Height="100"></RowDefinition>
					<RowDefinition Height="100"></RowDefinition>
					<RowDefinition Height="100"></RowDefinition>

				<Label Grid.Row="0" Grid.Column="0">Title</Label>
				<Label Grid.Row="1" Grid.Column="0">Director</Label>
				<Label Grid.Row="2" Grid.Column="0">Starring</Label>
				<Label Grid.Row="3" Grid.Column="0">Rating</Label>
				<Label Grid.Row="3" Grid.Column="3">Year</Label>
				<Label Grid.Row="4" Grid.Column="0">Genre</Label>

				<TextBlock Name="xaml_blockTitle" 
                   Grid.Row="0" Grid.Column="1" 
        <TextBlock Name="xaml_blockDirector"
                   Grid.Row="1" Grid.Column="1" 
				<TextBlock Name="xaml_blockStarring"
                   Grid.Row="2" Grid.Column="1" 
				<TextBlock Name="xaml_blockRating" 
                   Grid.Row="3" Grid.Column="1" 
				<TextBlock Name="xaml_blockYear" 
                   Grid.Row="3" Grid.Column="4" 
				<TextBlock Name="xaml_blockGenre" 
                   Grid.Row="4" Grid.Column="1" 

				<Line Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="6" 
              StrokeThickness="2" Stroke="Black" 
              X1="0" Y1="100" X2="840" Y2="100" />
				<Line Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="6" 
              StrokeThickness="2" Stroke="Black" 
              X1="0" Y1="100" X2="840" Y2="100" />
				<Line Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="6" 
              StrokeThickness="2" Stroke="Black" 
              X1="0" Y1="100" X2="840" Y2="100" />
				<Line Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="6" 
              StrokeThickness="2" Stroke="Black" 
              X1="0" Y1="100" X2="840" Y2="100" />
				<Line Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="6" 
              StrokeThickness="2" Stroke="Black" 
              X1="420" Y1="0" X2="420" Y2="100" />

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Ink;
using System.Windows.Threading;

public partial class FormAnalyzer : Window
    private InkAnalyzer analyzer;

    private AnalysisHintNode hintNodeTitle;
    private AnalysisHintNode hintNodeDirector;
    private AnalysisHintNode hintNodeStarring;
    private AnalysisHintNode hintNodeRating;
    private AnalysisHintNode hintNodeYear;
    private AnalysisHintNode hintNodeGenre;

    // Timer that raises an event to
    // clear the InkCanvas.
    private DispatcherTimer strokeRemovalTimer;

    private const int CLEAR_STROKES_DELAY = 5;

    public FormAnalyzer()

    protected override void OnContentRendered(EventArgs e)

        // Initialize the Analyzer.
        analyzer = new InkAnalyzer();
        analyzer.ResultsUpdated += 
            new ResultsUpdatedEventHandler(analyzer_ResultsUpdated);

        // Add analysis hints for each form area.
        // Use the absolute Width and Height of the Grid's
        // RowDefinition and ColumnDefinition properties defined in XAML, 
        // to calculate the bounds of the AnalysisHintNode objects.
        hintNodeTitle = analyzer.CreateAnalysisHint(
                                    new Rect(100, 0, 740, 100));
        hintNodeDirector = analyzer.CreateAnalysisHint(
                                    new Rect(100, 100, 740, 100));
        hintNodeStarring = analyzer.CreateAnalysisHint(
                                    new Rect(100, 200, 740, 100));
        hintNodeRating = analyzer.CreateAnalysisHint(
                                    new Rect(100, 300, 320, 100));
        hintNodeYear = analyzer.CreateAnalysisHint(
                                    new Rect(520, 300, 320, 100));
        hintNodeGenre = analyzer.CreateAnalysisHint(
                                    new Rect(100, 400, 740, 100));

        //Set the factoids on the hints.
        hintNodeTitle.Factoid = "(!IS_DEFAULT)";
        hintNodeDirector.Factoid = "(!IS_PERSONALNAME_FULLNAME)";
        hintNodeStarring.Factoid = "(!IS_PERSONALNAME_FULLNAME)";
        hintNodeRating.Factoid = "(!IS_DEFAULT)";
        hintNodeYear.Factoid = "(!IS_DATE_YEAR)";
        hintNodeGenre.Factoid = "(!IS_DEFAULT)";

    /// <summary>
    /// InkCanvas.StrokeCollected event handler.  Begins 
    /// ink analysis and starts the timer to clear the strokes.
    /// If five seconds pass without a Stroke being added,
    /// the strokes on the InkCanvas will be cleared.
    /// </summary>
    /// <par    am name="sender">InkCanvas that raises the
    /// StrokeCollected event.</param>
    /// <param name="args">Contains the event data.</param>
    private void RestartAnalysis(object sender,
                    InkCanvasStrokeCollectedEventArgs args)

        // If strokeRemovalTimer is enabled, stop it.
        if (strokeRemovalTimer != null && strokeRemovalTimer.IsEnabled)

        // Restart the timer to clear the strokes in five seconds
        strokeRemovalTimer = new DispatcherTimer(

        // Add the new stroke to the InkAnalyzer and
        // begin background analysis.

    /// <summary>
    /// Analyzer.ResultsUpdated event handler.
    /// </summary>
    /// <param name="sender">InkAnalyzer that raises the
    /// event.</param>
    /// <param name="e">Event data</param>
    /// <remarks>This method checks each AnalysisHint for 
    /// analyzed ink and then populated the TextBlock that 
    /// corresponds to the area on the form.</remarks>
    void analyzer_ResultsUpdated(object sender, ResultsUpdatedEventArgs e)
        string recoText;

        recoText = hintNodeTitle.GetRecognizedString();
        if (recoText != "") xaml_blockTitle.Text = recoText;

        recoText = hintNodeDirector.GetRecognizedString();
        if (recoText != "") xaml_blockDirector.Text = recoText;

        recoText = hintNodeStarring.GetRecognizedString();
        if (recoText != "") xaml_blockStarring.Text = recoText;

        recoText = hintNodeRating.GetRecognizedString();
        if (recoText != "") xaml_blockRating.Text = recoText;

        recoText = hintNodeYear.GetRecognizedString();
        if (recoText != "") xaml_blockYear.Text = recoText;

        recoText = hintNodeGenre.GetRecognizedString();
        if (recoText != "") xaml_blockGenre.Text = recoText;

    //Clear the canvas, but leave the current strokes in the analyzer.
    private void ClearCanvas(object sender, EventArgs args)