James Reever
James Reever

Reputation:

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

Upvotes: 8

Views: 19898

Answers (8)

Harshil
Harshil

Reputation: 1300

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

Upvotes: 3

Franck Charlier
Franck Charlier

Reputation: 439

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

Upvotes: -1

David Basarab
David Basarab

Reputation: 73351

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

Upvotes: 2

coco
coco

Reputation: 9

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

Upvotes: 0

Rick
Rick

Reputation:

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)

Upvotes: 2

Guffa
Guffa

Reputation: 700840

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.

Upvotes: 1

ist_lion
ist_lion

Reputation: 3209

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#

Upvotes: -1

AlaaShaker
AlaaShaker

Reputation: 203

Why don't you add to a string variable (or perhaps even use StringBuilder) then assign the value to the .Text property when you're done parsing?

Upvotes: 0

Related Questions