3

I have a win GUI application written in C#, in which I have a TextBox component that I write my log to. at some point it gets too loaded and the entire app is starting to falter. I'd like to make an efficient mechanism to make it a FIFO, meaning - make it a fixed size and deleting the oldest content automatically.

is there any .net/c# feature for doing that? otherwise, what will be the right way to do this?

UPDATE: I also have this issue with other sort of textual data, and not only with logs. therefor, the ListBox is not a proper solution for me.

  • Use a ListBox and remove items. – SLaks Sep 24 '13 at 17:27
  • my content is free text, rather than line-oriented, so it won't due. – Edgar James luffternstat Sep 24 '13 at 17:30
  • I doubt there's anything built-in. My approach would be to use some sort of [data-binding](http://msdn.microsoft.com/en-us/library/c8aebh9k(v=vs.110).aspx) or MVVM, and maintain a model that has: **1.** a collection of log entries that behaves in a FIFO manner; and **2.** a computed property called, say `LogText`, that joins together these lines (or strings). You would bind the textbox to this computed property, and whenever a log entry is added, you emit a change notification for the computed property and the textbox should update automatically. – millimoose Sep 24 '13 at 17:32
  • (This should probably be as efficient as this gets - for rendering in a textbox, you necessarily have to at some point create one uberstring. To get rid of that step you'd have to display the data in some other way.) – millimoose Sep 24 '13 at 17:34
  • is this `winforms` or `WPF`? – King King Sep 24 '13 at 17:34
  • Why not take a substring of the current text, append your text to it, and write the new string in? – MildWolfie Sep 24 '13 at 17:38
  • You may be interested in [My Example](http://stackoverflow.com/a/16745054/643085) of a [High Performance](http://www.youtube.com/watch?v=D3Y6DnFpHCA), [Highly Customizable](http://stackoverflow.com/q/15532639/643085) log Viewer using Current, Relevant .Net Windows UI technologies. It doesn't require any horrible hacks such as the one you're talking about here. And supports high loads of data with really tight update frequencies. – Federico Berasategui Sep 24 '13 at 18:10

6 Answers6

6

While I'm sure there is a better solution - when I think FIFO - I think Queue. So, you could do something like make a Queue of strings to add your log items to and set up an integer to represent max number of log items.

private Queue<string> logQueue = new Queue<string>();
private const int logMax = 100;

then you could set up a logging method like so.

public void Log(string logText)
{
    // this should only ever run for 1 loop as you should never go over logMax
    // but if you accidentally manually added to the logQueue - then this would
    // re-adjust you back down to the desired number of log items.
    while (logQueue.Count > logMax - 1)
        logQueue.Dequeue();

    logQueue.Enqueue(logText);
    textBox.Text = string.Join(Environment.NewLine, logQueue.ToArray());
}

This should get you the functionality that you are looking for.

The one obvious drawback with this approach - is that you have your text stored in memory twice. Once in the queue and once as a concatenated string in the text box. If this isn't a big issue for you - then this may work.

Sean Hosey
  • 323
  • 1
  • 7
5

To create a circular buffer for text, I'd use a StringBuilder, with the capacity set to about twice the amount of data I want to display.

const int DisplaySize = 10000;
StringBuilder fifo = new StringBuilder(2 * DisplaySize);

string AppendToFifo( string s )
{
    if (s.Length >= DisplaySize) {
        // FACT: the display will only include data from s
        // therefore, toss the entire buffer, and only keep the tail of s
        fifo.Clear();
        fifo.Append(s, s.Length - DisplaySize, DisplaySize);
        return fifo.ToString();
    }
    if (fifo.Length + s.Length > fifo.Capacity) {
        // FACT: we will overflow the fifo
        // therefore, keep only data in the fifo that remains on the display
        fifo.Remove(0, fifo.Length + s.Length - DisplaySize);
    }
    fifo.Append(s);
    if (fifo.Length <= DisplaySize) {
        // FACT: the entire fifo content fits on the display
        // therefore, send it all
        return fifo.ToString();
    }
    // FACT: the fifo content exceed the display size
    // therefore, extract just the tail
    return fifo.ToString(fifo.Length - DisplaySize, DisplaySize);
}

The fast path, when none of the if conditions are true, avoids all unnecessary copies (in the .NET world where strings are immutable, the final copy to create the output string cannot be avoided). And in the other cases only needed characters are copied. Increasing the capacity of the buffer will improve utilization fraction of the fast path. What I've been careful to avoid doing is creating a string object with the old content that remains on the display, which has no purpose except to concatenate with the new content, and immediately becomes garbage.

Obviously, if you use p/invoke to pass a pointer to the StringBuffer's content instead of copying out a substring, that will be even more efficient.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • SBs are designed to efficiently append; they aren't particularly efficient when you start removing from the middle of the string or appending information to the middle of the string. – Servy Sep 24 '13 at 17:54
  • @Servy: I never insert (which you wrongly called "append to the middle") or "remove from the middle". I do remove from the beginning, but only rarely. And even that is cheap, because I do one remove operation that gets rid of over half the buffer -- only content being kept needs to be copied. – Ben Voigt Sep 24 '13 at 17:59
  • Selected. I just wonder what are the deltas in terms of efficiency/resources consumption when comparing to simple solution suggested by caackley. – Edgar James luffternstat Sep 24 '13 at 18:22
  • 1
    @EdgarJamesluffternstat: I'd expect this to create about half as many garbage strings for the GC to clean up, since his creates two and passes one of those to the `Text` property, while mine creates one (which you would pass to the `Text` property). I think doing better would require p/invoke (and I believe that p/invoke to `SetWindowText` would cut out another copy along with the remaining garbage string objects). – Ben Voigt Sep 24 '13 at 18:27
1

You could try something like this.

public void updateMyTextBox( string newText )
{
    // Get the current text, and append the newText to the end
    string text = MyTextBox.Text;
    text += newText;

    // Ensure text does not exceed maximum length
    if( text.Length > MaxLength ) // Max Length constant declared elsewhere
        text = text.Substring( text.Length - MaxLength );

    // Update MyTextBox
    myTextBox.Text = text;       
}

This is a very simple solution, so you'd need to do some extra work to get it to check for new lines and other such conditions, but it's where I would start.

MildWolfie
  • 2,492
  • 1
  • 16
  • 27
  • 1
    This should work, but it creates a garbage string of size `MaxLength + newText.Length` on every call. That's what my answer carefully avoids. – Ben Voigt Sep 24 '13 at 18:00
  • @BenVoigt Ahh, good to know. I'm still new to c#, so little tidbits like that are very helpful. – MildWolfie Sep 24 '13 at 18:04
  • Of course mine is MUCH more complicated than this, so it probably should be wrapped in a `StringTail` class or something like that, to hide the complexity from the calling code. – Ben Voigt Sep 24 '13 at 18:07
0

You can use the ListBox control and add new items to the beginning of the list, like this:

ListBox1.Items.Insert(0, "Newest Item");

Then you can remove the oldest from the list box, like this:

ListBox1.Items.RemoveAt(ListBox1.Items.Count - 1);
Karl Anderson
  • 34,606
  • 12
  • 65
  • 80
0

It kind of depends on your needs and the look you are after.

The approach I took was to creat A Scrolling Status Control that automatically tracks the last n lines of output, scrolls as needed, and renders the output correctly.

I was kind of going for the scrolling text window at the bottom of Visual Studio such as during compilation, but I ended up with something far less functional. But it is extremely light on resources. You might take a look and see if something similar would work for you.

Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
-1

// LIFO Stack simple

    private void btnSend_Click(object sender, EventArgs e) // on button press
    {
        string oldText = rtbReceive.Text;          // copy old text from richTextBox
        rtbReceive.Clear();                        // Clear richTextBox

        rtbReceive.AppendText(tbSend.Text + "\r\n"); // add new text from textBox to richTextBox
        rtbReceive.AppendText(oldText);              // add old text to richTextBox

    // Best Regards by Petar Ivanov Upinov (BG)
    }