【问题标题】:How to programmatically replace some content of WFP RichTextBox without loosing the formatting?如何在不丢失格式的情况下以编程方式替换 WPF RichTextBox 的某些内容?
【发布时间】:2021-04-02 18:35:47
【问题描述】:

问题:我们如何以编程方式替换WPF RichTextBox 中的某些文本而不丢失其格式?在下面的代码中,我显然没有做正确的事情。我的在线搜索提供了一些相关建议,但他们似乎使用的是 Winform,其中 RichTextBox 具有 rtf 属性 - 例如this one。

以下代码正确地将 rstu 内的 WPF RichTexBox 中的文本 abcd 替换为 WPF RichTexBox,但它会丢失 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;

将 abcd 替换为 rstu 之前的 RichTextBox 屏幕截图

将 abcd 替换为 rstu 后的 RichTextBox 屏幕截图

我们可以看到格式丢失了。下面显示的列表并不是真正的格式化编号列表,它可能只是未格式化的文本(如1. Item 1 等)

【问题讨论】:

    标签: c# wpf richtextbox rtf


    【解决方案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);
    

    【讨论】:

    • 以这种方式操作RTF文档在某些情况下不起作用,甚至可能损坏文档内容。例如,在文档中包含\r\n 字符并尝试将其替换为rtf = rtf.Replace(@"\r\n", "rstu");
    • @AbdullahLeghari 您的建议确实正确地保留了格式,但由于某些原因,它消除了行之间的默认距离。例如,正如您在我的帖子的图片 2 中看到的那样,线条之间的距离已被删除。除了您的代码现在正确显示列表之外,您建议的代码中也发生了同样的事情。我的 RichTextBox 的声明如下。所以,我不确定为什么会这样:<RichTextBox x:Name="rtbTest" AcceptsTab="True" FontFamily="Calibri"/>
    • @nam 你可以根据需要设置行高,把它作为我分享的最后一行代码,rtbTest.SetValue(Paragraph.LineHeightProperty, 10.0);
    • @AbdullahLeghari 我注意到上述问题与您的代码替换文本无关(例如abcdrstu)。因为即使从代码中注释掉 rtf = rtf.Replace("abcd", "rstu"); 行,代码也可以正常运行,但会消除行之间的默认距离。
    • @AbdullahLeghari 10.0 中的 rtbTest.SetValue(Paragraph.LineHeightProperty, 10.0); 没有任何区别,但 25.0 有任何区别。这可能是一个不同的问题。所以,我会为它发布一个不同的帖子 - 因为可能有其他替代方案来解决它。然而,你做到了。回答我原来的问题(谢谢)。
    【解决方案2】:

    在修改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 &amp; 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 的其他方面(段落/行等)。
    • @nam:欢迎对您的帖子提出任何进一步的问题! :)
    猜你喜欢
    • 1970-01-01
    • 2017-01-01
    • 2017-09-30
    • 2021-12-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多