Traverse Text Using UI Automation

This topic shows how to use Microsoft UI Automation to traverse the textual content of a document by TextUnit increments.

Example

The following code example demonstrates how to traverse the content of a UI Automation text provider. The Move method moves the Start and End endpoints of a TextPatternRange. This text range is typically a degenerate range representing the text insertion point.

NoteNote:

Since only text-based embedded objects are considered part of the text stream embedded objects such as images do not affect Move or its return value.

    '--------------------------------------------------------------------
    ' <summary>
    ' Starts the target application.
    ' </summary>
    ' <param name="app">
    ' The application to start.
    ' </param>
    ' <returns>The automation element for the app main window.</returns>
    ' <remarks>
    ' Three WPF documents, a rich text document, and a plain text document 
    ' are provided in the Content folder of the TextProvider project.
    ' </remarks>
    '--------------------------------------------------------------------
    Private Function StartApp(ByVal app As String) As AutomationElement
        ' Start application.
        Dim p As Process = Process.Start(app)

        ' Give the target application some time to start.
        ' For Win32 applications, WaitForInputIdle can be used instead.
        ' Another alternative is to listen for WindowOpened events.
        ' Otherwise, an ArgumentException results when you try to
        ' retrieve an automation element from the window handle.
        Thread.Sleep(2000)

        targetResult.Content = WPFTarget + " started. " + vbLf + vbLf + _
        "Please load a document into the target application and click " + _
        "the 'Find edit control' button above. " + vbLf + vbLf + _
        "NOTE: Documents can be found in the 'Content' folder of the FindText project."
        targetResult.Background = Brushes.LightGreen

        ' Return the automation element for the app main window.
        Return AutomationElement.FromHandle(p.MainWindowHandle)

    End Function 'StartApp

...

    '--------------------------------------------------------------------
    ' <summary>
    ' Finds the text control in our target.
    ' </summary>
    ' <param name="src">The object that raised the event.</param>
    ' <param name="e">Event arguments.</param>
    ' <remarks>
    ' Initializes the TextPattern object and event handlers.
    ' </remarks>
    '--------------------------------------------------------------------
    Private Sub FindTextProvider_Click( _
    ByVal src As Object, ByVal e As RoutedEventArgs)
        ' Set up the conditions for finding the text control.
        Dim documentControl As New PropertyCondition( _
        AutomationElement.ControlTypeProperty, ControlType.Document)
        Dim textPatternAvailable As New PropertyCondition( _
        AutomationElement.IsTextPatternAvailableProperty, True)
        Dim findControl As New AndCondition(documentControl, textPatternAvailable)

        ' Get the Automation Element for the first text control found.
        ' For the purposes of this sample it is sufficient to find the 
        ' first text control. In other cases there may be multiple text
        ' controls to sort through.
        targetDocument = targetWindow.FindFirst(TreeScope.Descendants, findControl)

        ' Didn't find a text control.
        If targetDocument Is Nothing Then
            targetResult.Content = _
            WPFTarget + " does not contain a Document control type."
            targetResult.Background = Brushes.Salmon
            startWPFTargetButton.IsEnabled = False
            Return
        End If

        ' Get required control patterns 
        targetTextPattern = DirectCast( _
        targetDocument.GetCurrentPattern(TextPattern.Pattern), TextPattern)

        ' Didn't find a text control that supports TextPattern.
        If targetTextPattern Is Nothing Then
            targetResult.Content = WPFTarget + _
            " does not contain an element that supports TextPattern."
            targetResult.Background = Brushes.Salmon
            startWPFTargetButton.IsEnabled = False
            Return
        End If
        ' Text control is available so display the client controls.
        infoGrid.Visibility = Visibility.Visible

        targetResult.Content = "Text provider found."
        targetResult.Background = Brushes.LightGreen

        ' Initialize the document range for the text of the document.
        documentRange = targetTextPattern.DocumentRange

        ' Initialize the client's search buttons.
        If targetTextPattern.DocumentRange.GetText(1).Length > 0 Then
            searchForwardButton.IsEnabled = True
        End If
        ' Initialize the client's search TextBox.
        searchString.IsEnabled = True

        ' Check if the text control supports text selection
        If targetTextPattern.SupportedTextSelection = SupportedTextSelection.None Then
            targetResult.Content = "Unable to select text."
            targetResult.Background = Brushes.Salmon
            Return
        End If

        ' Edit control found so remove the find button from the client.
        findEditButton.Visibility = Visibility.Collapsed

        ' Initialize the client with the current target selection, if any.
        NotifySelectionChanged()

        ' Search starts at beginning of doc and goes forward
        searchBackward = False

        ' Initialize a text changed listener.
        ' An instance of TextPatternRange will become invalid if 
        ' one of the following occurs:
        ' 1) The text in the provider changes via some user activity.
        ' 2) ValuePattern.SetValue is used to programatically change 
        ' the value of the text in the provider.
        ' The only way the client application can detect if the text 
        ' has changed (to ensure that the ranges are still valid), 
        ' is by setting a listener for the TextChanged event of 
        ' the TextPattern. If this event is raised, the client needs 
        ' to update the targetDocumentRange member data to ensure the 
        ' user is working with the updated text. 
        ' Clients must always anticipate the possibility that the text 
        ' can change underneath them.
        Dim onTextChanged As AutomationEventHandler = _
        New AutomationEventHandler(AddressOf TextChanged)
        Automation.AddAutomationEventHandler( _
        TextPattern.TextChangedEvent, targetDocument, TreeScope.Element, onTextChanged)
        ' Initialize a selection changed listener.
        ' The target selection is reflected in the client.
        Dim onSelectionChanged As AutomationEventHandler = _
        New AutomationEventHandler(AddressOf OnTextSelectionChange)
        Automation.AddAutomationEventHandler( _
        TextPattern.TextSelectionChangedEvent, targetDocument, _
        TreeScope.Element, onSelectionChanged)

    End Sub 'FindTextProvider_Click

