Trying to reset the cursor position in a WPF Rich Text Box control - wpf

I have a WPF Rich Text Box control where I am replacing the text with new text of the same length and I simply want to get the original caret position before I replace the text, replace the text and then reset the caret position.
I think that the problem is that it uses a TextPointer that is tied to the RichTextBox and is reset when I clear the original text so that when reapply it the position has changed. What I actually want to do is to reset it to either the character position index in the original text or I am happy to deal with Lined and Columns.
I have scoured the internet for an answer to what should be a very simple problem and nothing seems to answer it. What I really want is an X/Y coordinate or anything that will help.

You can use GetOffsetToPosition() and GetPositionAtOffset() to save the relative position of the Caret and to restore it.
In other words, assuming that the RichTextBox initializes like this:
RichTextBox rtb;
int paragraphIndex = -1;
int indexInParagraph;
public MainWindow()
{
InitializeComponent();
rtb = new RichTextBox();
rtb.Document = new FlowDocument();
Paragraph para = new Paragraph(new Run("some text some text some text."));
rtb.Document.Blocks.Add(para);
// sets the caret at a specific (random) position in the paragraph:
rtb.CaretPosition = para.ContentStart.GetPositionAtOffset(5);
this.Content = rtb;
}
Note the three private fields in the class.
You should save the caret's paragraph index and caret index in the paragraph, before you replace the text:
public void SaveCaretState()
{
//enumerate and get the paragraph index
paragraphIndex = -1;
foreach (var p in rtb.Document.Blocks)
{
paragraphIndex++;
if (p == rtb.CaretPosition.Paragraph)
break;
}
//get index relative to the start of the paragraph:
indexInParagraph = rtb.CaretPosition.Paragraph.ElementStart.GetOffsetToPosition(rtb.CaretPosition);
}
and restore it whenever you liked:
public void RestoreCaretState(MouseEventArgs e)
{
// you might need to insure some conditions here (paragraph should exist and ...)
Paragraph para = rtb.Document.Blocks.ElementAt(paragraphIndex) as Paragraph;
rtb.CaretPosition = para.ElementStart.GetPositionAtOffset(indexInParagraph);
}
Please note that its a simple example and there might be other Blocks in RichTextBox.Document. However, the idea and implementation is not that much different.

Related

How can I move the caretposition programatically in a RichTextBox?

I have a RichTextBox with custom formatting on special bits of text in it. However there is a bug where after a character is inserted, the caret is placed before the newly inserted character instead of after.
This is because for every edit, the code recalculates the content to apply the custom formatting and then sets the CaretPosition like so...
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
currentPos = CaretPosition.GetNextInsertionPosition(LogicalDirection.Forward);
// Apply special formatting on the content
Content = GetContentValue();
if (currentPos != null)
CaretPosition = currentPos;
}
I am not sure how to move the caret in code so that it appears AFTER the inserted character e.g if original content is "11" and I insert a "2" in the middle of the text, I would like the Caret to be after the "2".
It currently appears as "1x21" (where x is the Caret). Any help would be appreciated
The position and LogicalDirection indicated by a TextPointer object
are immutable. When content is edited or modified, the position
indicated by a TextPointer does not change relative to the surrounding
text; rather the offset of that position from the beginning of content
is adjusted correspondingly to reflect the new relative position in
content. For example, a TextPointer that indicates a position at the
beginning of a given paragraph continues to point to the beginning of
that paragraph even when content is inserted or deleted before or
after the paragraph.
MSDN
The code below inserts text on Button.Click.
private void Button_Click(object sender, RoutedEventArgs e)
{
/* text to insert */
string text = "some text";
/* get start pointer */
TextPointer startPtr = Rtb.Document.ContentStart;
/* get current caret position */
int start = startPtr.GetOffsetToPosition(Rtb.CaretPosition);
/* insert text */
Rtb.CaretPosition.InsertTextInRun(text);
/* update caret position */
Rtb.CaretPosition = startPtr.GetPositionAtOffset((start) + text.Length);
/* update focus */
Rtb.Focus();
}

