remove first line in a textbox (readonly)

J

jleslie48

ok this should be easy but I can't find the solution.

I have a multiline readonly textbox that I am using as STDOUT.

when it gets too big I want to delete the oldest lines so I don't grow
too big.

I add lines with:

textbox01.appendText("hello world\n");

and methods like:

int linecount = textbox01.Lines.count();
string tempstring = textbox01.lines[5];

work,

but I want to be able to check if the linecount is greater than say,
1000,
then delete the first (aka oldest) 300 lines. This way the "stdout
file" is never more than 1000 lines big, and always has the last lines
written.

TIA,

Jleslie48
 
J

Jeff Johnson

ok this should be easy but I can't find the solution.

I have a multiline readonly textbox that I am using as STDOUT.

when it gets too big I want to delete the oldest lines so I don't grow
too big.

I add lines with:

textbox01.appendText("hello world\n");

and methods like:

int linecount = textbox01.Lines.count();
string tempstring = textbox01.lines[5];

work,

but I want to be able to check if the linecount is greater than say,
1000,
then delete the first (aka oldest) 300 lines. This way the "stdout
file" is never more than 1000 lines big, and always has the last lines
written.

....create a new string and copy textbox01.Lines[300] through
textbox01.Lines[textbox01.Lines.count() -1] to it and assign that as the new
Text property? I mean you basically described the solution in your question.
Am I missing something?
 
J

jleslie48

ok this should be easy but I can't find the solution.
I have a multiline readonly textbox that I am using as STDOUT.
when it gets too big I want to delete the oldest lines so I don't grow
too big.
I add lines with:
textbox01.appendText("hello world\n");
and methods like:
int linecount = textbox01.Lines.count();
string tempstring = textbox01.lines[5];

but I want to be able to check if the linecount is greater than say,
1000,
then delete the first (aka oldest) 300 lines. This way the "stdout
file" is never more than 1000 lines big, and always has the last lines
written.

...create a new string and copy textbox01.Lines[300] through
textbox01.Lines[textbox01.Lines.count() -1] to it and assign that as the new
Text property? I mean you basically described the solution in your question.
Am I missing something?

What your missing is that I'm a C programmer for 25 years and this C#/C
++ syntax/never ending list of methods is giving me the hives :)))

I originally tried it with a new instance of the textbox, but that
didn't work, and I wasn't sure how the string rewrite of the .text
method (or is it member??)
of the textbox1 instance would react to being stomped on. This seems
to work:


//_____________________________code section_______________________

output02.AppendText(textBox1.Text +"\r\n");

//tempstring = output02.Lines[4];
//tempint = output02.Lines.Count();
//tempint = output02.Lines[3].Length;

