views:

2027

answers:

3

I am inserting a column in a DataGridView programmatically (i.e., not bound to any data tables/databases) as follows:

int lastIndex = m_DGV.Columns.Count - 1;  // Count = 4 in this case
DataGridViewTextBoxColumn col = (DataGridViewTextBoxColumn)m_DGV.Columns[lastIndex];
m_DGV.Columns.RemoveAt(lastIndex);
m_DGV.Columns.Insert(insertIndex, col);  // insertIndex = 2

I have found that my columns are visually out of order sometimes using this method. A workaround is to manually set the DisplayIndex property of the column afterwards. Adding this code "fixes it", but I don't understand why it behaves this way.

Console.Write(m_DGV.Columns[0].DisplayIndex); // Has value of 0
Console.Write(m_DGV.Columns[1].DisplayIndex); // Has value of 1
Console.Write(m_DGV.Columns[2].DisplayIndex); // Has value of 3
Console.Write(m_DGV.Columns[3].DisplayIndex); // Has value of 2
col.DisplayIndex = insertIndex;
Console.Write(m_DGV.Columns[0].DisplayIndex); // Has value of 0
Console.Write(m_DGV.Columns[1].DisplayIndex); // Has value of 1
Console.Write(m_DGV.Columns[2].DisplayIndex); // Has value of 2
Console.Write(m_DGV.Columns[3].DisplayIndex); // Has value of 3

As an aside, my grid can grow its column count dynamically. I wanted to grow it in chunks, so each insert didn't require a column allocation (and associated initialization). Each "new" column would then be added by grabbing an unused column from the end, inserting it into the desired position, and making it visible.

+2  A: 

I suspect this is because the order of the columns in the DataGridView do not necessarily dictate the display order, though without explicitly being assigned by default the order of the columns dictate the DisplayIndex property values. That is why there is a DisplayIndex property, so you may add columns to the collection without performing Inserts - you just need to specify the DisplayIndex value and a cascade update occurs for everything with an equal or greater DisplayIndex. It appears from your example the inserted column is also receiving the first skipped DisplayIndex value.

From a question/answer I found:

Changing the DisplayIndex will cause all the columns between the old DisplayIndex and the new DisplayIndex to be shifted.

As with nearly all collections (other than LinkedLists) its always better to add to a collection than insert into a collection. The behavior you are seeing is a reflection of that rule.

cfeduke
Additionally due to the separation of the underlying Columns data structure and its display ordering from DisplayIndex usage, optimization by adding columns in chunks is not necessary and may only complicate things.
cfeduke
A: 

Thanks to cfeduke for excellent advice. I suspected Insert would be slower, but the provided link enlightened me on JUST HOW MUCH slower.

This brings up the question of how to efficiently insert and remove columns dynamically on a DataGridView. It looks like the ideal design would be to add plenty of columns using Add or AddRange, and then never really remove them. You could then simulate removal by setting the Visible property to false. And you could insert a column by grabbing an invisible column, setting its DisplayIndex and making it visible.

However, I suspect there would be landmines to avoid with this approach. Foremost being that you can no longer index your data in a straightforward manner. That is, m_DGV.Columns[i] and m_DGV.Rows[n].Cells[i] will not be mapped properly. I suppose you could create a Map/Dictionary to maintain an external intuitive mapping.

Since my application (as currently designed) requires frequent column insertion and removal it might be worth it. Anyone have any suggestions?

e-holder
+1  A: 

I have a couple of ideas.

  1. How about addressing your columns by a unique name, rather than the index in the collection? They might not already have a name, but you could keep track of who's who if you gave them a name that meant something.

  2. You can use the GetFirstColumn, GetNextColumn, GetPreviousColumn, GetLastColumn methods of the DataGridViewColumnCollection class, which work on display order, not the order in the collection. You can also just iterate through the collection using a for loop and m_DGV.Columns[i] until you find the one you want.

  3. Create an inherited DataGridView and DataGridViewColumnCollection. The DataGridView simply is overridden to use your new collection class. Your new DataGridViewColumnCollection will include a method to address the collection by display index, presumably by iterating through the collection until you find the one you want (see #2). Or you can save a dictionary and keep it updated for very large numbers of columns.

    I doubt the performance increase of keeping a dictionary, since every time a column moves, you essentially have to rewrite the entire thing. Iterating through is O(n) anyway, and unless you're talking asynchronous operations with hundreds of columns, you're probably okay.

    You might be able to override the this[] operator as well, assuming it doesn't screw up the DataGridView.

Idea #1 might be the easiest to implement, but not necessarily the prettiest. Idea #2 works, and you can put it in a function DataGridViewColumn GetColumnByDisplayIndex(int Index). Idea #3 is cute, and certainly the most encapsulated approach, but isn't exactly trivial.

lc