WinForms Textbox Changing its margins? - c#

So I've been working on cobbling together a game and decided I'd like to have a little program to show a file with each character replaced by its byte equivalent for working with coding saves and whatnot. Figured it'd a layup. Three hours later, I've been wracking my brain trying to figure this out.
When I load a small (or perhaps short is the better term) file it looks like the window on top. When I load a larger file, it looks like the window on the bottom.
http://dl.dropbox.com/u/16985121/Images/ViewAsBytes.PNG
That's 10pt Courier New, but it seems to happen with any font I try. There's always that extra column, and if there wasn't enough room for the column, it'd just squeeze in whatever it could in that space that it previously didn't use. I've tried tweaking all kinds of variables, as well as comparing the textbox before and after it adds the text from the file (which is read in just as bytes from a FileStream and then fed into a StringBuilder) but nothing seems to change even though something is clearly different.
I can think of a bunch of different workarounds for this, but now I'm just more interested in what TextBox thinks it's doing exactly than getting my program done. Anyone got any idea?
Here's the code that reads in the data and puts that to the textbox:
FileStream stream = new FileStream(files[0], FileMode.Open);
StringBuilder sb = new StringBuilder();
int byteIn = stream.ReadByte();
while (byteIn != -1)
{
sb.Append('[');
if (byteIn < 100)
sb.Append('0');
if (byteIn < 10)
sb.Append('0');
sb.Append(byteIn.ToString());
sb.Append(']');
byteIn = stream.ReadByte();
}
txtView.Text = sb.ToString();
stream.Close();

This is because you set to the WordWrap property to True. Set it to False, set Multiline to True and ScrollBars to Both. Append Environment.NewLine to the string you generate, every 16 bytes is the norm for hex viewers. Use byte.ToString("X2") to generate a hex string instead of a decimal string.
You now have a full scrollable view of the data, any amount is supported. Allow the user to resize the window so she won't have to scroll horizontally. Or just make it big enough.

Related

.NET Windows forms DataGridView Cell text disappears when added programatically