if (output02.Lines.Count() > 10) { //then
MessageBox.Show("textbox is over 10 removing
first 5 line");
for (int icount = 0; icount < 5; icount++) {
output02.Text =
output02.Text.Remove(0,output02.Lines[0].Length+2); //add 2 for the \r
\n in the append text.
}//for
}//then


output02.SelectionStart = output02.Text.Length;
output02.ScrollToCaret();



//_______________________________end code section_________________

but its sloppy with the for loop.
 
J

Jeff Johnson

What your missing is that I'm a C programmer for 25 years and this
C#/C++ syntax/never ending list of methods is giving me the hives :)))

I feel similarly about the STL! Honestly, it's all about practice.
Eventually you become familiar with it. Took me forever to get the hang of
streams, but now I know them backwards and forwards. (Well, backwards only
if they support seeking. HA HA! That was a geek joke. Laugh.)
I originally tried it with a new instance of the textbox, but that
didn't work, and I wasn't sure how the string rewrite of the .text
method (or is it member??)

It's a "property," actually.
of the textbox1 instance would react to being stomped on.

I'm in a good mood today so here's a function for you (complete with some
sanity checks at no cost!):

private void TrimOldLines(TextBox target, int linesToRemove)
{
if (!target.Multiline)
{
throw new Exception("Please use this method with Multiline text
boxes only.");
}

// Nothing to do if the text box doesn't have more lines
// than we're trying to remove
if (target.Lines.Length > linesToRemove)
{
StringBuilder newText = new StringBuilder();

for (int i = linesToRemove; i < target.Lines.Length; i++)
{
newText.AppendLine(target.Lines);
}

target.Text = newText.ToString();
}
}

(Note how I only replace the Text property once instead of constantly
altering it like your code sample did.)

You'll call it like this:

if (output02.Lines.Length > 1000)
{
TrimOldLines(output02, 300);
}
 
J

jleslie48

Excellent discussion Gentlemen, thank you for your inputs.

First off, both of you referred to xxxx.Length. I believe in both
cases you mean xxxx.Lines.Count()
the count of the number of lines of the multiline text box.

Pete you have concluded correctly that my 1000/300 split was exactly
an attempt to keep the program cpu from thrashing as the log file
grew. I will be taking a look at your custom control solution as
well.

Jeff, Sure you only replace output02.text once, but you still go
through the pain of a for loop to build up newText (also how does the
magic of memory allocation work here, my old school c training tells
me that the old target.text string[] structure has just become a
memory leak, and the newtext memory allocator in ??stringbuilder??
(more new stuff to me) is new allocation.) Actually this whole
memory allocation thing scares me in C# ala Microsoft. Too much
magic.

I was thinking the best solution was the old school C way. something
like this:

output02.Text =
output02.text.remove
( 0, //always the first character when looking at the Text as a
char[]
(&output02.Lines[301][0] - 1) - &output02.Lines[0][0])
);

that fancy line is address subtraction, I take the address of the
character just before the first character of the 301st line and
subtract from it the address of the first character period. This
should be the length of all the characters of the first 300 lines.

the question is, what has C# done to the syntax to accomplish this???
 
J

Jeff Johnson

Excellent discussion Gentlemen, thank you for your inputs.

First off, both of you referred to xxxx.Length. I believe in both
cases you mean xxxx.Lines.Count()
the count of the number of lines of the multiline text box.

Nope. The Lines property returns an array, and arrays have Length.
Collection objects have Count. [ And since it's a property, it's .Count, not
..Count(). ]
Jeff, Sure you only replace output02.text once, but you still go
through the pain of a for loop to build up newText (also how does the
magic of memory allocation work here, my old school c training tells
me that the old target.text string[] structure has just become a
memory leak, and the newtext memory allocator in ??stringbuilder??
(more new stuff to me) is new allocation.) Actually this whole
memory allocation thing scares me in C# ala Microsoft. Too much
magic.

If you want to write C, then write C. If you want to write C#, I recommend
you do it the C# way.

Strings are immutable, so every time you mess with the Text property you're
creating a new string. Therefore a second string is getting allocated no
matter what approach you take and you'll just have to accept that the old
string will be reclaimed by the Garbage Collector eventually.
 
J

Jeff Johnson

I will suggest that in Jeff's examples, it's not a great idea to
repeatedly retrieve the Lines property value. The array returned is
necessarily a copy of the actual data; at best, the array needs to be
created and initialized for each call to the getter, and it's entirely
possible all of the string instances are created as well.

Better would be to just get the property value once into a local variable
and then reuse that array instance as needed.

Looking at the property's implementation via Reflector, that's correct.
Getting a local copy is much better than repeatedly accessing Lines. Good
thing I never use that property in my own code!
 
J

jleslie48

jleslie48 said:
Excellent discussion Gentlemen, thank you for your inputs.
First off, both of you referred to xxxx.Length.   I believe in both
cases you mean xxxx.Lines.Count()
the count of the number of lines of the multiline text box.

Did you try compiling the code examples?  The Length property being used
is from the string[] array, not the textbox itself.

I will suggest that in Jeff's examples, it's not a great idea to
repeatedly retrieve the Lines property value.  The array returned is
necessarily a copy of the actual data; at best, the array needs to be
created and initialized for each call to the getter, and it's entirely
possible all of the string instances are created as well.

Better would be to just get the property value once into a local
variable and then reuse that array instance as needed.

But as far as the Length vs Count() thing goes, inspecting the Length
property of the string[] array is in fact correct.
Pete you have concluded correctly that my 1000/300 split was exactly
an attempt to keep the program cpu from thrashing as the log file
grew.  I will be taking a look at your custom control solution as
well.
Jeff, Sure you only replace output02.text once, but you still go
through the pain of a for loop to build up newText (also how does the
magic of memory allocation work here, my old school c training tells
me that the old target.text string[] structure has just become a
memory leak, and the newtext memory allocator in ??stringbuilder??
(more new stuff to me) is new allocation.)   Actually this whole
memory allocation thing scares me in C# ala Microsoft.  Too much
magic.

It's not magic.  It's garbage collection.  Memory resources cannot be
leaked at all in .NET, not in the conventional sense.  Your own code can
mistakenly keep references to memory you never actually will use again,
but only if the memory is actually reachable by your code some way will
it remain allocated.

I'm not really sure what concern you have with Jeff's code anyway, with
respect to memory allocations.  The only object that he explicitly
allocates (and thus does not explicitly free) is the StringBuilder
instance.  The AppendLine() method may indeed need to reallocate the
internal buffer, but you could see exactly the same kind of API in a
C/C++ library (and in fact, you do when using STL types like vector).
The allocations and memory management are encapsulated within the class
and as such, they "just work".  :)

It's true he doesn't explicitly free the StringBuilder instance itself.
  But that's just how a GC-based system works.

As for Jeff's loop, as my own example shows, I agree that there is a
more efficient way to approach the problem.  But Jeff's version is
definitely better than the one you posted, which has multiple problems:

   • Setting the Text property repeatedly is inefficient
   • Retrieving the Lines property repeatedly is inefficient
   • Using the Lines property as a way of analyzing the line lengthis
