The purpose of this article is to provide some general programming practice guidelines and troubleshooting tips to improve performance when using the UltraWinGrid control. Not all of the tips provided here will apply to every application, but it should help with the most common performance issues a developer is likely to encounter.
If you are concerned about reducing the amount of memory used by the grid, then there are several important things you can do to reduce it.
The grid does not create UltraGridCell objects for every row of data. Cells are only created as neccessary. So whenever possible, you should avoid accessed a cell. For example, suppose your code is using the InitializeRow event of the grid in order to calculate a total. For example:
}
This code references three cells in each row of the grid, thus creating a lot of objects which are potentially unneccessary. This can be avoided using methods on the row to deal with cell values, rather than the cell objects themselves. Like so:
By using the GetCellValue method, we can eliminate 2 cells per row in this example and save memory.
Another common use for the InitializeRow event is to apply an appearance to a cell based on the Value of that cell. Applying an appearance to a cell requires getting a reference to the UltraGridCell, so that is unavoidable. But you can save memory by re-using the same appearance object for multiple cells. Suppose, for example, that you want to change the ForeColor of a cell to red for negative numbers and black for positive numbers. You might do something like this:
This code will create a cell for every row. That is unavoidable, since the cell must be created in order to have an Appearance. The bigger problem here is that the Appearance property on the cell is lazily created. That means that we are not only creating a cell for each row, but a new Appearance object for each row. And since there are only two possible colors, we end up creating a large number of identical appearance objects.
A better way to do this is to create the Appearances we need up front and then re-use the same appearance wherever it is needed.
Another benefit to this approach is that you can change the appearance everywhere en masse. For example, suppose your users want to use Green instead of Black for positive appearances. You could, at run-time, set the ForeColor of the “Positive” Appearance object, and every cell in the grid that was using that appearance would update automatically.
I just did a benchmark to the GetCellValue. With 100000 rows of three integers I saved 28MB of memory! (but just 0.2 seconds) now I'm going to replace it.
Just to be easy, you can add too extension methods so you can get the value by the column name or index:
{
Great article Mike!
i have a first question: Why exist the Value property(i mean the get part of this property) of a cell and GetValueCell method?
thank you
The Value property on the cell is more convenient to use and easier to discover so it makes working with the grid more intuitive. In a case where the cell has already been created, it's easier to use Value then to walk up to the Row and use GetCellValue. Also, you need the Value property for the setter. :)
Mike, great article. You article is a great headup and comes at a very good time personally since I would designing for the next phase of my project that involves changing Appearance for cells. Here's the problem:
- Depending on the user's selection, the data pulled over from server may have as many as 100 rows and 100 columns (about 10000 cells)
- Depending on some application setting, half of these cells may need to be have it Appearance set by the business logic
- The business logic has four parameters and affects Appearance differently. Two parameters affects the background color, one Appearance affects the bolding of foreground and last one affects the foreground.
- These four parameters are independent i.e. so one does not affect the other. If the two parameter that affect background happen together, then one takes precedence over the other.
___________________________________________________________________
Question:
- Reading your article, seems like I would be cause instantiation of the Cell Object for all those cells that the business logic wants to set the Appearance for. If that is around 5000 cells, how big a perrformance concern is that?
- Reading your article, I will be instantiating about 5000 Appearance object for each Cell. How big a concern is that?
- In your article you suggest caching the copy of a Appearance and reusing that. In my case, the Appearance is a result of some combination of the independent four parameters. I will have to create an elaborate scheme to cache for various parameter combination. You think that would worth doing?
- Any other suggestions?
vrn said:- Reading your article, seems like I would be cause instantiation of the Cell Object for all those cells that the business logic wants to set the Appearance for. If that is around 5000 cells, how big a perrformance concern is that?
A lot of this will depends on the machine you are running on. Obviously, a faster machine with more memory will be better able to handle this than a slower one with less memory. So it's hard for me to say whether 5000 cells will be too many. The best thing for you to do would be to protoype it and try it out on a typical user machine and see how it goes.
vrn said:- Reading your article, I will be instantiating about 5000 Appearance object for each Cell. How big a concern is that? - In your article you suggest caching the copy of a Appearance and reusing that. In my case, the Appearance is a result of some combination of the independent four parameters. I will have to create an elaborate scheme to cache for various parameter combination. You think that would worth doing?
From your description here, it sounds like you don't really need 5000 appearance objects. You have a limited number of appearances that you actually need. You can certainly limit the number of appearances you create to only the combinations you need and then re-use the same appearances for different cells. What I would do in a case like this is write a sort've caching class with a Dictionary. The key of the dictionary would be some class that contains the information you need to get the appearance - whatever factors are important. Then you use that object to see if the apearance already exists in the dictionary, and if so, you use it, otherwise, you create it and add it to the dictionary.
It's really not all that elaborate a scheme, I've done this myself many times. The trickiest part is creating some object that contains the information you need. You have to override Equals and GetHashTable on this key object so that the dictionary works correctly, but it's really not that hard.
vrn said:- Any other suggestions?
If it turns out that the caching is too complex or 5000 cells is too much, there is another way you can apply appearances to the cells without forcing the creation of more than a few cells and without creating any appearance objects at all. You could use a DrawFilter, instead. The DrawFilter will only apply to cells that are actually visible on the screen. It's essentially like overring the OnPaint, so you are affecting the painting of the grid without using Appearances. Cells that are displayed on the screen are already created, anyway, so there's no concern about creating unneccessary cells inside the Drawfilter.
This will drastically reduce the amount of memory your application needs. But the down side is that a DrawFilter is more complicated to create.