I am developing a Windows forms application that includes a DataGridView. This DataGridView has 3 columns, all of which are simply text cells:
Timestamp
Connection
Message
The issue I'm running into is that when I add a row (programmatically), I'm finding that the text disappears if it is too long. To be specific, if the text exceeds 4563 characters in length, then the text disappears.
I know that the DataGridViewTextBoxColumn class has a property call MaxInputLength that can limit the number of characters entered. But according the the Documentation, it only affects text that is input manually by the user. I, however, am inputting this text programmatically.
Just to make sure though, I set this property very high but the disappearing text issue still arises when I pass the 4563 character limit.
One thing I have noticed is that the text is still there (i.e. the scroll bar along the bottom can still be scrolled as though the text is still there) but I cannot see the text itself. I can also edit the text.
I can add characters until the 4563 limit but as soon as I pass that, the text disappears. If I press backspace to return to exactly 4563 characters, the text reappears.
I am developing this using .NET 4.0, since I have to support Windows XP.
Here's the short answer that will probably disappoint you: It's a reported bug and verified by Microsoft, closed as "Not important enough to fix". There may be more instances of it, but it's been known since at least 2011 DataGridView control shows blank cell if large string is entered and column resized to max. The "workaround" is to just limit the size of the cells width, but for you that may not be satisfactory.
However, curiosity got the best of me so I started looking into it a little deeper; Here's the first observation worth mentioning:
If you look at the series of pictures, you'll notice I replicated your problem with the default font size/style and the specific number 5460. What's so special about 5460? Well, nothing in particular, except that as your character threshold crosses it the ContentBounds and Width of the column passes 32767. What's so special about 32767? Other than being the default MaxInputLength of a DataGridViewTextBoxCell, it's the upper limit of a signed short or Int16 (2^15-1). I highly doubt it's a coincidence the issue is occurring here, though not cause of anything to do with MaxInputLength per se. I'd be willing to bet you first noticed the issue at 4563 characters because your font size expanded the width to 32767 as well.
The next question, is why? I'm not really sure. I started following the rabbit hole and disassembled some of the .NET 4.0 DataGridView* libraries to find out. It's a pretty massive and complicated control, and I haven't been able to draw any definite conclusions, but one thing I found that's worth noting is the absolute maximum width a column can assume is 65536, the value of an UNsigned Int16 (2^16):
You see this check in a lot of private internal places when adding or resizing a column, and I tested it. The size won't go larger
This is ironic for two reasons. For one, using the default settings, you can only display 10922 characters (65536 / 6 pixels per character) in a column despite the editing input length being 32767 characters, and programmatically arbitrary.
Second, why would this issue start cropping up at exactly the max of the signed variant of the columns max width? Hmmmm. This is totally a guess, but I think somewhere along the line the max value for whatever renders the text was set as a regular short instead of an unsigned short... or something along those lines. I have my suspicions of the PaintPrivate() method in the implementation of DataGridViewTextBoxCell(), so if you're feeling frisky, maybe put a microscope to it. You'll need an IL disassembler to see this stuff that's not exposed publicly. Specifically, this part of the code I have suspicions of:
if (text != null && (paint && !flag2 || computeContentBounds))
{
int y = cellStyle.WrapMode == DataGridViewTriState.True ? 1 : 2;
rectangle3.Offset(0, y);
// ISSUE: explicit reference operation
// ISSUE: variable of a reference type
Rectangle& local = #rectangle3;
// ISSUE: explicit reference operation
int width = (^local).Width;
// ISSUE: explicit reference operation
(^local).Width = width;
rectangle3.Height -= y + 1;
if (rectangle3.Width > 0 && rectangle3.Height > 0)
{
TextFormatFlags cellStyleAlignment = DataGridViewUtilities.ComputeTextFormatFlagsForCellStyleAlignment(this.DataGridView.RightToLeftInternal, cellStyle.Alignment, cellStyle.WrapMode);
if (paint)
{
if (DataGridViewCell.PaintContentForeground(paintParts))
{
if ((cellStyleAlignment & TextFormatFlags.SingleLine) != TextFormatFlags.Default)
cellStyleAlignment |= TextFormatFlags.EndEllipsis;
TextRenderer.DrawText((IDeviceContext) graphics, text, cellStyle.Font, rectangle3, flag3 ? cellStyle.SelectionForeColor : cellStyle.ForeColor, cellStyleAlignment);
}
}
else
rectangle1 = DataGridViewUtilities.GetTextBounds(rectangle3, text, cellStyleAlignment, cellStyle);
}
Sorry for the book!
TL;DR USE A SMALL ASS FONT IF YOU WANT TO PACK CHARACTERS INTO HUGE CELLS.

Why does textbox overflow slow down the program so significantly?

I made an application (something like Google Maps) and I added a textbox field to which debugging data were written (of course I meant to remove it afterwards). The interesting fact is that after it was "full" let's say several kilobytes - the whole program slowed down significantly and needed to be exited because one could not work with it.
Could you please explain?
Well, it is surely more than a couple of kilobytes. But yes, TextBox is pretty unsuitable as a control to display tracing information. Every time you add a new line, it must re-allocate its internal buffer, merging the old text with the new text. It is the exact same kind of problem with .NET's String class. With the StringBuilder class as a workaround, but no equivalent exists for TextBox.
Another option that makes TextBox very slow when you add a lot of lines is the WordWrap property. Setting it to True requires it to do a lot of work to figure out the length of each line every time it paints itself.
So workarounds are to leave WordWrap set to False and to prevent the amount of text from growing boundlessly by throwing half of it away whenever the length reaches a limit. Or by using a different control, TextBox isn't very suitable anyway since it doesn't make sense to edit tracing data. Like ListBox.
Instead of appending a little data at a time, eg:
debugTextBox.Text += "Some new debug info"
Perhaps this stragegy might be faster:
StringBuilder debugText = new StringBuilder();
...
debugText.Append("Some new debug info");
debugTextBox.Text = debugText.ToString();
(although StringBuilder is probably overkill for this, and may prove slower than just working directly with string concatenations against a string debugText)

Highlighting in a RichTextBox is taking too long

I have a large list of offsets which I need to highlight in my RichTextBox. However this process is taking too long. I am using the following code:
foreach (int offset in offsets)
{
richTextBox.Select(offset, searchString.Length);
richTextBox.SelectionBackColor = Color.Yellow;
}
Is there a more efficient way to do so?
UPDATE:
Tried using this method but it doesn't highlight anything:
richTextBox.SelectionBackColor = Color.Yellow;
foreach (int offset in offsets)
{
richTextBox.Select(offset, searchString.Length);
}
I've googled your issue and I found that RichTextBox is getting very slow when having many lines. In my opinion, you have either buy a third part control which you can be satisfied by its performance or you may need threads to devide the whole selection task. I think they can accelerate things up.
Hope it helps !
I've had this same problem before. I ended up disregarding all of the methods they give you and manipulated the underlying RTF data. Also, the reason that your second block of code doesnt work is that RTF applies formatting as it goes, so if you call a function (or Property in this case) to change the selection color, it will only apply it for the currently selected block. Any changes made to the selection after that call become irrelavent.
You can play around with the RGB values, or here is a great source on how to do different things within the RTF control. Pop this function in your code and see how well it works. I use it to provide realtime syntax highlighting for SQL code.
public void HighlightText(int offset, int length)
{
String sText = richTextBox.Text.Trim();
sText = sText.Insert(offset + length - 1, #" \highlight0");
sText = sText.Insert(offset, #" \highlight1");
String s = #"{\rtf1\ansi\deff0{\fonttbl{\f0\fnil\fcharset0 Courier New;}}
{\colortbl ;\red255\green255\blue0;}\viewkind4\uc1\pard";
s += sText;
s += #"\par}";
richTextBox.Rtf = s;
}
Does it make any difference if you set the SelectionBackColor outside of the loop?
Looking into the RichTextBox with Reflector shows, that a WindowMessage is sent to the control every time when the color is set. In the case of large number of offsets this might lead to highlighting the already highlighted words again and again, leading to O(n^2) behavior.

How can I add a huge string to a textbox efficiently?

I have a massive string (we are talking 1696108 characters in length) which I have read very quickly from a text file. When I add it to my textbox (C#), it takes ages to do. A program like Notepad++ (unmanaged code, I know) can do it almost instantly although Notepad takes a long time also. How can I efficiently add this huge string and how does something like Notepad++ do it so quickly?
If this is Windows Forms I would suggest trying RichTextBox as a drop-in replacement for your TextBox. In the past I've found it to be much more efficient at handling large text. Also when making modifications in-place be sure to use the time-tested SelectionStart/SelectedText method instead of manipulating the Text property.
rtb.SelectionStart = rtb.TextLength;
rtb.SelectedText = "inserted text"; // faster
rtb.Text += "inserted text"; // slower
Notepad and Window TextBox class is optimized for 64K text. You should use RichTextBox
You could, initially, just render the first n characters that are viewable in the UI (assuming you have a scrolling textbox). Then, start a separate thread to render successive blocks asynchronously.
Alternatively, you could combine it with your input stream from the file. Read a chunk and immediately append it to the text box. Example (not thorough, but you get the idea) ...
private void PopulateTextBoxWithFileContents(string path, TextBox textBox)
{
using (var fs = File.OpenRead(path))
{
using (var sr = new StreamReader(fs))
{
while (!sr.EndOfStream)
textBox.Text += sr.ReadLine();
sr.Close();
}
fs.Close();
}
}

.NET C# - Random access in text files - no easy way?

I've got a text file that contains several 'records' inside of it. Each record contains a name and a collection of numbers as data.
I'm trying to build a class that will read through the file, present only the names of all the records, and then allow the user to select which record data he/she wants.
The first time I go through the file, I only read header names, but I can keep track of the 'position' in the file where the header is. I need random access to the text file to seek to the beginning of each record after a user asks for it.
I have to do it this way because the file is too large to be read in completely in memory (1GB+) with the other memory demands of the application.
I've tried using the .NET StreamReader class to accomplish this (which provides very easy to use 'ReadLine' functionality, but there is no way to capture the true position of the file (the position in the BaseStream property is skewed due to the buffer the class uses).
Is there no easy way to do this in .NET?
There are some good answers provided, but I couldn't find some source code that would work in my very simplistic case. Here it is, with the hope that it'll save someone else the hour that I spent searching around.
The "very simplistic case" that I refer to is: the text encoding is fixed-width, and the line ending characters are the same throughout the file. This code works well in my case (where I'm parsing a log file, and I sometime have to seek ahead in the file, and then come back. I implemented just enough to do what I needed to do (ex: only one constructor, and only override ReadLine()), so most likely you'll need to add code... but I think it's a reasonable starting point.
public class PositionableStreamReader : StreamReader
{
public PositionableStreamReader(string path)
:base(path)
{}
private int myLineEndingCharacterLength = Environment.NewLine.Length;
public int LineEndingCharacterLength
{
get { return myLineEndingCharacterLength; }
set { myLineEndingCharacterLength = value; }
}
public override string ReadLine()
{
string line = base.ReadLine();
if (null != line)
myStreamPosition += line.Length + myLineEndingCharacterLength;
return line;
}
private long myStreamPosition = 0;
public long Position
{
get { return myStreamPosition; }
set
{
myStreamPosition = value;
this.BaseStream.Position = value;
this.DiscardBufferedData();
}
}
}
Here's an example of how to use the PositionableStreamReader:
PositionableStreamReader sr = new PositionableStreamReader("somepath.txt");
// read some lines
while (something)
sr.ReadLine();
// bookmark the current position
long streamPosition = sr.Position;
// read some lines
while (something)
sr.ReadLine();
// go back to the bookmarked position
sr.Position = streamPosition;
// read some lines
while (something)
sr.ReadLine();
FileStream has the seek() method.
You can use a System.IO.FileStream instead of StreamReader. If you know exactly, what file contains ( the encoding for example ), you can do all operation like with StreamReader.
If you're flexible with how the data file is written and don't mind it being a little less text editor-friendly, you could write your records with a BinaryWriter:
using (BinaryWriter writer =
new BinaryWriter(File.Open("data.txt", FileMode.Create)))
{
writer.Write("one,1,1,1,1");
writer.Write("two,2,2,2,2");
writer.Write("three,3,3,3,3");
}
Then, initially reading each record is simple because you can use the BinaryReader's ReadString method:
using (BinaryReader reader = new BinaryReader(File.OpenRead("data.txt")))
{
string line = null;
long position = reader.BaseStream.Position;
while (reader.PeekChar() > -1)
{
line = reader.ReadString();
//parse the name out of the line here...
Console.WriteLine("{0},{1}", position, line);
position = reader.BaseStream.Position;
}
}
The BinaryReader isn't buffered so you get the proper position to store and use later. The only hassle is parsing the name out of the line, which you may have to do with a StreamReader anyway.
Is the encoding a fixed-size one (e.g. ASCII or UCS-2)? If so, you could keep track of the character index (based on the number of characters you've seen) and find the binary index based on that.
Otherwise, no - you'd basically need to write your own StreamReader implementation which lets you peek at the binary index. It's a shame that StreamReader doesn't implement this, I agree.
I think that the FileHelpers library runtime records feature might help u. http://filehelpers.sourceforge.net/runtime_classes.html
A couple of items that may be of interest.
1) If the lines are a fixed set of characters in length, that is not of necessity useful information if the character set has variable sizes (like UTF-8). So check your character set.
2) You can ascertain the exact position of the file cursor from StreamReader by using the BaseStream.Position value IF you Flush() the buffers first (which will force the current position to be where the next read will begin - one byte after the last byte read).
3) If you know in advance that the exact length of each record will be the same number of characters, and the character set uses fixed-width characters (so each line is the same number of bytes long) the you can use FileStream with a fixed buffer size to match the size of a line and the position of the cursor at the end of each read will be, perforce, the beginning of the next line.
4) Is there any particular reason why, if the lines are the same length (assuming in bytes here) that you don't simply use line numbers and calculate the byte-offset in the file based on line size x line number?
Are you sure that the file is "too large"? Have you tried it that way and has it caused a problem?
If you allocate a large amount of memory, and you aren't using it right now, Windows will just swap it out to disk. Hence, by accessing it from "memory", you will have accomplished what you want -- random access to the file on disk.
This exact question was asked in 2006 here: http://www.devnewsgroups.net/group/microsoft.public.dotnet.framework/topic40275.aspx
Summary:
"The problem is that the StreamReader buffers data, so the value returned in
BaseStream.Position property is always ahead of the actual processed line."
However, "if the file is encoded in a text encoding which is fixed-width, you could keep track of how much text has been read and multiply that by the width"
and if not, you can just use the FileStream and read a char at a time and then the BaseStream.Position property should be correct
Starting with .NET 6, the methods in the System.IO.RandomAccess class is the official and supported way to randomly read and write to a file. These APIs work with Microsoft.Win32.SafeHandles.SafeFileHandles which can be obtained with the new System.IO.File.OpenHandle function, also introduced in .NET 6.

Categories