...

    '--------------------------------------------------------------------
    ' <summary>
    ' Handles the navigation item selected event.
    ' </summary>
    ' <param name="sender">The object that raised the event.</param>
    ' <param name="e">Event arguments.</param>
    '--------------------------------------------------------------------
    Private Sub NavigationUnit_Change( _
    ByVal sender As Object, ByVal e As SelectionChangedEventArgs)
        Dim cb As ComboBox = CType(sender, ComboBox)
        navigationUnit = CType(cb.SelectedValue, TextUnit)

    End Sub 'NavigationUnit_Change

    '--------------------------------------------------------------------
    ' <summary>
    ' Handles the Navigate button click event.
    ' </summary>
    ' <param name="sender">The object that raised the event.</param>
    ' <param name="e">Event arguments.</param>
    '--------------------------------------------------------------------
    Private Sub Navigate_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
        Dim moveSelection As Button = CType(sender, Button)
        Dim navDirection As Integer

        ' Which direction is the user searching through the text control?
        If (CType(moveSelection.Tag, traversalDirection) = traversalDirection.Forward) Then
            navDirection = 1
        Else
            navDirection = -1
        End If

        ' Obtain the ranges to move.
        Dim selectionRanges As TextPatternRange() = targetTextPattern.GetSelection()

        ' Iterate throught the ranges for a text control that supports
        ' multiple selections and move the selections the specified text
        ' unit and direction.
        Dim textRange As TextPatternRange
        For Each textRange In selectionRanges
            textRange.Move(navigationUnit, navDirection)
            textRange.Select()
        Next textRange
        ' The WPF target doesn't show selected text as highlighted unless
        ' the window has focus.
        targetDocument.SetFocus()

    End Sub 'Navigate_Click
    ///--------------------------------------------------------------------
    /// <summary>
    /// Starts the target application.
    /// </summary>
    /// <param name="app">
    /// The application to start.
    /// </param>
    /// <returns>The automation element for the app main window.</returns>
    /// <remarks>
    /// Three WPF documents, a rich text document, and a plain text document 
    /// are provided in the Content folder of the TextProvider project.
    /// </remarks>
    ///--------------------------------------------------------------------
    private AutomationElement StartApp(string app)
    {
        // Start application.
        Process p = Process.Start(app);

        // Give the target application some time to start.
        // For Win32 applications, WaitForInputIdle can be used instead.
        // Another alternative is to listen for WindowOpened events.
        // Otherwise, an ArgumentException results when you try to
        // retrieve an automation element from the window handle.
        Thread.Sleep(2000);

        targetResult.Content =
            WPFTarget +
            " started. \n\nPlease load a document into the target " +
            "application and click the 'Find edit control' button above. " +
            "\n\nNOTE: Documents can be found in the 'Content' folder of the FindText project.";
        targetResult.Background = Brushes.LightGreen;

        // Return the automation element for the app main window.
        return (AutomationElement.FromHandle(p.MainWindowHandle));
    }

