Using Recognition Alternates

 

Peter Gruenbaum
Microsoft Corporation

May 2004

Applies to:
   Microsoft® Tablet PC Platform SDK
   Ink
   Recognition

Summary: This article describes how to use recognition alternates. An application that runs on Microsoft Windows XP Tablet PC Edition creates recognition alternates when that application converts handwriting into text. This article describes various ways to present recognition alternates so that users can specify the correct text for handwriting. These descriptions and examples, in C++, C#, and Microsoft Visual Basic .NET, use the Microsoft Tablet PC Platform SDK version 1.7 API, currently in Beta, although nearly all the content applies to version 1.5 as well. Readers should be familiar with the Microsoft Tablet PC Platform SDK and either managed code or Microsoft Foundation Classes. For information about joining the Tablet PC Beta partners, send an e-mail to tabbeta@microsoft.com. (21 printed pages)

Download the sample code.

Contents

Introduction
How to Use the Example
Finding and Displaying the Top Alternate
Finding and Displaying the Other Alternates
Finding Confidence Level for Alternates
Displaying Strokes for Alternates
Modifying the Top Alternate
Conclusions

Introduction

Because there are so many variations between individuals' handwriting, handwriting recognizers may convert handwriting into text that is different than what the user intended. When a Recognizer performs recognition on a Strokes collection, the Recognizer finds the most likely set of words that the handwriting represents. In addition, the Recognizer also finds sets of alternative recognition matches, which are stored in a RecognitionAlternates collection. To allow a user to take advantage of recognition alternates, you must create a user interface that allows the user to select the correct RecognitionAlternate. Choosing a RecognitionAlternate does not have to be a final choice nor need be time-sensitive; you can store RecognitionAlternates so that the user can select or reselect alternates at any time.

This article makes heavy use of an example project that creates a dialog similar to the one in Windows Journal, found when you select text and then choose Convert Handwriting to Text on the Actions menu. The dialog allows you to select words, see the strokes that correspond to those words, and then choose recognition alternates from a list box.

This article discusses how to:

  • Display the top alternate for a recognition result.
  • Display which words are recognized with lower confidence.
  • Modify the top alternate with another alternate.
  • Show which strokes correspond to selected text.

This article does not discuss:

  • Details about how to perform recognition.
  • How to use various recognizers.

How to Use the Example

You need to run the example on a computer that has recognizers installed, such as a Tablet PC. The example also requires the Microsoft® Tablet PC Platform SDK version 1.7. Example projects are provided in Microsoft® Visual C#, Microsoft® Visual Basic® .NET, and Microsoft® Visual C++ using Microsoft® Foundation Classes (MFC).

Open the project, build the project, and then run the project. Write several words in the white panel, and then click Convert to Text. (In the C++ example, use the menu item.) This opens the TextCorrectionDialog, which displays the top alternate. Words for which the recognizer does not have strong confidence are displayed in bold. When you click a word or select several words, the example displays recognition alternates in the list box. The example also displays the ink that corresponds to the word or words you selected. Select an alternate from the list, and click Change to use that alternate. Click OK when you are satisfied with the text. The example displays this text at the bottom of the window, and the ink is cleared (In the C++ example, the text displays in the status bar.).

Finding and Displaying the Top Alternate

The top alternate is the set of characters that the Recognizer selects as the conversion from handwriting to text that is most likely to be accurate. In the constructor for the TextCorrectionDialog, the default recognizer is used to create a RecognizerContext, as shown in the following code. (You can also do this by using the RecognizerContext constructor, which uses the default recognizer.)

Note   If you expect to create multiple RecognizerContext objects, you may improve performance by maintaining the Recognizer that you are using.

C#

Recognizers allRecognizers = new Recognizers();
this.recognizerContext = 
    allRecognizers.GetDefaultRecognizer().CreateRecognizerContext();

Visual Basic .NET

Dim allRecognizers As New Recognizers
Me.TheRecognizerContext = _
    allRecognizers.GetDefaultRecognizer().CreateRecognizerContext()

C++

