I have a test app for some grid stuff I'm doing. The app has 4 buttons and allows me to bind different types of data to the grid. I can't actually include all the code involved, but the problem appears to be very isolated.
When I call UltraGridColumn.CalculateAutoResize, from time to time (and there doesn't appear to be a rhyme or reason to it), it throws an IndexOutOfRangeException with this call stack:
at Infragistics.Shared.SparseArray.ValidateIndex(Int32 index) at Infragistics.Shared.SparseArray.GetItem(Int32 index, ICreateItemCallback createItemCallback) at Infragistics.Win.UltraWinGrid.ScrollCountManagerSparseArray.GetItem(Int32 index, Boolean create) at Infragistics.Win.UltraWinGrid.RowsCollection.InternalTraverseRowsHelper(UltraGridBand band, IRowCallback rowCallback, IRowsCollectionCallback rowsCollectionCallback, Boolean recursive, Boolean includeTemplateAddRows) at Infragistics.Win.UltraWinGrid.UltraGridColumn.CalculateMaxCellTextWidth(Int32 maxColWidth, RowsCollection rows, Int32 nRows, Int32 maxRowsCollections) at Infragistics.Win.UltraWinGrid.UltraGridColumn.PerformAutoResizeHelper(RowsCollection rows, Int32 nRows, Boolean applyWidth, Boolean includeHeader, Boolean includeCells, Int32 maxRowsCollections) at Infragistics.Win.UltraWinGrid.UltraGridColumn.PerformAutoResizeHelper(RowsCollection rows, Int32 nRows, Boolean applyWidth, Boolean includeHeader, Boolean includeCells) at Infragistics.Win.UltraWinGrid.UltraGridColumn.CalculateAutoResizeWidth(PerformAutoSizeType autoSizeType, Boolean includeHeader) at DTC.NTFFV.Utilities.UltraGridBindingExtensions.GridExtensions.HandleColumnAttributes(UltraGrid grid, UltraGridColumn col, Type containedType, UltraGridLayout layout) in C:\DTCDev\NTFFV\Utilities\UltraGridBindingExtensions\Domain\GridExtension.cs:line 529
The code in question is:
int calculatedWidth = col.CalculateAutoResizeWidth(PerformAutoSizeType.AllRowsInBand, true) + 8;
Obviously col is a valid UltraGridColumn. What could I possibly be doing that would cause CalculateAutoResize to throw this exception?
Thanks
Pete
As an additional note, the resizing is taking place during the CurrencyManager.ListChanged event. That is, I do something along these lines:
CurrencyManager cm = BindingContext[grid.DataSource, grid.DataMember] as CurrencyManager;cm.ListChanged += new ListChangedEventHander(cm_ListChanged);
then in my cm_ListChanged, I'm looping through all the columns in the grid and performing the autoresize.
I have created a separate app that just does that and I cannot reproduce the error, but it occurs to me that that might be relevant in diagnosing the issue, since I'm sure the grid is also handling the CurrencyManager.ListChanged event.
I appear to have found a solution (leading from my last post). But I'd like some assurnace that this is the correct fix.
Instead of performing the resizing directly in the ListChanged event, I'm doing a SynchronizationContext.Post() call to the method that does the resizing. This seems to avoid the exception. I'm assuming that the grid is performing some work during ListChanged that's not complete until, perhaps, some later event gets called, and doing the auto-resize during this event is catching it off-guard.
I'm not familiar with SynchronizationContext.Post().
But your guess seems reasonable to me. I don't think ListChanged is a good place to AutoSize column because there is no way you can determine whether you got the ListChanged event before or after the grid, and if you get it after, the grid could be in the middle of some asynchronous processing.
Unfortunately, I cannot think of an alternative to ListChanged that will tell you when a cell value changes and which column the cell was in. So if you must use ListChanged, I would probably use a BeginInvoke to call a method that AutoSizes the column instead of calling the CalculateAutoSize from directly inside your ListChanged event handler. That should introduce enough of a delay so that the grid will finish whatever processing it's doing before the auto size happens. I imagine SynchronizationContext.Post() is doing something similar.
Mike,
Once I tracked it down the ListChanged being the common denominator in the exceptions, I suspected it was probably conflicting with the grid.
I wasn't aware the grid did async processing.
SynchronizationContext.Post() is independent of WinForms, but when you use it in a WinForms app, under the hood, it uses a WinForms specific implementation that, in fact, does a BeginInvoke(). (There are separate ASP.NET, Silverlight, and WPF implementations).
Thanks for the information.
Hi Pete,
Yes, there are many cases where the grid processes notifications from the DataSource asynchronously. There are a couple of reasons for this.
One of efficiency. If you make 1000 changes to rows in your data source, the grid performance would be horrific if it responded synchronously to each notification. So it just marks the rows collection dirty and re-synchronizes it the next time the grid paints or someone asks for the collection.
Another reason is that the BindingManager notification in Dotnet are a bit quirky sometimes. Notifications don't always come in the order you might expect, or even in the same order all the time. So doing things asynchronously is the only way to avoid certain problems with some data sources.
I actually wrote some grid databinding code back in .NET 1.1, so I'm pretty familiar with the quirks of CurrencyManager and WinForms databinding. I haven't looked at it lately, but I remember a lot of the CurrencyManager stuff being pretty poorly documented and, in some cases, incorrectly documented. For example, the documentation for some of the virtual methods used to say, "A derived class should blah blah blah..." But the constructor for CurrencyManager is internal, so you can't derive from it. In fact, I think in 1.1 CurrencyManager was sealed.
Needless to say, figuring out how to do databinding (and in this case, it was supporting hierarchical data) to the grid was interesting.