C# - 将大文件加载到 WPF RichTextBox 中?

8

我需要将一个大约10MB的文本文件加载到WPF RichTextBox中,但是我的当前代码会导致UI冻结。我尝试使用后台工作人员进行加载,但那似乎也不太好用。

这是我的加载代码。有没有办法提高它的性能?谢谢。

    //works well for small files only
    private void LoadTextDocument(string fileName, RichTextBox rtb)
    {
        System.IO.StreamReader objReader = new StreamReader(fileName);

        if (File.Exists(fileName))
        {
                rtb.AppendText(objReader.ReadToEnd());
        }
        else rtb.AppendText("ERROR: File not found!");
        objReader.Close();
    }






    //background worker version. doesnt work well
    private void LoadBigTextDocument(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        System.IO.StreamReader objReader = new StreamReader(   ((string[])e.Argument)[0]  );
        StringBuilder sB = new StringBuilder("For performance reasons, only the first 1500 lines are displayed. If you need to view the entire output, use an external program.\n", 5000);

            int bigcount = 0;
            int count = 1;
            while (objReader.Peek() > -1)
            {
                sB.Append(objReader.ReadLine()).Append("\n");
                count++;
                if (count % 100 == 0 && bigcount < 15)
                {
                    worker.ReportProgress(bigcount, sB.ToString());

                    bigcount++;
                    sB.Length = 0;
                }
            }
        objReader.Close();
        e.Result = "Done";
    }
8个回答

3

WPF RichTextBox控件使用Flow Document来显示富文本,然后将Flow Document附加到RTB控件中,而Windows Form RichTextBox控件直接显示富文本。这就是使WPF RTB变得超级缓慢的原因。 如果您愿意使用WinForm RTB,只需在您的WPF应用程序中托管它即可。 XAML代码:

<Window x:Class="WpfHostWfRTB.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
Title="MainWindow" Height="350" Width="525" Loaded="Window_Loaded">
<Grid>
    <Grid>
        <WindowsFormsHost Background="DarkGray" Grid.row="0" Grid.column="0">
            <wf:RichTextBox x:Name="rtb"/>
        </WindowsFormsHost>
    </Grid>
</Grid>
</Window>

C# 代码

private void LoadTextDocument(string fileName, RichTextBox rtb)
{
    System.IO.StreamReader objReader = new StreamReader(fileName);
        if (File.Exists(fileName))
        {
            rtb.AppendText(objReader.ReadToEnd());
        }
        else rtb.AppendText("ERROR: File not found!");
        objReader.Close();
}

我已经尝试了所有关于后台线程和其他 WPF RichTextBox 的技巧:无论是使用一个 AppendText 还是每行一个,甚至是将原始 RTF 加载到选择中,速度都非常慢。使用您提供的示例配置,我从 WPF 切换到 winforms,看到了超过一个数量级的加速,并且操作和索引文本的代码也变得非常简单。非常感谢! - Tony Delroy

2

我注意到在使用RichTextBox时,如果添加了更多的“行”,它会变得越来越慢。如果您可以在不添加“\n”的情况下完成它,它将为您加速。请记住,每个“\n”都是RichTextBox的新段落对象块。

这是我加载一个10MB文件的方法。它需要大约30秒的时间才能完成加载。我使用进度条对话框来让用户知道加载需要时间。

// Get Stream of the file
fileReader = new StreamReader(File.Open(this.FileName, FileMode.Open));

FileInfo fileInfo = new FileInfo(this.FileName);

long bytesRead = 0;

// Change the 75 for performance.  Find a number that suits your application best
int bufferLength = 1024 * 75;

while (!fileReader.EndOfStream)
{
    double completePercent = ((double)bytesRead / (double)fileInfo.Length);

    // I am using my own Progress Bar Dialog I left in here to show an example
    this.ProgressBar.UpdateProgressBar(completePercent);

    int readLength = bufferLength;

    if ((fileInfo.Length - bytesRead) < readLength)
    {
        // There is less in the file than the lenght I am going to read so change it to the 
        // smaller value
        readLength = (int)(fileInfo.Length - bytesRead);
    }

    char[] buffer = new char[readLength];

    // GEt the next chunk of the file
    bytesRead += (long)(fileReader.Read(buffer, 0, readLength));

    // This will help the file load much faster
    string currentLine = new string(buffer).Replace("\n", string.Empty);

    // Load in background
    this.Dispatcher.BeginInvoke(new Action(() =>
        {
            TextRange range = new TextRange(textBox.Document.ContentEnd, textBox.Document.ContentEnd);
            range.Text = currentLine;

        }), DispatcherPriority.Normal);
}

你,先生,是一个冠军! - Derek

2

我正在处理一个非常类似的项目。

这个项目包括加载一个大型文本文件(最大尺寸约为120MB,但我们想要更高),然后在树形结构中构建文本文件的概述。点击树中的节点将滚动用户到文本文件的那一部分。