inefficient
   • Assuming that the text always uses "\r\n" as the line terminator is
incorrect
   • Creating a new string instance each iteration of the loop is
inefficient


I was thinking the best solution  was the old school C way.  something
like this:
output02.Text =
   output02.text.remove
      ( 0,  //always the first character when looking at the Text as a
char[]
        (&output02.Lines[301][0] - 1) - &output02.Lines[0][0])
      );
that fancy line is address subtraction, I take the address of the
character just before the first character of the 301st line and
subtract from it the address of the first character period.  This
should be the length of all the characters of the first 300 lines.
the question is, what has C# done to the syntax to accomplish this???

Your "old school" C way doesn't work anyway (not even counting the more
mundane bugs in the code).  You can only perform the pointer arithmetic
that way if your array is a plain multi-dimensional array, where the
length of each row is a constant for the entire array.  But the array
returned by the Lines property is an array of strings, each string
containing (essentially) an array of characters.

Even in C or C++, that's not compatible with the plain multi-dimensional
array assumption.

Now, that said, it's _possible_ there may be some efficiency gain by
taking advantage of the TextBox methods that manage line/character
positions and operate on the text directly.  Specifically, here's an
alternative implementation of the Trim() method I posted earlier:

   void Trim(TextBox text, int max, int remove)
   {
     int count = text.GetLineFromCharIndex(text.TextLength) + 1;

     if (count > max)
     {
       int removeChars = text.GetFirstCharIndexFromLine(remove);

       text.Select(0, removeChars);
       text.SelectedText = "";
     }
   }

This implementation has the potential to be the _most_ efficient way to
accomplish it, at least while still using the built-in TextBox control.
  That's because it allows all of the complicated logic to occur within
the control implementation itself, operating only in the internal data
structures without having to present the internal data to the client
code via managed objects like instances string or string[].  But, at the
same time, you are then delegating the efficiency quality to that
implementation, trusting that they are doing the right thing.

Sometimes that works out.  Sometimes it doesn't.  :)  I can't tell you
whether for the TextBox class the above is actually going to be better.
  But you can easily try it and find out yourself (noting, of course
that whatever the current performance characteristics today, with a new
release of the library it could change).

Pete

Ok, gentlemen, I see that the code does in fact work.
textbox.Lines.length is the number of elements of the array of
strings. Its value is the same as textbox.lines.count(), but I concede
to you all as the more experienced programmers that the "C#" way it to
use the the Lines.length PROPERTY is the standard usage. I don't
understand why there are two ways to the same information.


I am trying to do it "the C# way"

thanks again for the advice.

jon
 
J

Jeff Johnson

Ok, gentlemen, I see that the code does in fact work.
textbox.Lines.length is the number of elements of the array of
strings. Its value is the same as textbox.lines.count(), but I concede
to you all as the more experienced programmers that the "C#" way it to
use the the Lines.length PROPERTY is the standard usage. I don't
understand why there are two ways to the same information.

Actually, the Count() thing is relatively new. I'm afraid I'm a dinosaur who
hasn't moved very far beyond Framework 2.0, and these extension methods were
new with 3.0, so for the 99.9999% of my code that doesn't have

using System.Linq;

in it, I will never see a Count() method and I'm not used to thinking about
it. I'm just stuck in 2005, I guess....
 
J

jleslie48

Or it could have been 3.5. They came out really close together.

OH I forgot. I'm using VS Express 2010 .NET 4.0 C#.

anyway, this is all very new to me and a big departure from unix and
c; my comfy place. These endless lists of members/properties/methods
are a bit overwhelming.

I will get to the finer issues of IEnumerable Interface vs. the length
vs count() in due course. Right now I'll be happy just manipulating
my data reasonably, and in a C# way.


I found it far more straightforward with C with just plain old data
and the data's pointer(address). I only had 2 things I had to consider
when moving things around. Shoot, this whole issue would of reduced
to implementing a circular queue with a head and tail pointer on a
char[60000] in that world and all would of been simple and fast, not
one bit of data moving around.

But I'm a dinosaur and its time to evolve.

so meantime in my travels I came across this bit of code that works:

if (output02.Lines.Count() > 10) { //then
output02.Text = output02.Text.Remove(0,
output02.GetFirstCharIndexFromLine(5));
}//then

so it looks like textbox wrapped up my address pointer in a member
function. I still liked &text.....lines[5][0] :))))
 
J

jleslie48

Actually, the Count() thing is relatively new. I'm afraid I'm a dinosaur who
hasn't moved very far beyond
it. I'm just stuck in 2005, I guess....



trust me, your a mammal at least. I'm still banging rocks together
compared to you.

I'm just pulling random stuff together to try and figure out how to
get my work done in this Microsoft environment. I've managed to avoid
for so long but It's tome to evolve.

Shoot I still have code running on DEC/Vax'es written in Pascal.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top