简体   繁体   English

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

[英]C# - Loading a large file into a WPF RichTextBox?

I need to load a ~ 10MB range text file into a WPF RichTextBox, but my current code is freezing up the UI. 我需要将一个~10MB范围的文本文件加载到WPF RichTextBox中,但我当前的代码正在冻结UI。 I tried making a background worker do the loading, but that doesnt seem to work too well either. 我尝试让后台工作人员进行加载,但这似乎也不太好用。

Here's my loading code. 这是我的加载代码。 Is there any way to improve its performance? 有没有办法改善其表现? Thanks. 谢谢。

    //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";
    }

WPF RichTextBox control use Flow Document to display Rich Text and then attach the Flow Document to RTB control,while Windows Form RichTextBox control display Rich Text directly. WPF RichTextBox控件使用Flow Document显示Rich Text,然后将Flow Document附加到RTB控件,而Windows Form RichTextBox控件直接显示Rich Text。 that's what makes WPF RTB super slow. 这就是WPF RTB超级慢的原因。 if you are okay with using a WinForm RTB just host it in your wpf app. 如果你可以使用WinForm RTB,只需在你的wpf应用程序中托管它。 the xaml : 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# code 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();
}

Graphical controls just isn't designed to handle that kind of data, simply because it would become unworkable. 图形控件并不是为处理那种数据而设计的,仅仅因为它变得不可行。 Even if the control could handle the large string, what's visible in the control is so little compared to the entire text that the scroll bars would become practically useless. 即使控件可以处理大字符串,控件中可见的内容与整个文本相比也是如此之少,以至于滚动条实际上​​变得无用。 To locate a specific line in the text you would have to move the slider to the closest position that it could specify, then scroll a line at a time for minutes... 要在文本中找到特定的行,您必须将滑块移动到它可以指定的最近位置,然后一次滚动一行几分钟......

Instead of submitting your users to something useless like that, you should rethink how you display the data, so that you can do it in a way that would actually be possible to use. 您应该重新考虑如何显示数据,以便您可以以实际可以使用的方式执行此操作,而不是将用户提交给无用的用户。

I'm working on a very similar project. 我正在做一个非常相似的项目。

The project entails loading a large text file (max size approx: 120MB but we want to go higher) and then constructing an outline of the text file in a tree. 该项目需要加载一个大文本文件(最大大小约120MB,但我们想要更高),然后在树中构建文本文件的大纲。 Clicking on a node in the tree will scroll the user to that portion of the text file. 单击树中的节点将用户滚动到文本文件的该部分。

After talking to a lot of people I think the best solution is to create a sort of "sliding window" viewer where you only load as much text as the user can see at a time into the rtb.Text. 在与很多人交谈之后,我认为最好的解决方案是创建一种“滑动窗口”查看器,您只需将用户一次可以看到的文本加载到rtb.Text中。

So.. say load the entire file into a List but only put 100 of those lines into rtb.Text. 所以..说将整个文件加载到List中,但只将100行放入rtb.Text。 If the user scrolls up remove the bottom line and add a line of text to the top. 如果用户向上滚动,请删除底行并在顶部添加一行文本。 If they scroll down remove the top line and add a line of text to the bottom. 如果他们向下滚动删除顶行并在底部添加一行文本。 I get pretty good performance with this solution. 我用这个解决方案获得了不错的表现。 (50s to load a 120MB file) (加载120MB文件50秒)

I have notice using RichTextboxes that as you add more "lines" it starts to slow down. 我注意到使用RichTextboxes,当你添加更多“行”时,它开始变慢。 If you can do it without appending the '\\n' it will speed up for you. 如果您可以在不附加'\\ n'的情况下执行此操作,它将为您加速。 Remember each '\\n' is a new paragraph object block for the RichTextbox. 请记住每个'\\ n'是RichTextbox的新段落对象块。

This is my method for loading a 10 MB file. 这是我加载10 MB文件的方法。 It takes about to 30 seconds to load. 加载大约需要30秒。 I use a progress bar dialog box to let my user know it is going to take time to load. 我使用进度条对话框让我的用户知道加载需要时间。

// 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);
}

为什么不添加到字符串变量(或者甚至可能使用StringBuilder),然后在解析完成后将值分配给.Text属性?

You can try this it worked for me. 你可以试试这个对我有用。

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();
}

I'm not improve the performance of loading, but I use it to load my richtextbox asynchronously. 我没有提高加载的性能,但我使用它来异步加载我的richtextbox。 I hope that could help you. 我希望能帮助你。

XAML : XAML:

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

Helper : 帮手:

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();
}
}

Have you considered trying to make the app multi-threaded? 您是否考虑过尝试使应用程序成为多线程?

How much of the text file do you need to see at once? 您需要立即查看多少文本文件? You may want to look into lazy-loading in .NET or in your case C# 您可能希望在.NET中或在您的情况下考虑延迟加载C#

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM