Reputation: 23868
Question: How can we programmatically replace some text in a WPF RichTextBox
without loosing its formatting? In the following code I am clearly not doing something right. My online search gives some relevant suggestions but they seem to be using Winform where RichTextBox has rtf property - such as this one.
Following code correctly replaces text abcd
with rstu
inside a WPF RichTexBox but it looses the formatting of the RichTextBox as shown in the two images below:
//rtbTest is the name of the RichTextBox
TextRange textRange = new TextRange(rtbTest.Document.ContentStart, rtbTest.Document.ContentEnd);
string oldText = textRange.Text;
string newText = oldText.Replace("abcd", "rstu");
textRange.Text = newText;
Screenshot of RichTextBox BEFORE replacing abcd with rstu:
Screenshot of RichTextBox AFTER replacing abcd with rstu:
As we can see the formatting is lost. The list shown below is not really a formatted numbered list, it probably is just unformatted text (like 1. Item 1
etc.)
Upvotes: 1
Views: 711
Reputation: 8985
When modifying the RichTextBox
content it's very important to analyzing the TextPointer
context. How it can be implemented see in the example below:
MainWindow.xaml
<Window ...
Title="MainWindow" Height="350" Width="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<RichTextBox x:Name="rtb" AllowDrop="True" VerticalScrollBarVisibility="Auto" Padding="2">
<FlowDocument>
<Paragraph>
<Run Text="Paste a content to the document..."/>
</Paragraph>
</FlowDocument>
</RichTextBox>
<Button Grid.Row="1" Click="FindAndReplace">Find & Replace </Button>
</Grid>
</Window>
Part of the MainWindow.xaml.cs
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
private void FindAndReplace(object sender, RoutedEventArgs e)
{
var textToFind = "ABC";
var textReplaceBy = "<A-B-C>";
TextPointer start = rtb.Document.ContentStart;
while (true)
{
var searchRange = new TextRange(start, rtb.Document.ContentEnd);
TextRange foundRange = searchRange.FindText(textToFind);
if (foundRange == null)
break;
foundRange.Text = textReplaceBy;
start = foundRange.End; // Continue the searching
}
rtb.Focus();
}
TextRangeExt.cs
using System;
using System.Windows.Documents;
namespace WpfApp7
{
public static class TextRangeExt
{
public static TextRange FindText(this TextRange searchRange, string searchText)
{
TextRange result = null;
int offset = searchRange.Text.IndexOf(searchText, StringComparison.OrdinalIgnoreCase);
if (offset >= 0)
{
var start = searchRange.Start.GetTextPositionAtOffset(offset);
result = new TextRange(start, start.GetTextPositionAtOffset(searchText.Length));
}
return result;
}
private static TextPointer GetTextPositionAtOffset(this TextPointer position, int offset)
{
for (TextPointer current = position; current != null; current = position.GetNextContextPosition(LogicalDirection.Forward))
{
position = current;
var adjacent = position.GetAdjacentElement(LogicalDirection.Forward);
var context = position.GetPointerContext(LogicalDirection.Forward);
switch (context)
{
case TextPointerContext.Text:
int count = position.GetTextRunLength(LogicalDirection.Forward);
if (offset <= count)
{
return position.GetPositionAtOffset(offset);
}
offset -= count;
break;
case TextPointerContext.ElementStart:
if (adjacent is InlineUIContainer)
{
offset--;
}
else if (adjacent is ListItem lsItem)
{
var trange = new TextRange(lsItem.ElementStart, lsItem.ElementEnd);
var index = trange.Text.IndexOf('\t');
if (index >= 0)
{
offset -= index + 1;
}
}
break;
case TextPointerContext.ElementEnd:
if (adjacent is Paragraph)
offset -= 2;
break;
}
}
return position;
}
}
}
Upvotes: 1
Reputation: 2470
It is losing the formatting because you are storing the RTF into a string and the string doesn't keep RTF formatting.
You can preserve it as following,
TextRange textRange = new TextRange(rtbTest.Document.ContentStart, rtbTest.Document.ContentEnd);
string rtf;
using (var memoryStream = new MemoryStream())
{
textRange.Save(memoryStream, DataFormats.Rtf);
rtf = ASCIIEncoding.Default.GetString(memoryStream.ToArray());
}
rtf = rtf.Replace("abcd", "rstu");
MemoryStream stream = new MemoryStream(ASCIIEncoding.Default.GetBytes(rtf));
rtbTest.SelectAll();
rtbTest.Selection.Load(stream, DataFormats.Rtf);
Upvotes: 2