Get part of the text from RichTextBox - c#

Can someone please tell me what is wrong with this. I am trying to get text between several characters before caret and the caret."comparable" is never longer than the actual text in the RichTextBox.
This is the code that I have:
int coLen = comparable.Length;
TextPointer caretBack = rtb.CaretPosition.GetPositionAtOffset(coLen,
LogicalDirection.Backward);
TextRange rtbText = new TextRange(caretBack, rtb.CaretPosition);
string text = rtbText.Text;
This returns text = ""
Please help!

This works as expected , I get I a
Piece of code :
RichTextBox rtb = new RichTextBox();
rtb.AppendText("I am adding some texts to the richTextBox");
rtb.CaretPosition = rtb.CaretPosition.DocumentEnd;
int coLen = 3;
TextPointer caretBack = rtb.CaretPosition.GetPositionAtOffset(-coLen);
TextRange rtbText = new TextRange(caretBack, rtb.CaretPosition);
string ttt = rtbText.Text;
EDIT
Here is an MSTest method to explain the behavior of the Caret and reading :
[TestMethod]
public void TestRichtTextBox()
{
RichTextBox rtb = new RichTextBox();
rtb.AppendText("I am adding some texts to the richTextBox");
int offset = 3;
TextPointer beginningPointer = rtb.CaretPosition.GetPositionAtOffset(offset);
TextPointer endPointer = rtb.CaretPosition.DocumentEnd;
TextRange rtbText = new TextRange(beginningPointer, endPointer);
Assert.IsTrue(rtbText.Text == "m adding some texts to the richTextBox\r\n");
// Now we if we keep the same beggining offset but we change the end Offset to go backwards.
beginningPointer = rtb.CaretPosition.GetPositionAtOffset(3);
endPointer = rtb.CaretPosition; // this one is the beginning of the text
rtbText = new TextRange(beginningPointer, endPointer);
Assert.IsTrue(rtbText.Text == "I a");
// Nowe we want to read from the back three characters.
// so we set the end Point to DocumentEnd.
rtb.CaretPosition = rtb.CaretPosition.DocumentEnd;
beginningPointer = rtb.CaretPosition.GetPositionAtOffset(-offset);
endPointer = rtb.CaretPosition; // we already set this one to the end document
rtbText = new TextRange(beginningPointer, endPointer);
Assert.IsTrue(rtbText.Text == "Box");
}
Plus here is a comment from MSDN about the negative index :
offset Type: System.Int32 An offset, in symbols, for which to
calculate and return the position. If the offset is negative, the
position is calculated in the logical direction opposite of that
indicated by the LogicalDirection property.

Related

How to display formatted text in C# WPF application?