...

    ///--------------------------------------------------------------------
    /// <summary>
    /// Finds the text control in our target.
    /// </summary>
    /// <param name="src">The object that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    /// <remarks>
    /// Initializes the TextPattern object and event handlers.
    /// </remarks>
    ///--------------------------------------------------------------------
    private void FindTextProvider_Click(object src, RoutedEventArgs e)
    {
        // Set up the conditions for finding the text control.
        PropertyCondition documentControl = new PropertyCondition(
            AutomationElement.ControlTypeProperty,
            ControlType.Document);
        PropertyCondition textPatternAvailable = new PropertyCondition(
            AutomationElement.IsTextPatternAvailableProperty, true);
        AndCondition findControl =
            new AndCondition(documentControl, textPatternAvailable);

        // Get the Automation Element for the first text control found.
        // For the purposes of this sample it is sufficient to find the 
        // first text control. In other cases there may be multiple text
        // controls to sort through.
        targetDocument =
            targetWindow.FindFirst(TreeScope.Descendants, findControl);

        // Didn't find a text control.
        if (targetDocument == null)
        {
            targetResult.Content =
                WPFTarget +
                " does not contain a Document control type.";
            targetResult.Background = Brushes.Salmon;
            startWPFTargetButton.IsEnabled = false;
            return;
        }

        // Get required control patterns 
        targetTextPattern =
            targetDocument.GetCurrentPattern(
            TextPattern.Pattern) as TextPattern;

        // Didn't find a text control that supports TextPattern.
        if (targetTextPattern == null)
        {
            targetResult.Content =
                WPFTarget +
                " does not contain an element that supports TextPattern.";
            targetResult.Background = Brushes.Salmon;
            startWPFTargetButton.IsEnabled = false;
            return;
        }

        // Text control is available so display the client controls.
        infoGrid.Visibility = Visibility.Visible;

        targetResult.Content =
            "Text provider found.";
        targetResult.Background = Brushes.LightGreen;

        // Initialize the document range for the text of the document.
        documentRange = targetTextPattern.DocumentRange;

        // Initialize the client's search buttons.
        if (targetTextPattern.DocumentRange.GetText(1).Length > 0)
        {
            searchForwardButton.IsEnabled = true;
        }
        // Initialize the client's search TextBox.
        searchString.IsEnabled = true;

        // Check if the text control supports text selection
        if (targetTextPattern.SupportedTextSelection ==
            SupportedTextSelection.None)
        {
            targetResult.Content = "Unable to select text.";
            targetResult.Background = Brushes.Salmon;
            return;
        }

        // Edit control found so remove the find button from the client.
        findEditButton.Visibility = Visibility.Collapsed;

        // Initialize the client with the current target selection, if any.
        NotifySelectionChanged();

        // Search starts at beginning of doc and goes forward
        searchBackward = false;

        // Initialize a text changed listener.
        // An instance of TextPatternRange will become invalid if 
        // one of the following occurs:
        // 1) The text in the provider changes via some user activity.
        // 2) ValuePattern.SetValue is used to programatically change 
        // the value of the text in the provider.
        // The only way the client application can detect if the text 
        // has changed (to ensure that the ranges are still valid), 
        // is by setting a listener for the TextChanged event of 
        // the TextPattern. If this event is raised, the client needs 
        // to update the targetDocumentRange member data to ensure the 
        // user is working with the updated text. 
        // Clients must always anticipate the possibility that the text 
        // can change underneath them.
        Automation.AddAutomationEventHandler(
            TextPattern.TextChangedEvent,
            targetDocument,
            TreeScope.Element,
            TextChanged);

        // Initialize a selection changed listener.
        // The target selection is reflected in the client.
        Automation.AddAutomationEventHandler(
            TextPattern.TextSelectionChangedEvent,
            targetDocument,
            TreeScope.Element,
            OnTextSelectionChange);
    }

...

    ///--------------------------------------------------------------------
    /// <summary>
    /// Handles the navigation item selected event.
    /// </summary>
    /// <param name="sender">The object that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    ///--------------------------------------------------------------------
    private void NavigationUnit_Change(object sender, SelectionChangedEventArgs e)
    {
        ComboBox cb = (ComboBox)sender;
        navigationUnit = (TextUnit)cb.SelectedValue;
    }

    ///--------------------------------------------------------------------
    /// <summary>
    /// Handles the Navigate button click event.
    /// </summary>
    /// <param name="sender">The object that raised the event.</param>
    /// <param name="e">Event arguments.</param>
    ///--------------------------------------------------------------------
    private void Navigate_Click(object sender, RoutedEventArgs e)
    {
        Button moveSelection = (Button)sender;

        // Which direction is the user searching through the text control?
        int navDirection =
            ((traversalDirection)moveSelection.Tag == traversalDirection.Forward) ? 1 : -1;            
        
        // Obtain the ranges to move.
        TextPatternRange[] selectionRanges =
                       targetTextPattern.GetSelection();
        
        // Iterate throught the ranges for a text control that supports
        // multiple selections and move the selections the specified text
        // unit and direction.
        foreach (TextPatternRange textRange in selectionRanges)
        {
            textRange.Move(navigationUnit, navDirection);
            textRange.Select();
       }
       // The WPF target doesn't show selected text as highlighted unless
       // the window has focus.
       targetDocument.SetFocus();
    }

Any method using TextUnit will defer to the next largest TextUnit supported if the given TextUnit is not supported by the control.

See Also

Tasks

Add Content to a Text Box Using UI Automation
Find and Highlight Text Using UI Automation

Concepts

UI Automation TextPattern Overview
UI Automation Control Patterns Overview
UI Automation Control Patterns for Clients