The grid can display objects of any class. Class objects correspond to grid lines, and their functions correspond to columns. Values returned by these functions are displayed through formats in grid cells. This gives enormous advantages as compared to grids that do binding to object containers or present data as arrays of strings. How does it work? Let's take a simple class:
CMyClass(double price, decimal quantity);
double GetPrice() const;
void SetPrice(double price);
long GetQuantity() const;
This class has some public methods and private fields. Now, let's explain how values returned by GetPrice() function can be shown in the grid. When you create a new object, memory is allocated only to the values declared in this object (if there are no virtual functions). There is also memory allocation for methods, but the compiler turns it into a code segment. Each method can have its pointer. The declaration of the method pointer looks like
typedef double (CMyClass::*pfnGetDouble)() const;
A non-static method can be called only together with the object. Without the object this doesn't make sense. The method is called and data are retrieved as follows:
double price = (object->*pfnGetDouble)();
This is a fundamental principle of the Common
Now, when one can call methods, let's add the notion of formats. The values returned by functions can't be presented directly in the grid. The values can have integer, float or other type. The grid can display only strings. Indeed, the integer value 123456 can be presented in different way as '123456' or '0x1E240' etc. So the form of presentation depends on the format that can convert the value into string. The Common library offers a number of formats to convert value into string and vice-versa. Each format may be bi-directional. When a user types a string in the cell, the typed string can be parsed in the format and transformed into the value. Then this value can be passed to SetPrice() function of the object.
The last step is to explain how a function can be associated with grid columns and what the Field of data object is. Data object is your own object of an arbitrary class. This class may have various Get and Set functions that manipulate internal data of the object. Get and Set functions can be grouped in the field. So the field is a pair of Get and Set functions with a format to transform the value into a string and vice-versa. The field has its own type (long, double etc). To identify the field numeric identifiers are used. Why? To avoid binding by strings that can lead to further errors when an identifier is changed in one place and is left unchanged in another one. Another reason is that numeric identifiers may be expressed by enumerations, which is good practice. So you can detect the errors at compilation stage.
Set function of the field permits to transform string or data obtained from edit-in-place controls (edit box, dropdown list etc.) to a value and pass it to a business object by the described mechanism.
Now, an important issue is multithreading protection. Indeed, MFC controls can work only in a single thread. They are not thread-safe. Any call from any non-GUI thread can lead to crash of the application. So, once you implemented notifications, the grid can get them from any thread. There are many aspects of this problem:
- How to protect the grid?
- Whatís the balance between performance and crash risks?
- How does the application overhead in an easy way without thread-safe mechanisms?
- Where is a borderline between crash and deadlock risks?
Well, let's explain how it works. All Grid functions are thread-safe. They use two main algorithms: with or without blocking the calling thread. We don't mean that calling these functions in any thread is the best practice but itís the only way the grid can work properly in case of such call. All functions that add, remove or get data block the calling thread for period of operation. If you listen for some notifications from the grid, be aware that there is an implicit lock that holds the calling thread. Internally the grid doesn't call methods of business data that can lead to deadlock. So, be careful with the notification handlers.
Data are updated without locking the calling thread. There is no risk of the deadlock, but it is up to you to protect your business data. We implemented two algorithms of data updating:
- Dapfor::GUI::CPreferences::DirectCall: The grid asks for values in the GUI thread every time it needs to paint cells or scroll lines. It is up to you to protect your business data (the grid is thread-safe). This mode is more productive than another one. It has low CPU load and low memory consumption.
- Dapfor::GUI::CPreferences::MemoryBuffer: At each update the grid copies values, formats them in the calling thread and then doesnít call object methods. The grid gets all information from the local cache. This mode is less productive but more secure.
Now let's explain what Dapfor::Common::CDataObject is: This is a base class for classes, the objects of which can be inserted in the grid. It implements the above binding mechanism and permits to call methods like GetPrice or SetPrice from the grid by their identifiers.
||std::string or std::wstring
||8-bit signed value
||8-bit unsigned value
||16-bit signed value
||16-bit unsigned value
||32-bit signed value
||32-bit unsigned value
||64-bit signed value
||32 bit value with a floating point
||64 bit value with a floating point
||Pointer to a data object
||Combination of the types described above
Read How to install the MFC grid and compile the first application? article to get more information.