In the C++ code, we reference the RecognizerContext by using a smart pointer of type CComPtr<IInkRecognizerContext> named m_spRecognizerContext. Using the CoCreateInstance function creates the RecognizerContext from the default recognizer.

HRESULT hr;
// Create a recognizer context that uses the default recognizer.
// The single context will be used for all the recognition.
hr = m_spRecognizerContext.CoCreateInstance(CLSID_InkRecognizerContext);

When you set the TextCorrectionDialog.StrokesToConvert property (For the C++ example, this is when you call the CTextCorrectionDialog::SetStrokesToConvert method.), the TextCorrectionDialog.RecognizeStrokes method is called. This method performs recognition on the strokes by using the RecognizerContext.Recognize method, and then puts the string representation of the top alternate into the text box by using RecognitionResult.TopString. This operation is shown in the following code.

Note   As an alternate method, you can also use RecognizerContext.BackgroundRecognize to recognize the text asynchronously.

Note   If you expect to collect a lot of strokes, you may improve performance by adding the strokes to the RecognizerContext as they are created, rather than all at once before recognition.

C#

// First end ink input so that no further ink can be added 
// during recognition.
this.recognizerContext.EndInkInput();

// Recognize strokes
this.recognizerContext.Strokes = this.strokesToConvert;

RecognitionStatus status;
this.recognitionResult = this.recognizerContext.Recognize(out status);

if (status == RecognitionStatus.NoError)
{
    // Put top alternate into converted text box
    this.richTextBoxConverted.Text = this.recognitionResult.TopString;
...
}

Visual Basic .NET

' First end ink input so that no further ink can be added 
' during recognition.
Me.TheRecognizerContext.EndInkInput()

' Recognize strokes
Me.TheRecognizerContext.Strokes = Me.StrokesToConvert
Dim status As RecognitionStatus
Me.TheRecognitionResult = Me.TheRecognizerContext.Recognize(status)

If status = RecognitionStatus.NoError Then
    ' Put top alternate into converted text box
    Me.RichTextBoxConverted.Text = Me.TheRecognitionResult.TopString
...
End If

C++

In this example, m_firstTopAlternate is a CString object that is shown in the CRichEditCtrl box when the dialog first displays.

HRESULT result;
result = m_pRecognizerContext->putref_Strokes(m_pStrokesToConvert);
if (SUCCEEDED(result))
{
    InkRecognitionStatus status;
    result = m_pRecognizerContext->Recognize(&status, &m_pRecognitionResult);

    if (SUCCEEDED(result) && status == IRS_NoError)
    {
        // Put top alternate into edit control
        BSTR convertedBSTR;
        result = m_pRecognitionResult->get_TopString(&convertedBSTR);
        if (SUCCEEDED(result))
        {
            // Convert BSTR to CString 
            CString convertedString(convertedBSTR);
            m_firstTopAlternate = convertedString;
        }
    }
}

Finding and Displaying Other Alternates

The RecognizeStrokes method also calls the ShowAlternates method, which populates the list box with alternates. Within the ShowAlternates method, if no characters have been selected, then the SelectNearestWord method is called. The SelectNearestWord method selects the word closest to the cursor. Then, the RecognitionResult.GetAlternatesFromSelection method is called to get the alternates for the words that are selected. You use the RichTextBox.SelectionStart and RichTextBox.SelectionLength properties when specifying the selection start and length for finding the alternates. These alternates are then used to populate the list box, as shown in the following code.

Note   In the managed examples, when you add the RecognitionAlternate to the ListBox.Items collection the result of RecognitionAlternate.ToString is displayed in the ListBox.

Note The top alternate displays first within the list box, because the top alternate is always first in the list of recognition alternates. If you choose not to display the top alternate, skip the first alternate in the collection.

C#

// Find alternates for the selection by using the text box's selection 
// start and length.
RecognitionAlternates alternates =
    this.recognitionResult.GetAlternatesFromSelection(selectionStart, 
    selectionLength);

// Populate list box
this.listBoxAlternatives.Items.Clear();
// Note: if we wanted not to show the top alternate,
// we could skip the first alternate in the collection.
foreach (RecognitionAlternate alternate in alternates)
{
    this.listBoxAlternatives.Items.Add(alternate);
}

