Hi,
I need to set the appearance (backcolour, forecolour, fontdata.name) of a large number of cells in a grid (over 100,000), and wonder which approach is recommended to a) reduce memory usage and b) achieve reasonable performance.
Initially, I was using code like the following:
cell.Appearance.ForeColor = colorBilledAndPaid;
and I noticed that the heap contained 130 thousand Infragistics.Win.Appearance objects (as reported by windbg) -- approximately 1 per cell whose Appearance was being changed. I noticed another post on this forum suggesting that, to improve performance, cell Appearances should be set using predefined Appearance objects, so I changed to using code like the following:
// during form startup:
paidCellAppearance = (Infragistics.Win.Appearance)cellAppearance.Clone(); paidCellAppearance.ForeColor = colorPaid; paidCellAppearance.FontData.Bold = DefaultableBoolean.True;
// later, when painting the cells:
cell.Appearance = paidCellAppearance;
Unfortunately, this approach used even more memory, and when I checked the heap using windbg there were now over 100,000 UltraGridCell objects (but the Infragistics.Win.Appearance had been reduced to a few dozen, as expected).
Is it normal that setting the Appearance of a cell would cause a new object to be created in memory (one per cell)? If so, is there a different approach that is recommended when setting these colour and font properties for a large number of cells?
If the per-cell memory usage isn't normal, then do you have any suggestions on what I might be doing to cause these UltraGridCell objects to be created on the heap? The code that I'm using to access the cells involves hundreds of lines across 10 methods so I won't paste it all here, but the important parts are:
foreach (UltraGridRow row in gridBuyLines.Rows.GetAllNonGroupByRows()) {
// paint the child band UltraGridRow airing = row.GetChild(ChildRow.First); while (airing != null) { paintRow(airing); airing = airing.GetSibling(SiblingRow.Next); } // paint the parent row paintRow(row); }
...
private void paintRow(UltraGridRow row) {
for (int intWeekNumber = 0; intWeekNumber < IntNumWeeks; intWeekNumber++) { string strColumnName = "IntAiringsWeek" + (intWeekNumber + 1); row.Cells[strColumnName].Appearance = paidCellAppearance; // actually,various if statements are used to determine which Appearance object to use here -- it isn't always paidCellAppearance, and not every cell has its Appearance set, but over 100,000 cells do }
}
Sorry if this problem description is somewhat lame. If the 2nd approach that I used is the right one but I need to set the .Appearance property more sparingly, then I'm OK with that. (Until now, I hadn't realized that setting Appearance at the cell level was going to be a performance problem). But I just wanted to make sure that I wasn't barking up the wrong tree before restructuring the code.
Thanks,
Dan.
Hi Mike,
Yes, you're right, when coded properly the approach of creating Appearance objects up-front uses less memory. In my case, it ended up using more memory because my new code was setting the Appearance property on a lot more cells than before, resulting in a lot more UltraGridCell objects on the heap. I hadn't realized that each cell reference uses memory, as well as each Appearance property setting.
Your suggestion of doing the Appearance settings in the InitializeRow event also greatly reduced the memory used when loading the grid.
I'm still fine-tuning the performance of this grid, but the things that have helped reduce memory usage so far are:
1. Set the appearance of as many cells as possible at the Band, Column or Row level, rather than the Cell level. For example, my drill-down rows in band 1 have a different background colour than the rows in band 0, so it seems to be best to set that colour at the band level:
band1.Override.RowAppearance.BackColor = Color.OldLace;
2. When changing the appearance of cells based on their content, put the code in the InitializeRow event, rather than looping through all of the rows after loading the data.
3. Set the Appearance property using pregenerated .Appearance objects, as described in my original post. As you say, this greatly reduces the number of Appearance objects on the heap, without affecting the number of UltraGridCell objects.
Runtime speed has also improved. I was concerned that change #2 might cause scrolling through a large grid to be sluggish, but there is no problem there.
Thanks for your help,
Hi Dan,
There's nothing lame about your description at all, it's all very clear.
Everything you describe here is perfectly normal and correct.
If you set a property on an Appearance, then the grid lazily creates an appearance object. So if you are going to be applying the same set of colors and settings to a lot of different cells, the best thing to do is use InitializeLayout event and use e.Layout.Appearance and create all the appearances you need up front. Then you assign them to the cells inside the InitializeRow event.
The only thing I am a little fuzzy on is why this approach would use MORE memory than the approach of setting an appearance on each cell. I think you must be mistaken there. Whether you set the cell.Appearance.BackColor to a color or set the cell.Appearance to an Appearance object, the grid has to create a cell object in memory. So both approaches would be creating 100,000+ cells, but only the first approach would create 100,000+ appearance objects.
One thing you might try is to use the InitializeRow event instead of looping through all of the cell in the grid. This probably won't make much difference in you only have a single band in your grid because the grid will iniitalize all of the root-level rows anyway by default (although there may be a way around this - see the next paragraph). But if your grid is a hierarchy, then this will spare you from creating cells in the child rows until they are expanded.
Also, it might help you to set the e.Layout.LoadStyle to LoadOnDemand. This will tell the grid to only load rows as needed for display. So this will help with the root-level rows. But this will not help if you sort or filter the grid because that will force all rows to be loaded.
Another thing to watch out for is your 'if' statements that are examining the cell value. You are probably doing something like this:
if (e.Row.Cells["My Cell"].Value == x)
This code refers to a cell, though. So you will be creating the cell object even if you don't apply an appearance to this cell. A better way to do it is like so:
if (e.Row.GetCellValue(e.Row.Band.Columns["My Cell"]) == x)
This gets the value of the cell without creating the cell object.
Finally, if none of this helps you enough, you can probably use a DrawFilter instead of using Appearances. Using the DrawFilter, you can basically intercept the drawing of the cell and change the colors right before it paints. The tricky part of this is determining which elements draw which part of the cell. If you want to try this approach, I recommend checking the Infragistics KnowledgeBase for articles and samples of DrawFilters. Also, get the Infragistics UIElementViewer Utility. It will be a huge help in identifying UIElements.