DataGridView line index cells
-
Hi, I'm trying to add line index cell to my DataGridView by iterating over the rows. However, when the list has many rows (hundreds), this loop takes very long time due to CPU consuming. The "heavy" part is converting the "int" to "String". The code:
for (int iRowIter = 0; iRowIter < this.Rows.Count; iRowIter++)
this.Rows[iRowIter].Cells[0].Value = iRowIter + 1;Any suggestions? Thanks!
-
Hi, I'm trying to add line index cell to my DataGridView by iterating over the rows. However, when the list has many rows (hundreds), this loop takes very long time due to CPU consuming. The "heavy" part is converting the "int" to "String". The code:
for (int iRowIter = 0; iRowIter < this.Rows.Count; iRowIter++)
this.Rows[iRowIter].Cells[0].Value = iRowIter + 1;Any suggestions? Thanks!
eyalbi007 wrote:
for (int iRowIter = 0; iRowIter < this.Rows.Count; iRowIter++) this.Rows[iRowIter].Cells[0].Value = iRowIter + 1;
IMO, Iterating and putting line number will not be the best option. Try handling RowPostPaint[^] or
RowPrePaint
event. You will get index of the current row. Using that get the row object and add line number (RowIndex + 1). This will avoid unnecessary iterations on the gridview. :)Best wishes, Navaneeth
-
Hi, I'm trying to add line index cell to my DataGridView by iterating over the rows. However, when the list has many rows (hundreds), this loop takes very long time due to CPU consuming. The "heavy" part is converting the "int" to "String". The code:
for (int iRowIter = 0; iRowIter < this.Rows.Count; iRowIter++)
this.Rows[iRowIter].Cells[0].Value = iRowIter + 1;Any suggestions? Thanks!
First of all, I don't actually see any code here that converts an int to a string. Second, I doubt your analysis is correct - doing this over a few hundred rows should *not* take long.
-
First of all, I don't actually see any code here that converts an int to a string. Second, I doubt your analysis is correct - doing this over a few hundred rows should *not* take long.
1. Since the relevant DataGridView cells display Strings, there is an automatic conversion from (iRowIter + 1), which is int, to Cells[0].Value which is String Object in this case. It's the same as
Cells[0].Value = Convert.ToString(iRowIter + 1)
2. My analysis is correct. I tried the following code:
Cells[0].Value = "1"
and it worked amazingly fast.
-
eyalbi007 wrote:
for (int iRowIter = 0; iRowIter < this.Rows.Count; iRowIter++) this.Rows[iRowIter].Cells[0].Value = iRowIter + 1;
IMO, Iterating and putting line number will not be the best option. Try handling RowPostPaint[^] or
RowPrePaint
event. You will get index of the current row. Using that get the row object and add line number (RowIndex + 1). This will avoid unnecessary iterations on the gridview. :)Best wishes, Navaneeth
Hi, First of all, thanks. Your solution saves the iterating part. However, the problem with using these events is that it's called every time the row is painted (for example, when scrolling). This slows down the response time since every time a row becomes visible (due to scrolling) the value of the index cell is set all over again. Even if I add "if" statement (in order to update the index cell only when required), scrolling after sorting becomes very slow since the index cell of the hidden rows wasn't updated yet. Any suggestions? Eyal.
-
1. Since the relevant DataGridView cells display Strings, there is an automatic conversion from (iRowIter + 1), which is int, to Cells[0].Value which is String Object in this case. It's the same as
Cells[0].Value = Convert.ToString(iRowIter + 1)
2. My analysis is correct. I tried the following code:
Cells[0].Value = "1"
and it worked amazingly fast.
First of all, C# doesn't allow implicit conversion from int to string, as demonstrated by this snippet:
void foo()
{
string s;
s = 5;
}Cannot implicitly convert type 'int' to 'string'. So that's not what's going on and your "equivalent statement" isn't equivalent at all. Second, DataGridViewCell.Value is not of type string - it's of type object. That means that if you assign a value type to it (such as int) there is a boxing operation, but a few hundred boxing operations (which is what you get with a few hundred rows) does NOT take long. Try it for yourself:
// Making this a public instance member guarantees the compiler cannot optimize away the operations.
public object obj;string test()
{
int count = 10000;
var w = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
obj = i;
}
w.Stop();
return string.Format("{0} boxing operations took {1}.", count, w.Elapsed);
}Show it in a messagebox or just set a breakpoint (after w.Stop(), obviously!) and see what you get. On my lameass workstation 10,000 boxing operations took 0.0001546 seconds. In a debug build. With the debugger attached. In short, the boxing is not going to make any noticeable difference. Something else is clearly going on here. That said, if assigning the string literal was so fast, why don't you try
Cells[0].Value = (iRowIter + 1).ToString();
instead? Calling ToString() on an int is not expensive either, so if there really is some oddity in the DataGridView that makes it freak out if it has to work with ints this should work around it. I find it difficult to believe such a major flaw in the component would have gone unnoticed though.
-
First of all, C# doesn't allow implicit conversion from int to string, as demonstrated by this snippet:
void foo()
{
string s;
s = 5;
}Cannot implicitly convert type 'int' to 'string'. So that's not what's going on and your "equivalent statement" isn't equivalent at all. Second, DataGridViewCell.Value is not of type string - it's of type object. That means that if you assign a value type to it (such as int) there is a boxing operation, but a few hundred boxing operations (which is what you get with a few hundred rows) does NOT take long. Try it for yourself:
// Making this a public instance member guarantees the compiler cannot optimize away the operations.
public object obj;string test()
{
int count = 10000;
var w = Stopwatch.StartNew();
for (int i = 0; i < count; i++)
{
obj = i;
}
w.Stop();
return string.Format("{0} boxing operations took {1}.", count, w.Elapsed);
}Show it in a messagebox or just set a breakpoint (after w.Stop(), obviously!) and see what you get. On my lameass workstation 10,000 boxing operations took 0.0001546 seconds. In a debug build. With the debugger attached. In short, the boxing is not going to make any noticeable difference. Something else is clearly going on here. That said, if assigning the string literal was so fast, why don't you try
Cells[0].Value = (iRowIter + 1).ToString();
instead? Calling ToString() on an int is not expensive either, so if there really is some oddity in the DataGridView that makes it freak out if it has to work with ints this should work around it. I find it difficult to believe such a major flaw in the component would have gone unnoticed though.
Thanks a lot, you are totally right! I found the problem: The column's AutoSizeMode property was set to DataGridViewAutoSizeColumnMode.AllCells, which dragged heavy calculations every time one of the column's cells value was changed. I changed it to DataGridViewAutoSizeColumnMode.None and now everything works smooth. In order to have the auto-size functionality, I've set the column's Resizable property to DataGridViewTriState.False, and added calls of AutoResizeColumn(ColumnIndex) where it's needed. Again, thanks a lot. Eyal.
-
Thanks a lot, you are totally right! I found the problem: The column's AutoSizeMode property was set to DataGridViewAutoSizeColumnMode.AllCells, which dragged heavy calculations every time one of the column's cells value was changed. I changed it to DataGridViewAutoSizeColumnMode.None and now everything works smooth. In order to have the auto-size functionality, I've set the column's Resizable property to DataGridViewTriState.False, and added calls of AutoResizeColumn(ColumnIndex) where it's needed. Again, thanks a lot. Eyal.
You're welcome! And thanks for posting the explanation. I knew it wasn't conversion from int to string that took the time, but I had no idea what it really was and your findings may be of use to someone else (including myself) some day. However, I think the way we're intended to avoid this is to call BeginEdit() and EndEdit() - this pattern is used with many controls to suppress expensive side-effects until EndEdit is called. For some reason I just didn't think about that.
DataGridView v;
v.BeginEdit();
try
{
// .. do a lot of editing
}
finally
{
v.EndEdit();
}(The BeginEdit is not part of the try block because we should only call EndEdit if BeginEdit has actually succeeded - though this is of course academic if the call to BeginEdit() always succeeds.)