In this article we will describe existing solutions presenting data in grids. There are following kinds of grids:
Grids working in the real mode (without virtualization)
Grids with virtualization
Data-table like grids
Grids with data binding
These grids have an internal data structure presented by a list of rows. Each row is presented by a list of items shown in grid cells. Generally, these cells are accessed by referencing to the row and column. The interface may look like:
grid.Rows[index][column] = "something";
For example, CListCtrl of MFC library offers the following syntax:
lvi.iSubItem = 1;
lvi.pszText = _T("something");
where lvi corresponds to a row, and lvi.iSubItem - to a 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 cell dimensions, and at reception WM_PAINT it draws "something" string in the right position. This behaviour is transparent for the programmer.
There are few advantages of this mode. The single advantage is that it is easy.
Limitations of this design:
- There is no separation of data from its presentation.
- Maintaining the same data in various grids is a complex operation.
- Data sorting may give unexpected results (because data are compared by formatted values) Ex: if you compare two strings "2" and "10", then the result will be "2" > "10", which is wrong.
- Data filtering is a 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 also labor-consuming operations. You can find difficulties when filtering data or swapping columns.
- Data identification issuers (if two or more rows have identical formatted content).
- 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 the feature of Windows operation system where the painting mechanism is divided in two parts. If you want to design something somewhere, you should first call Invalidate()
function of CWnd
with specified bounds. Windows cumulates rectangles, and then it outputs WM_PAINT
message. By receiving this message, the control can draw text, lines, etc in requested bounds. All grids use this mechanism directly or indirectly. In case of virtualization the grid does not keep formatted data, but requests it at painting time.
- Low memory and CPU load
- Unlimited customization.
- Programmers should have some technical skills to implement this algorithm and understand principles of data virtualization.
- Everything must be done manually (sorting, selection, filtering, etc.). Many features (edit in place, hierarchy, highlighting, etc...) require heavy implementation.
- When a programmer wants to update data, he must find a rectangle, where the data is located and then invalidate it. This is not an intuitive step.
- The code implemented by a programmer is generally not reusable.
The next step to simplify programmer's life is data table-like grids. The main approach is to create an intermediate level presented by a 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). Data are presented through formats that transform these values into strings. One or multiple grids can listen to notifications from the same table and automatically update the visible content.
Access to data looks as follows:
table[row][column] = my_value
- Partial data separation from its presentation (through formats)
- The same data may be presented in many grids
- Sorting gives the right results
- Filtering becomes possible
- Easy to implement
- 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 search and identification. You should give the right 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 search twice (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 GUI tread. This may have greatly impact application 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. It permits to greatly simplify the application by separating business data from their presentation. Data-table approach limits 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 functions may correspond to columns. The mechanism that enables retrieving data from the object by calling its functions is called data binding. If the object offers a notification mechanism, and the grid implements it, programming becomes very easy. Thanks to it, you will have a clear business model and after 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.
- Programming becomes very easy
- Clear data separation from its presentation
- Many features may be implemented by the grid and not by a programmer
- Low memory load (reference to your business data and binding table)
- High performance (depends on implementation)
- If data binding uses string identifiers to identify object methods, then there are no verifications at compilation time which can result in detecting errors at a later stage.
- Generally, this mechanism is not thread-safe. This fact restricts application development. Indeed, all graphical components can work in a single thread. Therefore you must call set methods of your business object only in GUI thread. Synchronization with this thread happens by calling SendMessage or PostMessage methods. To call these functions, you need to know a handler to the window or have a reference to CWnd object.
- The main approach to populate the grid with business objects is to give some container of business objects (or IEnumerable interface in ..Net platform), which complicates hierarchy building.
We offer the solution based on the data binding thread-safe mechanism. This means that the grid can receive data updates from any thread and perform 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.