SelectionFont is not a member of System.Windows.Controls.RichTextBox [duplicate]

how can I change the font for a currently selected text area inside a WPF RichTextBox?
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}
How about something like:
TextSelection text = richTextBox.Selection;
if (!text.IsEmpty)
{
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
Solved...
if (this.TextEditor.Selection.IsEmpty)
this.TextEditor.CurrentFontFamily = SelectedFont;
else
this.TextEditor.Selection.ApplyPropertyValue(TextElement.FontFamilyProperty, SelectedFont);
To get the current selection use:
Dim rng As TextRange = New TextRange(YourRtfBox.Selection.Start, YourRtfBox.Selection.End)
And then set the fontstyle:
rng.ApplyPropertyValue(Inline.FontSizeProperty, YourFontSizeValue)
rng.ApplyPropertyValue(Inline.FontFamilyProperty, YourFontFamilyValue)
To change the font family for a selection in the RichTextBox you should use this:
text.ApplyPropertyValue(Run.FontFamilyProperty, value);
The selected text in a RichTextBox is a Run object, so one must use the Run Dependency Properties.
This seems to work in Silverlight at least, so should be the same thing in WPF.

Silverlight Richtextbox obtain cursor position

I'm trying create a user control using the silverlight 5 richtextbox. I need to be able to insert "inline ui" and can't work out how to get the current cursor position.
I trigger my code like this:
this.GetAbsolutePos(this.richText.Selection.Start);
The guts of this method is here:
private int GetAbsolutePos(TextPointer textPointer)
{
int index = 0;
TextPointer pos = this.richText.ContentStart;
while (pos.CompareTo(textPointer) != 0)
{
if (pos.IsAtInsertionPosition)
{
index++;
}
pos = pos.GetNextInsertionPosition(LogicalDirection.Forward);
}
return index;
}
Given the following text in a richtextbox control....
If the cursor is between 5 & 6 on the first line, then the above function correctly returns 5. But as the cursor is further into the text the position becomes more inaccurate. ie between 5 & 6 on the second line returns 16 and the third line returns 27.
It also becomes more difficult as I'm inserting inline elements at these positions, which then count as a "symbol" and further cause the count to go wrong.
This image shows what is happening when I "insert" the inline ui between 5 & 6 on each line.
Just for completeness here is the Xaml from richtext.Xaml (I've removed all the extra attributes from the Section/Paragraph elements to make it clearer)
<Section>
<Paragraph>
<Run>1234567890</Run>
<LineBreak />
<Run>1234567890</Run>
<LineBreak />
<Run>1234567890</Run>
</Paragraph>
</Section>
Based on the remarks on this page MSDN Silverlight TextPointer Class
Symbol - For TextPointer operations, any of the following is considered to be a symbol:
An opening or closing tag for a TextElement element.
A UIElement element contained within an InlineUIContainer. Note that
a UIElement is always counted as exactly one symbol. Any additional
content or elements contained by the UIElement are not considered
symbols.
Each 16-bit Unicode character inside of a text Run element.
I think I need to "know" what kind of "symbol" I am currently on but cannot figure out how.
Seems like it should be easy, but working with TextPointers seems very unintuitive.
I had an idea to parse the Xaml to find the cursor position but that seems like a real hack.
Any help would be appreciated.
Thanks
In the end we just just manually adjust the index by the number of SubstituteEdits (our representation of the UIElement) that are before the text point. Its still flaky and we have other issues with our control, but it's good enough for the moment.
Cheers
Tim
private int GetAbsolutePos(SubstituteEdit newSub)
{
int index = 0;
TextPointer textPointer = this.richText.Selection.Start;
TextPointer caretWhere = this.richText.ContentStart;
while (true)
{
caretWhere = caretWhere.GetNextInsertionPosition(LogicalDirection.Forward);
if (caretWhere == null || caretWhere.CompareTo(textPointer) == 0)
{
break;
}
index++;
}
foreach (SubstituteEdit sub in this.Substitutes)
{
if (sub.Position < index && sub != newSub)
{
index--;
}
}
return index;
}
I am not sure if this will help, but I ended up letting the user insert where the selection is instead of caret position and it seems to work well. Here is some of my code:
InlineUIContainer MyUI = new InlineUIContainer();
TextBlock tblx = new TextBlock() { Text = addedItem.Title, FontWeight = FontWeights.Bold };
MyUI.Child = tblx;
Paragraph myParagraph = new Paragraph();
myParagraph.Inlines.Add(MyUI);
rtb.Selection.Insert(myParagraph);

WPF RichTextBox: How to change selected text font?

how can I change the font for a currently selected text area inside a WPF RichTextBox?
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}
How about something like:
TextSelection text = richTextBox.Selection;
if (!text.IsEmpty)
{
text.ApplyPropertyValue(RichTextBox.FontSizeProperty, value);
}
Solved...
if (this.TextEditor.Selection.IsEmpty)
this.TextEditor.CurrentFontFamily = SelectedFont;
else
this.TextEditor.Selection.ApplyPropertyValue(TextElement.FontFamilyProperty, SelectedFont);
To get the current selection use:
Dim rng As TextRange = New TextRange(YourRtfBox.Selection.Start, YourRtfBox.Selection.End)
And then set the fontstyle:
rng.ApplyPropertyValue(Inline.FontSizeProperty, YourFontSizeValue)
rng.ApplyPropertyValue(Inline.FontFamilyProperty, YourFontFamilyValue)
To change the font family for a selection in the RichTextBox you should use this:
text.ApplyPropertyValue(Run.FontFamilyProperty, value);
The selected text in a RichTextBox is a Run object, so one must use the Run Dependency Properties.
This seems to work in Silverlight at least, so should be the same thing in WPF.

Set font properties in RichTextBox

HI,
I am developing an editor using RichTextBox in WPF, i have to implement feature that user can set font of selected Text if some text is selected, if nothing is selected then font should be set for new text.
If i set the font properties(like FontStyle,FontSize) of RTB in later case it will set the properties for whole text, How can i set font properties for new text(i.e if user enter the text it will come with new font setting).
I've implemented a toolbar that can change the font size, family, color, etc. What i found is the details can be tricky with the wpf richtextbox. Setting the selection font makes some sense, but, there are also the default font properties of the text box, and the current caret properties to contend with. Here is what i've written to get it to work for most cases with the font size. The process should be the same for fontfamily and fontcolor. Hope it helps.
public static void SetFontSize(RichTextBox target, double value)
{
// Make sure we have a richtextbox.
if (target == null)
return;
// Make sure we have a selection. Should have one even if there is no text selected.
if (target.Selection != null)
{
// Check whether there is text selected or just sitting at cursor
if (target.Selection.IsEmpty)
{
// Check to see if we are at the start of the textbox and nothing has been added yet
if (target.Selection.Start.Paragraph == null)
{
// Add a new paragraph object to the richtextbox with the fontsize
Paragraph p = new Paragraph();
p.FontSize = value;
target.Document.Blocks.Add(p);
}
else
{
// Get current position of cursor
TextPointer curCaret = target.CaretPosition;
// Get the current block object that the cursor is in
Block curBlock = target.Document.Blocks.Where
(x => x.ContentStart.CompareTo(curCaret) == -1 && x.ContentEnd.CompareTo(curCaret) == 1).FirstOrDefault();
if (curBlock != null)
{
Paragraph curParagraph = curBlock as Paragraph;
// Create a new run object with the fontsize, and add it to the current block
Run newRun = new Run();
newRun.FontSize = value;
curParagraph.Inlines.Add(newRun);
// Reset the cursor into the new block.
// If we don't do this, the font size will default again when you start typing.
target.CaretPosition = newRun.ElementStart;
}
}
}
else // There is selected text, so change the fontsize of the selection
{
TextRange selectionTextRange = new TextRange(target.Selection.Start, target.Selection.End);
selectionTextRange.ApplyPropertyValue(TextElement.FontSizeProperty, value);
}
}
// Reset the focus onto the richtextbox after selecting the font in a toolbar etc
target.Focus();
}

Resources