In this article we will give description of existing solutions to present data in grids. There are the next kinds of grids:
Grids working in the real mode (without virtualization)
Grids with virtualization
Data-table like grids
Grids with the data binding
These grids have an internal data structure, which is presented by a list of rows. Each row is presented by a list of items that are shown in the cells of the grid. Generally, an access to these cells happens by referencing the row and column. The interface may look like:
grid.Rows[index][column] = "something";
For example, the CListCtrl of the MFC library offers the next syntax:
LVITEM lvi;
lvi.iSubItem = 1;
lvi.pszText = _T("something");
grid.SetItem(&lvi);
where lvi corresponds to the row, and lvi.iSubItem to the column. As you see, this looks like a bi-dimensional array, where all data is stored as formatted strings. When you call grid.SetItem(...), behind the grid calls Invalidate() with the dimensions of the cell, and at reception WM_PAINT it draws the string "something" in the right position. This behaviour is transparent for the programmer.
There are not a lot of advantages of this mode. The single advantage is that it is easy.
Limitations of this design:
- There is no separation the data from its presentation.
- Maintenance the same data in various grids is a complex operation.
- Data sorting may give unexpected results (because the data comparing occurs by the formatted values) Ex: if you compare two strings "2" and "10", then the result will be "2" > "10", which is wrong.
- Data filtering is very complex operation - you will look for the rows, check visibility conditions and then will add or remove rows manually.
- Finding and updating the data are expensive operations too. You can find difficulties when you filter data or swap the columns.
- Data identification issuers (if two or more rows have identical formatted content)
- The grids of this type are very slow and take a lot of memory and CPU resources.
- Limited customization.
The main idea of this approach is based on peculiarity of the Windows operation system where the painting mechanism is separated on two parts. If you want to design something somewhere, firstly you should call the
Invalidate() function of the
CWnd with specified bounds. Windows cumulates rectangles, and then it fires
WM_PAINT message. By receiving this message, the control has a possibility to draw text, lines, etc in the requested bounds. All grids use directly or indirectly this mechanism. In case of virtualization the grid does not keep formatted data, but ask it at painting time.
Advantages:
- Low memory and CPU consumption
- Unlimited customization.
Main problems:
- The programmers should have some technical skill to implement this algorithm and understand principles of the data virtualization.
- Everything must be done manually. Sorting, selection, filtering, etc. Many features (edit in place, hierarchy, highlighting, etc...) ask for heavy implementation.
- When the programmer wants to update the data, it must find a rectangle, where the data is located and then invalidate it. This is not intuitive step.
- The code, implemented by the programmer generally is not reusable.
The next step to simplify the programmer's life is the data-table like grids. The main approach is to create an intermediate level, presented by the data table. Such table contains bi-dimensional array (rows & columns) which can be bound to the grid. The main difference between the real-mode and table-like grids is that the table contains an array of non-formatted values (long, double, etc... but not formatted strings). The presentation happens through formats that transform these values to strings. The one or multiple grids can listen for notifications from the same table and automatically update the visible content.
Access to the data looks like:
table[row][column] = my_value
Advantages:
- Partially data separation from its presentation (through formats)
- The same data may be presented in many grids
- Sorting gives the right results
- Filtering is now possible.
- Easy to implement
Difficulties:
- It is difficult to build the hierarchy by using the data table model (by nature it is flat).
- Mainly, the datatable model is an intermediate level between your business data and the grid. Finally, you have to populate the table from somewhere and this may be not easy. There is another problem - the data finding and identification. You should give the right the row index and column to set or get value.
- In some cases, when you want to know where your business data is presented in the grid, you have to do a double find (in the table and in the grid).
- Generally, the pair grid - datatable is not thread safe. Therefore, when you develop your business data model, you have to know, where it will be used and to do synchronization with the GUI tread. This may have a big impact on the application's design.
- The grid is slow.
- In some implementations the value referenced by table[row][column] may be untyped and multiple castings are possible.
- Some problems with data table structure refactoring (adding/removing the columns, changing their type, swapping, etc.)
This is a real revolution in the grid design. This permits to greatly simplify the application by separating the business data from the presentation. Data-table approach limits the data organization and adds an intermediate level between business data and its presentation. What is new in this mechanism? Each object may have some data and functions that get or manipulate this data. Such object may be directly inserted into the grid and the functions of this object can give information for cells. That means that the functions may correspond to the columns. The mechanism that permits to retrieve data from the object by calling its functions is called data binding. If the object offers a notification mechanism, and the grid implements it, then the programming becomes very easy. Thanks to it you will have a clear business model and after the binding you can forget that your object is presented somewhere. If you call set-functions of your object, each grid that is bound to your data will update, sort, filter, etc... your data. You don't need to worry where it is presented.
Advantages:
- Programming becomes very easy
- Clear data separation from its presentation
- Many features may be implemented by the grid and not by the programmer
- Low memory consumption (reference to your business data and binding table)
- High performance (depends on implementation)
General problems:
- If data binding uses string identifiers to identify methods of the object, then there are no verifications at compile time which can lead to late-detected errors.
- Generally, this mechanism is not thread-safe. This fact limits a design of the application. Indeed, all graphical components can work in a single thread. Therefore you must call set- methods of your business object only in the GUI thread. Synchronization with this thread happens by calling SendMessage or PostMessage methods. To call these functions, you need to know a handle to the window or have a reference to the CWnd object.
- The main approach to populate the grid with business objects is to give some container of business objects (or IEnumerable interface in the .Net platform), which complicates the hierarchy building.
We offer the solution, which is based on the data binding thread-safe mechanism. This means, that the grid can receive data updates from any thread and performs the synchronization itself. Moreover, our solution is not only thread-safe, but also dead-lock free because the data updating doesn't locks the calling thread.