Visual Basic .NET

' Find alternates for the selection by using the text box's selection 
' start and length
Dim alternates As RecognitionAlternates = _
    Me.TheRecognitionResult.GetAlternatesFromSelection(selectionStart, _
    selectionLength)

' Populate list box
Me.ListBoxAlternatives.Items.Clear()
Dim alternate As RecognitionAlternate
' Note: if we wanted not to show the top alternate,
' we could skip the first alternate in the collection.
For Each alternate In alternates
    Me.ListBoxAlternatives.Items.Add(alternate)
Next alternate

C++

In this example, m_spAlternates is of type CComPtr<IInkRecognitionAlternates> and m_spRecognitionResult is of type CComPtr<IInkRecognitionResult>.

// Find alternates for the selection by using the text box's selection 
// start and length
m_spAlternates = NULL;
hr = m_spRecognitionResult->AlternatesFromSelection(
     selectionStart, selectionLength, 10, &m_spAlternates);

// Populate list box
m_listBoxAlternates.ResetContent();
long nAlternates;
hr =  m_spAlternates->get_Count(&nAlternates);
if (SUCCEEDED(hr))
{
    // Note: if we wanted to not show the top alternate,
    // we could start this loop at i = 1.
    for (int i = 0; i < nAlternates; i++)
    {
        CComPtr<IInkRecognitionAlternate> spAlternate;
        m_spAlternates->Item(i, &spAlternate);
        CComBSTR alternateBSTR;
        hr = spAlternate->get_String(&alternateBSTR);
        if (SUCCEEDED(hr))
        {
            CString alternateString(alternateBSTR);
            m_listBoxAlternates.AddString(alternateString);
        }
    }
}

Finding Confidence Level for Alternates

Some recognizers provide a confidence level for each alternate. You can use the confidence level to let the user know when an alternate's confidence is not high. First, you need to determine if the Recognizer supports confidence. You can do this by examining the Recognizer.SupportedProperties property, as shown in the following code. The example sets a Boolean member isConfidenceSupported (m_isConfidenceSupported in C++) to true if the default Recognizer supports confidence levels. The isConfidenceSupported variable is initialized to false.

C#

foreach (Guid supportedProperty in 
    allRecognizers.GetDefaultRecognizer().SupportedProperties)
{
    if (supportedProperty == RecognitionProperty.ConfidenceLevel)
    {
        this.isConfidenceSupported = true;
        break;
    }
}

Visual Basic .NET

Dim supportedProperty As Guid
For Each supportedProperty In _
    allRecognizers.GetDefaultRecognizer().SupportedProperties
    If supportedProperty.Equals(RecognitionProperty.ConfidenceLevel) Then
        Me.IsConfidenceSupported = True
        Exit For
    End If
Next

C++

m_isConfidenceSupported = false;
CComPtr<IInkRecognizer> spRecognizer;
hr = m_spRecognizerContext->get_Recognizer(&spRecognizer);
if (SUCCEEDED(hr))
{
    // Get the supported properties as a VARIANT
    CComVariant supportedProperties;
    hr = spRecognizer->get_SupportedProperties(&supportedProperties);
    // Make sure it is an array with at least one element
    if (SUCCEEDED(hr) && (VT_ARRAY == (VT_ARRAY & supportedProperties.vt))
        && (NULL != supportedProperties.parray)      
        && (0 < supportedProperties.parray->rgsabound[0].cElements))
    {
        // Get lower and upper bound of the SAFEARRAY
        long lBound, uBound;
        hr = SafeArrayGetLBound(supportedProperties.parray, 1, &lBound);
        if (SUCCEEDED(hr))
            hr = SafeArrayGetUBound(supportedProperties.parray, 1, &uBound);
        if (SUCCEEDED(hr))
        {
            // Convert the GUID to a BSTR
            BSTR bstrConfidenceGuid = 
                SysAllocString(INKRECOGNITIONPROPERTY_CONFIDENCELEVEL);
            // Loop through the properties
            for (int i = lBound; i <= uBound; i++)
            {
                long index = i;
                BSTR propertyGuid;
                hr = SafeArrayGetElement(supportedProperties.parray,
                    &index, &propertyGuid);
                if (SUCCEEDED(hr))
                {
                     // Check to see if it matches the Guid
                    if (VARCMP_EQ == VarBstrCmp(propertyGuid,
                        bstrConfidenceGuid, GetUserDefaultLCID(), 0))
                    {
                        m_isConfidenceSupported = true;
                        SysFreeString(propertyGuid);
                        break;
                    }
                SysFreeString(propertyGuid);
                }
            }
            SysFreeString(bstrConfidenceGuid);
       }
    }
}

