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.
Related
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.
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.
sorry for my bad english... The default for a RichTextBox content is to inherit the Foreground color from the RichTextBox itself. That's nice, but if I set a specific Foreground color to some part of my text, that part does not inherit the Foreground anymore, obviously. How can I make my "colored" text inherit the Foreground again? I'm trying to do something like the "Automatic" color from Office Word but after I have set a specific color to a TextRange, I do not know how to unset it :/
TextRange.ClearAllProperties() does what I need, but also erases other properties like FontSize and FontFamily...
TextRange.ApplyPropertyValue(ForegroundProperty, DependencyProperty.UnsetValue) also does not do the trick...
You can also unset it by setting the property to null (this worked for me clearing out the background, for example removing highlighting)
TextRange.ApplyPropertyValue(TextElement.BackgroundProperty, null);
This seemed almost impossible to achieve since there is no "RemovePropertyValue" method. I also tried with span and got the same exception as you did so I made a method that collects all the Paragraphs within the TextRange and made a span for each separetly.. less than ideal, I know.. Anyway, it works for a small example but might be pretty hard to work with for something more complex.
private List<Span> m_spanList = new List<Span>();
private void c_setForegroundButton_Click(object sender, RoutedEventArgs e)
{
TextPointer textPointerStart = c_richTextBox1.Selection.Start;
TextPointer textPointerEnd = c_richTextBox1.Selection.End;
TextRange textRange = new TextRange(textPointerStart, textPointerEnd);
SetForeground(textRange);
}
private void c_clearForegroundButton_Click(object sender, RoutedEventArgs e)
{
foreach (Span span in m_spanList)
{
span.ClearValue(Span.ForegroundProperty);
}
}
public void SetForeground(TextRange textRange)
{
List<Paragraph> spannedParagraphs = new List<Paragraph>();
if (textRange.Start.Paragraph != null)
{
TextRange curRange = null;
Block cur = textRange.Start.Paragraph;
do
{
spannedParagraphs.Add(cur as Paragraph);
// Get next range
curRange = new TextRange(cur.ContentStart, cur.ContentEnd);
} while ((textRange.End.Paragraph == null || !curRange.Contains(textRange.End.Paragraph.ContentEnd)) && (cur = cur.NextBlock) != null);
}
if (spannedParagraphs.Count == 1)
{
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
for (int i = 0; i < spannedParagraphs.Count; i++)
{
if (i == spannedParagraphs.Count - 1)
{
Paragraph paragraph = spannedParagraphs[i];
// For some reason I get an exception here when I try this..
//m_span = new Span(paragraph.ElementStart, c_richTextBox1.Selection.End);
c_richTextBox1.Selection.Select(paragraph.ElementStart, c_richTextBox1.Selection.End);
Span span = new Span(c_richTextBox1.Selection.Start, c_richTextBox1.Selection.End);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else if (i == 0)
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(c_richTextBox1.Selection.Start, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
else
{
Paragraph paragraph = spannedParagraphs[i];
Span span = new Span(paragraph.ElementStart, paragraph.ElementEnd);
span.Foreground = Brushes.Red;
m_spanList.Add(span);
}
}
}
}
If you look at the code of method TextRange.ApplyPropertyValue in the .NET Reference Source, you'll see that in the end it calls DependencyObject.SetValue on a collection of Inlines and Blocks, and DependencyObject.SetValue treats the value DependencyProperty.UnsetValue specially by effectively clearing the local value for the property.
The problem is that they didn't think of that when implementing TextRange.ApplyPropertyValue: it checks the passed property value against the property type, and in case of a reference type, it makes sure the passed value is either null or inherits from the same class, thus preventing us from passing DependencyProperty.UnsetValue.
One solution I found to implement a way of clearing local values of a TextRange for dependency properties of a reference type is the following:
// We declare a marker brush to be detected later in the TextRange.
var markerBrush = new SolidColorBrush();
// First we ask the TextRange implementation to set our marker brush on its content.
// Using ApplyPropertyValue here takes care of splitting inlines when necessary to make
// sure that only the selected text gets affected.
range.ApplyPropertyValue(TextElement.ForegroundProperty, markerBrush);
// Now, we search the text range for every Inline that has our brush set as the foreground
// brush, and we clear the Foreground dependency property.
var position = range.Start;
while (position != null && range.Contains(position))
{
if (position.GetPointerContext(LogicalDirection.Backward) == TextPointerContext.ElementStart &&
position.Parent is Inline inline &&
inline.ReadLocalValue(TextElement.ForegroundProperty) == _foregroundClearBrush)
inline.ClearValue(TextElement.ForegroundProperty);
position = position.GetNextContextPosition(LogicalDirection.Forward);
}
I'm building a simple editor using the wpf richtextbox. This editor has some sort of toggle buttons for Bold, Italic, Underlined, etc. which are 'pressed' when the selected text or the text at the cursor has the approptiate property. I did it like this:
private TextRange GetSelectedTextRange() {
if(_richTextBox == null) return null;
return new TextRange(_richTextBox.Selection.Start, _richTextBox.Selection.End);
}
private void UpdateIsItalic() {
TextRange selectedTextRange = GetSelectedTextRange();
if(selectedTextRange == null) {
IsItalic = false;
return;
}
object fontStyleObject = selectedTextRange.GetPropertyValue(Run.FontStyleProperty);
if(fontStyleObject is FontStyle) {
FontStyle fontStyle = (FontStyle)fontStyleObject;
IsItalic = (fontStyle == FontStyles.Italic || fontStyle == FontStyles.Oblique);
} else {
IsItalic = false;
}
}
The problem is, when the cursor is at the end of the line and a send for instance the ToggleItalic command to the RichTextBox, the values I get back from SelectedTextRange.GetPropertyValue are valid for the text the cursor is behind and not the text I'm about to type, thus I will get back the same value as before the command. But what I want is that when I send the ToggleItalic command, the result is that IsItalic is set to true when the letter I'm about to type is italic. Has anyone has an idea how to tackle this problem?
Many thanks in advance,
Liewe
I found a solution, I shoudn't make a new TextRange but just use the TextSelection, in short like this:
object fontStyleObject = _richTextBox.Selection.GetPropertyValue(Run.FontStyleProperty);
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();
}