简体   繁体   中英

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. 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. that's what makes WPF RTB super slow. if you are okay with using a WinForm RTB just host it in your wpf app. the 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

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. 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.

So.. say load the entire file into a List but only put 100 of those lines into 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)

I have notice using RichTextboxes that as you add more "lines" it starts to slow down. If you can do it without appending the '\\n' it will speed up for you. Remember each '\\n' is a new paragraph object block for the RichTextbox.

This is my method for loading a 10 MB file. It takes about to 30 seconds to load. 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. I hope that could help you.

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#

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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