If confidence is supported, then each recognition alternate has a Confidence property set to a level of Strong, Intermediate, or Poor. The example calls the MarkWordsWithoutHighConfidence method if confidence is supported. This method looks at the confidence of each word in the top alternate and marks those that do not have Strong confidence. The user is therefore more likely to notice whether or not those words have been recognized correctly. Use the RecognitionAlternate.ConfidenceAlternates property to get a collection of RecognitionAlternates grouped by their confidence.

Note   The RecognitionAlternates collection returned when you call the ConfidenceAlternates method breaks down a RecognitionAlternate into pieces, which is different than the collection of alternates for a RecognitionResult. You can then loop through these alternates and look for ones whose Confidence is not Strong. To find where in the RichTextBox to change to bold font, use the RecognitionAlternate.GetTextRangeFromStrokes method and pass in the strokes from the RecognitionAlternate you wish to display in bold.

The MarkWordsWithoutHighConfidence method is shown in the following example code.

C#

private void MarkWordsWithoutHighConfidence()
{
    if (this.recognitionResult != null)
    {
        // Loop through alternates for the top alternate by confidence
        RecognitionAlternate topAlternate =
            this.recognitionResult.TopAlternate;
        foreach (RecognitionAlternate alternate in 
            topAlternate.ConfidenceAlternates)
        {
            if (alternate.Confidence != RecognitionConfidence.Strong)
            {
                // Figure out where in the top alternate the poor
                // confidence corresponds to
                int start = 0; 
                int length = topAlternate.ToString().Length;
                topAlternate.GetTextRangeFromStrokes(alternate.Strokes, 
                    ref start, ref length);
                // Change the font to bold in the rich text box
                this.richTextBoxConverted.Select(start, length);
                this.richTextBoxConverted.SelectionFont = 
                    new Font(this.richTextBoxConverted.Font, FontStyle.Bold);
            }
        }
    }
}

Visual Basic .NET

Private Sub MarkWordsWithoutHighConfidence ()
    If (Not Me.TheRecognitionResult Is Nothing) Then
        ' Loop through alternates for the top alternate by confidence
        Dim topAlternate As RecognitionAlternate = _
            Me.TheRecognitionResult.TopAlternate
        Dim alternate As RecognitionAlternate
        For Each alternate In topAlternate.ConfidenceAlternates
            If alternate.Confidence <> RecognitionConfidence.Strong Then
                ' Figure out where in the top alternate the poor
                ' confidence corresponds to
                Dim start As Integer = 0
                Dim length As Integer = topAlternate.ToString().Length
                topAlternate.GetTextRangeFromStrokes(alternate.Strokes, _
                    start, length)
                ' Change the font to bold in the rich text box
                Me.RichTextBoxConverted.Select(start, length)
                Me.RichTextBoxConverted.SelectionFont = _
                    New Font(Me.RichTextBoxConverted.Font, FontStyle.Bold)
            End If
        Next
    End If
End Sub

C++

void CTextCorrectionDialog::MarkWordsWithoutHighConfidence()
{
  if (m_pRecognitionResult != NULL)
  {
    HRESULT result;
    // Loop through alternates from the top alternate by confidence
    IInkRecognitionAlternate* pTopAlternate;
    result = m_pRecognitionResult->get_TopAlternate(&pTopAlternate);
    if (SUCCEEDED(result))
    {
      IInkRecognitionAlternates* pConfidenceAlternates;
      result = pTopAlternate->get_ConfidenceAlternates(&pConfidenceAlternates);
      if (SUCCEEDED(result))
      {
          long nAlternates;
          result = pConfidenceAlternates->get_Count(&nAlternates);
          if (SUCCEEDED(result))
          {
            for (int i = 0; i < nAlternates; i++)
            {
              IInkRecognitionAlternate* pAlternate;
              result = pConfidenceAlternates->Item(i, &pAlternate);
              if (SUCCEEDED(result))
              {
                InkRecognitionConfidence confidence;
                result = pAlternate->get_Confidence(&confidence);
                if (SUCCEEDED(result) && confidence != IRC_Strong)
                {
                  // Figure out where in the top alternate the poor
                  // confidence corresponds to
                  long start, length;
                  IInkStrokes* pStrokes;
                  result = pAlternate->get_Strokes(&pStrokes);
                  if (SUCCEEDED(result))
                  {
                    result = pTopAlternate->GetTextRangeFromStrokes(pStrokes, 
                      &start, &length);
                    if (SUCCEEDED(result))
                    {
                      // Change the font to bold in the rich text box
                      m_richEditConvertedText.SetSel(start, length + start);
                      CHARFORMAT format;
                      CHARFORMAT& rFormat = format;
                      m_richEditConvertedText.GetSelectionCharFormat(rFormat);
                      rFormat.dwEffects |= CFE_BOLD;
                      m_richEditConvertedText.SetSelectionCharFormat(rFormat);
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}

Displaying Strokes for Alternates

The example uses the ShowAlternates method to display a thumbnail sketch of the strokes for the text that has been selected. You can obtain these strokes from the RecognitionAlternates.Strokes property. The actual drawing of the strokes occurs in the panel's Paint event handler. (Or in the case of the C++ example, in the dialog's OnPaint method.) The ShowAlternates method uses the Strokes.GetBoundingBox and Renderer.PixelToInkSpace methods to scale the Renderer to draw inside the panel, and then uses the Renderer.Draw method to draw the strokes. These operations are shown in the following example code.

Note   When you call the Renderer.Scale method, both scales must be the same in order to avoid distorting the ink. In the example code we use the smaller of the x and y scaling factors. Because of the way the scaling is done, the ink is drawn so that it fits exactly within the borders of the panel. A better way to scale the ink is to add a margin by placing the ink slightly in from the top left corner, and scaling it slightly smaller than the panel dimensions.

C#

private void panelSelectedInk_Paint(object sender,
    System.Windows.Forms.PaintEventArgs e)
{
  if (this.selectedStrokes != null)
  {
    // Figure out how to scale to fit the box
    Renderer renderer = new Renderer();
    Rectangle strokeBounds = this.selectedStrokes.GetBoundingBox();
    Point panelSizeAsPoint = 
      new Point(this.panelSelectedInk.Width, 
      this.panelSelectedInk.Height);
    renderer.PixelToInkSpace(e.Graphics, ref panelSizeAsPoint);
    float scaleX = (float) panelSizeAsPoint.X / strokeBounds.Width;
    float scaleY = (float) panelSizeAsPoint.Y / strokeBounds.Height;

    // First translate so that the bounding box is at the
    // upper left corner of the panel
    renderer.Move(-strokeBounds.Left, -strokeBounds.Top);

    // We want the smallest of the two scales, so that we
    // can scale uniformly and not distort the ink
    float scale = Math.Min(scaleX, scaleY);     
    renderer.Scale(scale, scale);

    // (Note that it would be even better to add some math so 
    // that there's a small margin, so that the selected ink doesn't 
    // come all the way to the edge of the panel.)

    // Draw the ink
    renderer.Draw(e.Graphics, this.selectedStrokes);
  }
}

Visual Basic .NET

Private Sub PanelSelectedInk_Paint(ByVal sender As Object, _
ByVal e As System.Windows.Forms.PaintEventArgs) _
Handles PanelSelectedInk.Paint
  If Not (Me.SelectedStrokes Is Nothing) Then
    ' Figure out how to scale to fit the box
    Dim renderer As New Renderer
    Dim strokeBounds As Rectangle = Me.SelectedStrokes.GetBoundingBox()
    Dim panelSizeAsPoint As New Point(Me.PanelSelectedInk.Width, _
      Me.PanelSelectedInk.Height)
    renderer.PixelToInkSpace(e.Graphics, panelSizeAsPoint)
    Dim scaleX As Single = _
      System.Convert.ToSingle(panelSizeAsPoint.X) / strokeBounds.Width
    Dim scaleY As Single = _
      System.Convert.ToSingle(panelSizeAsPoint.Y) / strokeBounds.Height

    ' First translate so that the bounding box is at the upper
    ' left corner of the panel
    renderer.Move(-strokeBounds.Left, -strokeBounds.Top)

    ' We want the smallest of the two scales, so that we
    ' can scale uniformly and not distort the ink
    If scaleX < scaleY Then
      renderer.Scale(scaleX, scaleX)
    Else
      renderer.Scale(scaleY, scaleY)
    End If

    '(Note that it would be even better to add some math so that there's a 
    ' small margin, so that the selected ink doesn't come all the way 
    ' to the edge of the panel.)

    ' Draw the ink
    renderer.Draw(e.Graphics, Me.SelectedStrokes)
  End If
End Sub 'panelSelectedInk_Paint

C++

In this method, we override the dialog's OnPaint method in order to draw ink into the CStatic control. If instead, you create a subclass for the CStatic object and override its OnPaint method to draw the ink, then you only need to call Invalidate on the one control rather than the entire dialog.

void CTextCorrectionDialog::OnPaint()
{
    // Paint the dialog
    CDialog::OnPaint();

    // If there are strokes to paint, then paint them. 
    if (m_pSelectedStrokes != NULL)
    {
        // Figure out how to scale to fit the box
        
        HRESULT result;

        // Get the bounding box of the strokes
        CRect rectStrokeBounds;
        IInkRectangle* pStrokeBounds;
        result = m_pSelectedStrokes->GetBoundingBox(IBBM_Default, 
            &pStrokeBounds);
        if (SUCCEEDED(result))
        {
            result = pStrokeBounds->get_Data(&rectStrokeBounds);
            pStrokeBounds->Release();
            pStrokeBounds = NULL;

        
            // Create DC for painting
            CPaintDC paintDC(&m_staticForInk);

            if (SUCCEEDED(result))
            {
                // First translate so that the bounding box is at the origin
                result = 
                    m_pRenderer->Move((float)-rectStrokeBounds.left, 
                    (float)-rectStrokeBounds.top);
            }
            if (SUCCEEDED(result))
            {
                // Next scale to fit

                // Get bounding box of the panel for drawing ink
                CRect rectPanelForInk;
                m_staticForInk.GetClientRect(&rectPanelForInk);

                // Convert panel size into ink units
                long panelLeft = (long) rectPanelForInk.left;
                long panelRight = (long) rectPanelForInk.right;
                long panelTop = (long) rectPanelForInk.top;
                long panelBottom = (long) rectPanelForInk.bottom;
                result = m_pRenderer->PixelToInkSpace(
                    PtrToLong(paintDC.GetSafeHdc()), 
                    &panelLeft, &panelTop);
                if (SUCCEEDED(result))
                {
                    result = m_pRenderer->
PixelToInkSpace(PtrToLong(paintDC.GetSafeHdc()),
                        &panelRight, &panelBottom);
                }
                if (SUCCEEDED(result))
                {
                    float scaleX = (float) (panelRight - panelLeft) / 
                        rectStrokeBounds.Width();
                    float scaleY = (float) (panelBottom - panelTop) / 
                        rectStrokeBounds.Height();

                    // We want the smallest of the two scales, 
                    // so that we can scale uniformly and not distort
                    // the ink
                    float scale = scaleX < scaleY ? scaleX : scaleY;     
                    result = m_pRenderer->ScaleTransform(scale, scale);
                }
            }

            // (Note that it would be even better to add some math 
            // so that there's a small margin, so that the selected 
            // ink doesn't come all the way to the edge of the panel.)

            if (SUCCEEDED(result))
            {
                // Draw the ink
                result = m_pRenderer->Draw(PtrToLong(paintDC.GetSafeHdc()), 
                    m_pSelectedStrokes);
            }

            // Reset Renderer's transform
            IInkTransform* pIdentityTransform;
            result = CoCreateInstance(CLSID_InkTransform, 
                            NULL, CLSCTX_INPROC_SERVER,
                            IID_IInkTransform, 
                            (void **) &pIdentityTransform);
            if (SUCCEEDED(result))
            {
                result = m_pRenderer->SetViewTransform(pIdentityTransform);
                pIdentityTransform->Release();
            }
        }
    }
}

Modifying the Top Alternate

Once the user selects an alternate from the list box and then clicks Change, we modify the top alternate so that it uses the selected alternate. This code can be found in the corresponding button's Click event handler. The event handler takes the selected item from the list box, and uses the RecognitionResult.ModifyTopAlternate method to change the top alternate so that it uses the alternate that the user selected. Finally, the RecognitionResult.TopString is placed in the text box.

C#

private void buttonChange_Click(object sender, System.EventArgs e)
{
    // Get the selected alternate from the list box
    RecognitionAlternate selectedAlternate = 
        (RecognitionAlternate)this.listBoxAlternatives.SelectedItem;

    // Modify the top alternate with the selected alternate
    this.recognitionResult.ModifyTopAlternate(selectedAlternate);

    // Put the new top alternate into converted text box
    this.richTextBoxConverted.Text = this.recognitionResult.TopString;
}

Visual Basic .NET

Private Sub ButtonChange_Click(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles ButtonChange.Click
    ' Get the selected alternate from the list box
    Dim selectedAlternate As RecognitionAlternate = _
        CType(Me.ListBoxAlternatives.SelectedItem, RecognitionAlternate)

    ' Modify the top alternate with the selected alternate
    Me.TheRecognitionResult.ModifyTopAlternate(selectedAlternate)

    ' Put the new top alternate into converted text box
    Me.RichTextBoxConverted.Text = Me.TheRecognitionResult.TopString
End Sub 'ButtonChange_Click

C++

void CTextCorrectionDialog::OnBnClickedButtonChange()
{
    // Get the selected alternate from the list box
    int nSelection = m_listBoxAlternates.GetCurSel();
    if (nSelection >= 0)
    {
        IInkRecognitionAlternate* pSelectedAlternate;
        m_pAlternates->Item(nSelection, &pSelectedAlternate);
 
        // Modify the top alternate with the selected alternate
        HRESULT result = m_pRecognitionResult->
            ModifyTopAlternate(pSelectedAlternate);

        if (SUCCEEDED(result))
        {
            // Put the new top alternate into converted text box
            BSTR topString;
            result = m_pRecognitionResult->get_TopString(&topString);
            if (SUCCEEDED(result))
            {
                m_richEditConvertedText.SetWindowText(CString(topString));
            }
        }
    }
}

Conclusions

There are a number of ways you can enhance working with recognition alternates, as shown in the example code in this article:

  • Create a RecognizerContext and use the Recognize method to create a RecognitionResult. If you desire, you can use the BackgroundRecognize method for asynchronous recognition.
  • Use RecognitionResult.GetAlternatesFromSelection to get alternates for the part of the TopString that the user has selected.
  • Use RecognitionAlternate.ConfidenceAlternates to find parts of the top alternate that have lower confidence. Use RecognitionAlternate.GetTextRangeFromStrokes to find which part of the top alternate the low confidence alternates correspond to.
  • Display the RecognitionAlternates that you get. One easy way of accomplishing this is to add the RecognitionAlternates to the Items of a list box.
  • Use RecognitionAlternates.Strokes to find the strokes that correspond to the selection. Use a Paint event handler to draw the strokes.
  • Use RecognitionResult.ModifyTopAlternate to put the selected alternate into the top alternate.