I have a text file of code from an old 3rd party system that I'm trying to upgrade. The code is structured text and looks very similar to VB. I'd like to parse the text file and display formatted text in a WPF application. Ideally, it would look something similar to the Visual Studio code editor.
Below is a sample of the code I am trying to format
--this is a comment
LOCAL tag1 --LOCAL would be formatted
LOCAL tag2
LOCAL foo
IF tag1 > tag2 THEN --IF and THEN would be formatted
foo = tag1
END IF --end if would be formatted
I've managed to do this by creating a FlowDocument from the original text of code. Then I search the text file for keywords and change the color of text with the following method
private FlowDocument FormatDocument(FlowDocument flowDocument, List<string> keyWordList, Brush brush)
{
TextPointer position = flowDocument.ContentStart;
while (position != null)
{
if (position.CompareTo(flowDocument.ContentEnd) == 0)
break;
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) //checks to see if textpointer is actually text
{
foreach (string keyword in keyWordList)
{
string textRun = position.GetTextInRun(LogicalDirection.Forward);
string pattern = #"\b" + Regex.Escape(keyword) + #"\b";
Match match = Regex.Match(textRun, pattern, RegexOptions.IgnoreCase);
if (match.Success)
{
int indexInRun = match.Index;
int indexOfComment = textRun.IndexOf("--");
TextPointer startPosition = position.GetPositionAtOffset(indexInRun);
TextPointer endPosition = startPosition.GetPositionAtOffset(keyword.Length);
TextRange keywordRange = new TextRange(startPosition, endPosition);
string test = keywordRange.Text;
if (indexOfComment == -1 || indexInRun < indexOfComment)
keywordRange.ApplyPropertyValue(TextElement.ForegroundProperty, brush);
}
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
else //If the current position doesn't represent a text context position, go to the next context position.
position = position.GetNextContextPosition(LogicalDirection.Forward); // This can effectively ignore the formatting or embed element symbols.
}
return flowDocument;
}
The code is a bit slow when the files are large so I'm wondering is there a better way to go about this?
Your code seems okay, except that you're creating a bunch of objects every iteration of each loop, which will be slow, especially for Regex objects. Regex are also much faster if you compile them. Create your Regex objects outside of either loop and compile them, and I'll bet you see some improvement.
If that's not enough improvement, try building a single Regex that will match any word in the keyword list (\b[keyword1|keyword2|keyword3|...]\b).
public static FlowDocument FormatDocument(FlowDocument flowDocument,
List<string> keyWordList,
Brush brush)
{
var regexForKeyword = keyWordList.ToDictionary(k => k,
k => new Regex(#"\b" + Regex.Escape(keyword) + #"\b",
RegexOptions.Compiled | RegexOptions.IgnoreCase));
var position = flowDocument.ContentStart;
while (position != null)
{
if (position.CompareTo(flowDocument.ContentEnd) == 0)
break;
if (position.GetPointerContext(LogicalDirection.Forward) == TextPointerContext.Text) //checks to see if textpointer is actually text
{
foreach (string keyword in keyWordList)
{
var textRun = position.GetTextInRun(LogicalDirection.Forward);
var match = regexForKeyword[keyword].Match(textRun);
if (match.Success)
{
var indexInRun = match.Index;
var indexOfComment = textRun.IndexOf("--");
var startPosition = position.GetPositionAtOffset(indexInRun);
var endPosition = startPosition.GetPositionAtOffset(keyword.Length);
var keywordRange = new TextRange(startPosition, endPosition);
var test = keywordRange.Text;
if (indexOfComment == -1 || indexInRun < indexOfComment)
keywordRange.ApplyPropertyValue(TextElement.ForegroundProperty, brush);
}
}
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
else //If the current position doesn't represent a text context position, go to the next context position.
position = position.GetNextContextPosition(LogicalDirection.Forward); // This can effectively ignore the formatting or embed element symbols.
}
return flowDocument;
}

How can I add underlined words in a line of text in a word header object?

I need to add underlined column headers to the header objects in a document. I am using C# and Microsoft.Office.Interop.Word
The pertinent parts of the code look like this...
foreach (Word.HeaderFooter header in wordSection.Headers)
{
int[] fiscalYears = RetrieveFiscalYears(docProfile);
string paddingFY = new String(' ', 8);
Word.Paragraph colParagraph = wordRng.Paragraphs.Add();
int year;
for (int i = fiscalYears.Length - 1; i >= 0; i--)
{
year = fiscalYears[i];
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
//begin underline
colParagraph.Range.InsertAfter(paddingFY + year.ToString() + paddingFY);
//end underline
}
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Add();
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
}
Basically it needs to look similar to ...
Expended Estimated Budgeted
2015 2016 2017
--------- ---------- --------
In the body of the document, my for loop looks like
foreach (int year in fiscalYears)
{
wordApp.Selection.TypeText(MARKUP_WORD_TAB);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
wordApp.Selection.TypeText(paddingFY + year.ToString() + paddingFY);
wordApp.Selection.Font.Underline = Word.WdUnderline.wdUnderlineNone;
}
But the when I use the selection object, it writes to the body of the document, not to the header/footer objects. I might be able to get around this by using the SeekHeader and making it the focus, but that offers its own challenges...
I've tried using the colParagraph.Range.Font.Underline object, but that underlines the entire line, not just the words that make up the column headings.
I've tried using a find object, but the execute doesn't find the text for some reason.
Appreciate any guidance you can provide.
I had to move the set style above the for loop and set a new range based on the paragraph range and move its start and end positions. Then apply the underlining to the new range.
So now it looks similar to ....
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the tab so the style can be applied.
colParagraph.set_Style(wordDoc.Styles["ColumnHeadings"]);
int year;
int start = colParagraph.Range.Text.Length - 1;
string yrHeading = string.Empty;
Word.Range underlineRange = null;
for (int i = 0 ; i < fiscalYears.Length; i++)
{
year = fiscalYears[i];
colParagraph = wordRng.Paragraphs.Last; //reset the range to include the last fiscal year that was entered.
start = colParagraph.Range.Text.Length - 1;
colParagraph.Range.InsertAfter(yrHeading);
colParagraph.Range.InsertAfter(MARKUP_WORD_TAB);
underlineRange = colParagraph.Range.Duplicate;
underlineRange.MoveStart(Word.WdUnits.wdCharacter, start);
underlineRange.MoveEnd(Word.WdUnits.wdCharacter, -2); //-2 = /t/r for tab & paragraph characters
underlineRange.Font.Underline = Word.WdUnderline.wdUnderlineSingle;
}
colParagraph = wordRng.Paragraphs.Add();

C# losing Font Style in RichTextBox after deleting lines

I have a RichTextBox for a simple chat where I add lines programmatically.
I make the usernames bold and the messages in regular style.
After some lines I want to delete the first lines to keep the chat in a acceptably length. But when I do so I lose the text format and everything appears bold. What am I doing wrong and how can I fix this?
EDIT
I could solve the problem where I wasn't able to delete the first line.
I had to set the the ReadOnly property to false. Even though I was able to add new lines it prevented deleting lines. So the following code works to delete lines. Thanks to #TaW!
if (ChatText.Lines.Length >= 10)
{
int p = 0; int count = 0;
do
{
p = ChatText.Text.IndexOf("\n\r");
if (p >= 0)
{
ChatText.SelectionStart = p;
ChatText.SelectionLength = 2; // length of "\n\r"
ChatText.SelectedText = "\n";
count++;
}
}
while(p >= 0);
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = ChatText.Lines.Take(0).Select(x => x.Length + nll).Sum() - nll;
int pL = ChatText.Lines.Take(1).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
ChatText.SelectionStart = pS;
ChatText.SelectionLength = pL - pS;
ChatText.Cut();
}
//////////////////////////////////
// now add new lines
//////////////////////////////////
string[] chatstr;
// string text is given as method parameter
chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// go to the end of the text
ChatText.SelectionStart = ChatText.Text.Length;
ChatText.SelectionLength = 0;
// make text bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
// add username (chatstr[0]) and colon
ChatText.AppendText(chatstr[0] + ": ");
// make text regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
// add message (chatstr[1])
ChatText.AppendText(chatstr[1] + "\n");
// and finaly scroll down
ChatText.ScrollToCaret();
So deleting lines works and new lines are added as intended. Finaly!
solved :)
Never change the Text of a RichtTextBox if it contains any formatting.
Changing the Lines property (by Skip) is just another way to change the Text.
Instead only use the functions the RTB provides: Always start by selecting the portion you want to format, then apply one or more of the functions and/or set one or more of the properties..:
To delete portions use Cut.
Here is a function that will delete a number of entire lines:
void DeleteLines(RichTextBox rtb, int fromLine, int count)
{
int p1 = rtb.GetFirstCharIndexFromLine(fromLine);
int p2 = rtb.GetFirstCharIndexFromLine(fromLine + count);
rtb.SelectionStart = p1;
rtb.SelectionLength = p2 - p1;
bool readOnly = rtb.ReadOnly; // allow change even when the RTB is readonly
rtb.ReadOnly = false; ;
rtb.Cut();
rtb.ReadOnly = readOnly;
}
Trying to keept the formatting alive yourself is a tedious and error-prone waste of your time.
In addition to font properties you would also have to resore all other things you can set with the SelectedXXX properties, like colors, alignment, spacing etc etc..
To delete the first 3 lines use:
DeleteLines(yourRTB, 0, 3);
To restrict the text to 10 lines use:
DeleteLines(yourRTB, 0, yourRTB.Lines.Length - 10);
Note that the function above should have a few checks for valid input; I left them out as the checks somehow need a decision what to do, if count or fromLine if greater than Lines.Length or if fromLine is negative..
While we are at it, here is how to append a bold line:
yourRTB.SelectionStart = yourRTB.Text.Length;
yourRTB.SelectionLength = 0;
using (Font font = new Font(yourRTB.SelectionFont, FontStyle.Bold))
yourRTB.SelectionFont = font;
yourRTB.AppendText(yourNewLine + textOfNewLine);
Of course it really shold go into a reuseable function that the the bolding as a parameter..
Update:
since you are using WordWrap you may prefer this function. It deletes the actual lines, not the visible ones:
void DeleteLinesWW(RichTextBox rtb, int fromLine, int count)
{
int nll = 1; // <<=== pick the length of your new line character(s)!!
int pS = rtb.Lines.Take(fromLine).Select(x => x.Length + nll).Sum() - nll;
int pL = rtb.Lines.Take(fromLine + count).Select(x => x.Length + nll).Sum() - nll;
if (pS < 0) { pS = 0; pL++; }
rtb.SelectionStart = pS;
rtb.SelectionLength = pL - pS ;
bool readOnly = rtb.ReadOnly;
rtb.ReadOnly = false; // allow change even when the RTB is readonly
rtb.Cut();
rtb.ReadOnly = readOnly;
}
A word on NewLine: Do note that I have not used the Environment.NewLine constant as it not really a good idea. If you add multiline text to the RichTextBox in the designer and then look at it you will see that it uses simple '\n' new lines, no returns, no NL-CR, just '\n'. So this seems to be the generic way in a winforms RTB and I recommend using it..
The new function relies on all lines having a newline of the same length!
To make sure you can use this replacement function:
int RTBReplace(RichTextBox rtb, string oldText, string newText)
{
int p = 0; int count = 0;
do
{
p = richTextBox1.Text.IndexOf(oldText);
if (p >= 0)
{
richTextBox1.SelectionStart = p;
richTextBox1.SelectionLength = oldText.Length;
richTextBox1.SelectedText = newText;
count ++;
}
}
while (p >= 0);
return count;
}
Calling it like this:
RTBReplace(yourrichTextBox, "\r\n", "\n");
Update 2:
Here is an example how to add your chat lines:
private void button1_Click(object sender, EventArgs e)
{
string cLine = "Taw: Hello World"; // use your own lines!
var chatstr = cLine.Split(new string[] { ": " }, 2, StringSplitOptions.None);
AppendLineBold(yourrichTextBox, "\n" + chatstr[0], true);
AppendLineBold(yourrichTextBox, chatstr[1], false);
yourrichTextBox.ScrollToCaret();
}
void AppendLineBold(RichTextBox rtb, string text, bool bold)
{
rtb.SelectionStart = richTextBox1.Text.Length;
rtb.SelectionLength = 0;
using (Font font = new Font(rtb.SelectionFont,
bold ? FontStyle.Bold : FontStyle.Regular))
rtb.SelectionFont = font;
rtb.AppendText(text);
}
Update 3:
Looks like the ReadOnly property disallows the use of Cut. So we need to temporatily allow changes.
Funny: SelectedText can't be set either, but AppendText works fine..
To keep text formatting, you can also try the following (it's a little shorter and should also do the trick)
string text = "Username: hello this is a chat message";
// delete the first line when after 10 lines
if (ChatText.Lines.Length >= 10)
{
ChatText.SelectionStart = 0; // set SelectionStart to the beginning of chat text (RichTextBox)
ChatText.SelectionLength = ChatText.Text.IndexOf("\n", 0) + 1; // select the first line
ChatText.SelectedText = ""; // replace by an empty string
ChatText.SelectionStart = ChatText.Text.Length; // set SelectionStart to text end to make SelectionFont work for appended text
}
// split the string in chatstr[0] = username, chatstr[1] = message
string[] chatstr = text.Split(new string[] { ": " }, 2, StringSplitOptions.None);
// make the username bold
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Bold);
ChatText.AppendText(chatstr[0] + ": ");
// make the message regular
ChatText.SelectionFont = new Font(ChatText.Font, FontStyle.Regular);
ChatText.AppendText(chatstr[1] + Environment.NewLine);
ChatText.ScrollToCaret();

How to return the exact range of a specific word in RichTextBox

Trying to select and color a specific word in WPF Richtextbox but my method selects just first 5 letters of the word. Indexes 0,1 and 2 seems to be empty string although the first word in my rtb is "private" and there is no empty string before it.
What can be the cause of this problem?
public void FormatRtbText(RichTextBox rtb)
{
int x, y;
string str = "private";
var text = new TextRange(rtb.Document.ContentStart, rtb.Document.ContentEnd).Text;
x = text.IndexOf(str);
y = x + str.Length;
var range = new TextRange(rtb.Document.ContentStart.GetPositionAtOffset(x), rtb.Document.ContentStart.GetPositionAtOffset(y));
range.ApplyPropertyValue(TextElement.ForegroundProperty, Brushes.Red);
}
GetPositionAtOffset considers 3 things as symbols while calculating the offset:
An opening or closing tag for the TextElement element.
A UIElement element contained in an InlineUIContainer or BlockUIContainer. Note
that such a UIElement is always counted as exactly one symbol; any
additional content or elements contained by the UIElement are not
counted as symbols.
A 16-bit Unicode character inside of a text Run
element.
Here the first two symbols are the Paragraph and Run elements. Therefore your TextRange is two symbols behind what you want. This code should the the work. (What this code does is just skipping symbols until the next symbol is text.)
TextPointer start = rtb.Document.ContentStart;
while (start.GetPointerContext(LogicalDirection.Forward) != TextPointerContext.Text)
{
start = start.GetNextContextPosition(LogicalDirection.Forward);
if (start == null) return;
}
...
var range = new TextRange(start, start.GetPositionAtOffset(y));
I found that the offsets returned by the wpf rtf box are practically worthless. They don't take into account the hidden characters that the textbox requires. Each new paragraph, image, etc in the box will add even more hidden chars that skew the offset.
Here's what I came up with to search for the match closed to the caret position.
private TextRange FindText(string findText)
{
var fullText = DoGetAllText();
if (string.IsNullOrEmpty(findText) || string.IsNullOrEmpty(fullText) || findText.Length > fullText.Length)
return null;
var textbox = GetTextbox();
var leftPos = textbox.CaretPosition;
var rightPos = textbox.CaretPosition;
while (true)
{
var previous = leftPos.GetNextInsertionPosition(LogicalDirection.Backward);
var next = rightPos.GetNextInsertionPosition(LogicalDirection.Forward);
if (previous == null && next == null)
return null; //can no longer move outward in either direction and text wasn't found
if (previous != null)
leftPos = previous;
if (next != null)
rightPos = next;
var range = new TextRange(leftPos, rightPos);
var offset = range.Text.IndexOf(findText, StringComparison.InvariantCultureIgnoreCase);
if (offset < 0)
continue; //text not found, continue to move outward
//rtf has broken text indexes that often come up too low due to not considering hidden chars. Increment up until we find the real position
var findTextLower = findText.ToLower();
var endOfDoc = textbox.Document.ContentEnd.GetNextInsertionPosition(LogicalDirection.Backward);
for (var start = range.Start.GetPositionAtOffset(offset); start != endOfDoc; start = start.GetPositionAtOffset(1))
{
var result = new TextRange(start, start.GetPositionAtOffset(findText.Length));
if (result.Text?.ToLower() == findTextLower)
{
return result;
}
}
}
}
If you want to highlight the match then it'd be as simple as changing this method to void and doing this when you found the match:
textbox.Selection.Select(result.Start, result.End);

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.
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.
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
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.
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; // :)
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}"/>

Categories

Resources