经过与许多人的交谈,我认为最好的解决方案是创建一种“滑动窗口”查看器,在其中仅将用户每次能够看到的文本加载到rtb.Text中。

因此...将整个文件加载到List中,但仅将其中的100行放入rtb.Text。如果用户向上滚动,则删除底部行并将一行文本添加到顶部。如果他们向下滚动,则删除顶部行并将一行文本添加到底部。使用此解决方案可以获得相当不错的性能。(50秒即可加载120MB文件)


所有支持大文件的编辑器都通过一次显示有限数量的页面来工作。它们还使用自己的自定义控件来处理大量文本。有第三方控件支持数据虚拟化(仅加载和呈现文本的显示部分)。 - Panagiotis Kanavos

1
图形控件并不适合处理这种类型的数据,因为它将变得无法使用。即使控件可以处理大字符串,与整个文本相比,控件中可见的内容是微不足道的,滚动条实际上几乎没有用处。要在文本中定位特定行,您必须将滑块移动到最接近的位置,然后每次滚动一行需要几分钟时间…… 相比于这样毫无用处的操作,你应该重新考虑如何显示数据,以便以实际可用的方式进行操作。

6
这并不是一个不合理的要求。我可以在UltraEdit中轻松打开几GB的文件。虽然它不是RTF格式,但许多商业IDE(集成开发环境)都可以很好地处理10MB左右的文件——请记住,它们必须实际解析语言语法以获得漂亮的颜色,而不仅仅是读取预先渲染的格式。 - Richard Berg
1
我在很久以前就给它点了踩。正如我所说,原始问题是合理的。虽然WPF不太支持大型数据集,但许多其他图形控件(包括一些经典的Win32组件)确实可以做到这一点。更有帮助的答案将解释标准和虚拟数据绑定之间的区别,并可能提供有关如何在WPF中实现后者的提示。例如:http://blog.ramondeklein.nl/?p=24 - Richard Berg
2
我不明白你的帖子如何回答楼主的问题?你没有提供任何解决方案。一个好的答案应该是给出使用低级API实现所需性能的提示... - SepehrM
3
@Guffa 我明白了,但从教育的角度来看呢?假设我们只是想构建一个具有改进性能的文本框,尽管它在应用程序中并不实用... 值得一提的是,OP的问题是“有没有办法提高它的性能?”,而不是你对UI设计的看法。 - SepehrM
1
@SepehrM:那就是另一个问题了,这个问题是关于实际问题的实用解决方案。关于性能的问题,人们经常问关于他们认为是解决问题的最佳方式的解决方案,而不是询问问题本身。 - Guffa
显示剩余11条评论

0

你可以试试这个,它对我有效。

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    // Create new StreamReader
    StreamReader sr = new StreamReader(openFileDialog1.FileName, Encoding.Default);
    // Get all text from the file
    string str = sr.ReadToEnd();
    // Close the StreamReader
    sr.Close();

    // Show the text in the rich textbox rtbMain
    backgroundWorker1.ReportProgress(1, str);
}

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // richTextBox1.Text = e.ProgressPercentage.ToString() + " " + e.UserState.ToString();
    richTextBox1.Text = e.UserState.ToString();
}

0
为什么不将字符串变量(或者甚至使用StringBuilder)添加到一个变量中,然后在解析完毕后将其值分配给.Text属性呢?

-1

我没有改进加载性能,但我使用它来异步加载我的richtextbox。希望这可以帮到你。

XAML:

<RichTextBox Helpers:RichTextBoxHelper.BindableSource="{Binding PathFileName}" />

助手:

public class RichTextBoxHelper
{
private static readonly ILog m_Logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

public static readonly DependencyProperty BindableSourceProperty =
    DependencyProperty.RegisterAttached("BindableSource", typeof(string), typeof(RichTextBoxHelper), new UIPropertyMetadata(null, BindableSourcePropertyChanged));

public static string GetBindableSource(DependencyObject obj)
{
  return (string)obj.GetValue(BindableSourceProperty);
}

public static void SetBindableSource(DependencyObject obj, string value)
{
  obj.SetValue(BindableSourceProperty, value);
}

public static void BindableSourcePropertyChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
  var thread = new Thread(
    () =>
    {
      try
      {
        var rtfBox = o as RichTextBox;
        var filename = e.NewValue as string;
        if (rtfBox != null && !string.IsNullOrEmpty(filename))
        {
          System.Windows.Application.Current.Dispatcher.Invoke(
            System.Windows.Threading.DispatcherPriority.Background,
            (Action)delegate()
            {
              rtfBox.Selection.Load(new FileStream(filename, FileMode.Open), DataFormats.Rtf);
            });
        }
      }
      catch (Exception exception)
      {
        m_Logger.Error("RichTextBoxHelper ERROR : " + exception.Message, exception);
      }
    });
  thread.Start();
}
}

-1
你是否考虑过尝试将这个应用程序变成多线程的呢?
你需要一次看到文本文件的多少内容?你可能想要研究一下.NET中的懒加载或者在你的情况下是C#。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接