Shintaro Takechi
Shintaro Takechi

Reputation: 1365

Way to obtain the word cursor is on, in WPF RichTextBox Control

I would like to know how I can get the word that current cursor is on, in WPF RichTextBox. I am aware that RichTextBox has Selection Property. However, this only gives me the text that is highlighted in the RichTextBox. Instead I would like to know the word the cursor is on even if the whole word is not highlighted.

Any tips are appreciated.

Upvotes: 6

Views: 3752

Answers (5)

NearHuscarl
NearHuscarl

Reputation: 81430

Here is my alternative solution using LINQ and Dependency Property:

public class SelectionRichTextBox : RichTextBox
{
    public SelectionRichTextBox()
    {
        // Use base class style
        SetResourceReference(StyleProperty, typeof(RichTextBox));
    }

    public static readonly DependencyProperty SelectedWordProperty =
        DependencyProperty.Register(
                "SelectedWord",
                typeof(string),
                typeof(SelectionRichTextBox),
                new PropertyMetadata("")
                );

    public string SelectedWord
    {
        get
        {
            return (string)GetValue(SelectedWordProperty);
        }
        set
        {
            SetValue(SelectedWordProperty, value);
        }
    }

    protected override void OnMouseUp(MouseButtonEventArgs e)
    {
        TextPointer cursorPosition = CaretPosition;

        string strBeforeCursor = cursorPosition.GetTextInRun(LogicalDirection.Backward);
        string strAfterCursor = cursorPosition.GetTextInRun(LogicalDirection.Forward);

        string wordBeforeCursor = strBeforeCursor.Split().Last();
        string wordAfterCursor = strAfterCursor.Split().First();

        string text = wordBeforeCursor + wordAfterCursor;

        SelectedWord = string.Join("", text
            .Where(c => char.IsLetter(c))
            .ToArray());

        base.OnMouseUp(e);
    }
}

After that, you can use it in binding like this:

    <custom:SelectionRichTextBox
            SelectedWord="{Binding SelectedWord, Mode=OneWayToSource}"/>

Upvotes: -1

Prince Owen
Prince Owen

Reputation: 1505

Since words are divided by spaces, you can iterate through the runs around the caret until space is found. This function should work even when your RichTextBox even contains different Fonts and Font Sizes.

    public string GetWordByCaret(LogicalDirection direction)
    {
        // Get the CaretPosition
        TextPointer position = this.CaretPosition;
        TextPointerContext context = position.GetPointerContext(direction);

        string text = string.Empty;

        // Iterate through the RichTextBox based on the Start, Text and End of nearby inlines
        while (context != TextPointerContext.None)
        {
            // We are only interested in the text here
            //, so ignore everything that is not text
            if (context == TextPointerContext.Text)
            {
                string current = position.GetTextInRun(direction);

                // The strings appended based on whether they are before the caret or after it...
                // And well...I love switches :)
                switch (direction)
                {
                    case LogicalDirection.Backward:
                        {
                            int spaceIndex = current.LastIndexOf(' ');

                            // If space is found, we've reached the end
                            if (spaceIndex >= 0)
                            {
                                int length = current.Length - 1;

                                if (spaceIndex + 1 <= length)
                                {
                                    text = current.Substring(spaceIndex + 1, length - spaceIndex) + text;
                                }

                                return text;
                            }

                            else
                                text = current + text;
                        }
                        break;

                    default:
                        {
                            int spaceIndex = current.IndexOf(' ');

                            // If space is found, we've reached the end
                            if (spaceIndex >= 0)
                            {
                                int length = current.Length;

                                if (spaceIndex <= length)
                                {
                                    text += current.Substring(0, spaceIndex);
                                }

                                return text;
                            }

                            else
                                text += current;
                        }
                        break;
                }
            }

            // Move to the next position
            position = position.GetNextContextPosition(direction);

            // Get the next context
            if (position != null)
                context = position.GetPointerContext(direction);
            else
                context = TextPointerContext.None;
        }
        return text;
    }

Now you can get the word you caret is on like this.

    string before = GetWordByCaret(LogicalDirection.Backward);
    string after = GetWordByCaret(LogicalDirection.Forward); 
    string word = before + after; // :)

Upvotes: -1

Bal&#225;zs
Bal&#225;zs

Reputation: 2929

Attach this function to an arbitrary RichTextBox, now called testRTB, and see Output window for results:

private void testRTB_MouseUp(object sender, MouseButtonEventArgs e)
{
        TextPointer start = testRTB.CaretPosition;  // this is the variable we will advance to the left until a non-letter character is found
        TextPointer end = testRTB.CaretPosition;    // this is the variable we will advance to the right until a non-letter character is found

        String stringBeforeCaret = start.GetTextInRun(LogicalDirection.Backward);   // extract the text in the current run from the caret to the left
        String stringAfterCaret = start.GetTextInRun(LogicalDirection.Forward);     // extract the text in the current run from the caret to the left

        Int32 countToMoveLeft = 0;  // we record how many positions we move to the left until a non-letter character is found
        Int32 countToMoveRight = 0; // we record how many positions we move to the right until a non-letter character is found

        for (Int32 i = stringBeforeCaret.Length - 1; i >= 0; --i)
        {
            // if the character at the location CaretPosition-LeftOffset is a letter, we move more to the left
            if (Char.IsLetter(stringBeforeCaret[i]))
                ++countToMoveLeft;
            else break; // otherwise we have found the beginning of the word
        }


        for (Int32 i = 0; i < stringAfterCaret.Length; ++i)
        {
            // if the character at the location CaretPosition+RightOffset is a letter, we move more to the right
            if (Char.IsLetter(stringAfterCaret[i]))
                ++countToMoveRight;
            else break; // otherwise we have found the end of the word
        }



        start = start.GetPositionAtOffset(-countToMoveLeft);    // modify the start pointer by the offset we have calculated
        end = end.GetPositionAtOffset(countToMoveRight);        // modify the end pointer by the offset we have calculated


        // extract the text between those two pointers
        TextRange r = new TextRange(start, end);
        String text = r.Text;


        // check the result
        System.Diagnostics.Debug.WriteLine("[" + text + "]");
}

Change Char.IsLetter(...) to Char.IsLetterOrDigit(...) or whatever else appropriately depending on whether you wish to keep digits as well.

Tip: extract this into an extension method in a separate assembly to access it whenever needed.

Upvotes: 5

Shintaro Takechi
Shintaro Takechi

Reputation: 1365

OK so in order to solve this I brute forced it.

I used curCaret.GetTextInRun(LogicalDirection.Backward) and curCaret.GetTextInRun(LogicalDirection.Forward)

along with preCaretString.LastIndexOf(" ") and postCaretString.IndexOf(" ") plus other dividers that separates word and got the substrings.

Eventually I added the first half of string and second half of string to obtain the currently cursored word.

I bet there are cleverer way of doing this but at least this solved the problem

Upvotes: 3

ikh
ikh

Reputation: 2436

You can get the current position of the cursor via CaretPosition.

Unfortunately there is no easy way to get the characters to the left/right of the caret position. The only way I know of to get text out of a RichTextBox is in this answer, which is a bit convoluted. But it will accomplish what is necessary.

Upvotes: 1

Related Questions