如何以编程方式替换 WPF RichTextBox 的某些内容而不丢失格式?
How to programmatically replace some content of WFP RichTextBox without loosing the formatting?
问题:我们如何以编程方式替换 WPF RichTextBox
中的某些文本而不丢失其格式?在下面的代码中,我显然没有做正确的事情。我的在线搜索提供了一些相关建议,但他们似乎在使用 Winform,其中 RichTextBox 具有 rtf 属性 - 例如 this 一个。
以下代码在 WPF RichTexBox 中正确地将文本 abcd
替换为 rstu
,但它丢失了 RichTextBox 的格式,如下面两张图片所示:
//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;
用 rstu 替换 abcd 之前的 RichTextBox 屏幕截图:
用 rstu 替换 abcd 后的 RichTextBox 屏幕截图:
如我们所见,格式丢失了。下面显示的列表并不是真正的格式化编号列表,它可能只是未格式化的文本(如 1. Item 1
等)
它正在丢失格式,因为您将 RTF 存储到一个字符串中,而该字符串不保留 RTF 格式。
您可以保存如下,
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);
修改 RichTextBox
内容时,分析 TextPointer
上下文非常重要。如何实施请参见以下示例:
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>
部分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;
}
}
}
问题:我们如何以编程方式替换 WPF RichTextBox
中的某些文本而不丢失其格式?在下面的代码中,我显然没有做正确的事情。我的在线搜索提供了一些相关建议,但他们似乎在使用 Winform,其中 RichTextBox 具有 rtf 属性 - 例如 this 一个。
以下代码在 WPF RichTexBox 中正确地将文本 abcd
替换为 rstu
,但它丢失了 RichTextBox 的格式,如下面两张图片所示:
//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;
用 rstu 替换 abcd 之前的 RichTextBox 屏幕截图:
用 rstu 替换 abcd 后的 RichTextBox 屏幕截图:
如我们所见,格式丢失了。下面显示的列表并不是真正的格式化编号列表,它可能只是未格式化的文本(如 1. Item 1
等)
它正在丢失格式,因为您将 RTF 存储到一个字符串中,而该字符串不保留 RTF 格式。
您可以保存如下,
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);
修改 RichTextBox
内容时,分析 TextPointer
上下文非常重要。如何实施请参见以下示例:
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>
部分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;
}
}
}