Control Sharing In the Grid
Using lots of objects can be expensive. Therefore, sharing objects of the same class can bring a lot of resource savings. The previous section alluded to 'Control sharing' in the grid. In this section, we will look at this very important aspect of the grid architecture in more detail. Each cell in the grid is identified through a unique row and column number pair. The user should be able to specify the cell type and also the appearance of the cell with attributes like the text, the font, borders and some others. The cell type should be replaceable at run time and provide a common interface for both the programmer and the end-user of the grid component. The programmer should be able to exchange data and specify the cell appearance independent of the cell type. The end-user should be able to operate with different cell types in a unique way. For example, different cell types should be able to be copied and pasted in the same way. Also, doing Find/Replace should be possible with a unique interface.
As explained earlier, the object-oriented answer to this kind of problem is to create an abstract base class and inherit the individual cell types from this base class. Each cell should be an object and can be accessed through a common interface.
Some of the problems with this approach are:
1. Windows will go out of resources because the number of windows is limited.
2. Memory costs can be very high.
3. Performance will decrease because each window has to be repositioned when the user scrolls the grid.
4. Creating many window controls will cost a lot of time because creating a window is a very expensive operation.
Another problem is that if the cell appearance is stored in the cell type object, the appearance settings would get lost when the cell type is replaced.
We implemented a solution that enabled us to reduce the use of resources dramatically. The essence of this solution can be explained as below:
1. Make the cell attributes an extrinsic state which can be passed to the cell type object.
Cells can have certain attributes like the text, the font, borders and some other attributes which let you specify the appearance of the cell. Objective Grid separates data to be displayed in a cell and the cell attributes from the cell type object. All attributes are stored in the cell data object, which is an instance of the CGXStyle class. The cell data objects are stored in the grid and can be accessed through grid routines.
2. Provide a base class for all cell types (in this case CGXControl).
Each cell type is implemented through a class derived from CGXControl. CGXControl is an abstract base class, which defines the interface between a grid and a cell type object. Responsibilities of the cell type object are drawing the cell and interpreting user input (keyboard and mouse messages). The cell type object does not store cell specific information. If the user changes the cell text or value, all changes are stored in the cell data object for the cell.
Member functions in CGXControl can receive and act on extrinsic state and don't rely on internal stored state. For example, when drawing a cell, the grid passes the rectangle, the row and column coordinates and the cell data object to Draw method of the cell type object. The cell type object will draw the cell based on these parameters.
3. Maintain a pool of cell type objects.
The grid maintains a list of CGXControl objects. CGXControl objects are created and registered at initialization time of the grid. Each control is associated with an unique integer id and can be looked up in the pool through this integer id. This integer ID is an attribute of the CGXStyle class. Therefore cell types can be easily and simply replaced at run time by changing the integer id in the cell data object. Controls own a pointer to the grid object and therefore cannot be shared among different grid objects.
We should also explain the concept of a current cell. This is a real exception of the rule that the cell type object should not store information about the cell. When a cell is the current cell and the user modifies its contents, all changes are stored in the cell type object directly. Only when the user accepts the changes (e.g. by moving the current cell to a new position) will the changes be stored back into the cell data object in the grid.
Figure 118 illustrates the main interactions between a grid, a cell and a control: LookupStyleRowCol() returns the cell data object for the cell with the cell type identifier. The grid can look up a pointer to the cell type object in the CGXControl object pool (by calling GetRegisteredControl()) and execute operations on this control, such as Draw(), Store(), or Init().
Figure 118 – Interactions between a grid, a cell and a control
The cell type objects are instantiated in OnInitialUpdate(). This method is called from the MFC framework before the grid window is shown (in the case of dialog based grids this is explicitly called). RegisterControl() simply stores a pointer to the cell type object in the control pool.
When the grid needs to be repainted, the MFC framework calls the OnDraw() member function of the grid. This function loops through all cells and calls the DrawItem() member function. DrawItem() looks up a pointer to the control object and calls the Draw() member function of the associated control object.
SetCurrentCell() is a member function in the grid which moves the current cell to a new position. It first stores and deactivates the old current cell and then initializes the new current cell.
The instance diagram in Figure 119 shows how controls can be shared among several cells. aCell objects are style-objects constructed in LookupStyleRowCol. anEditControl and aBitmapButton are control-objects registered in the grid. They are referenced through the cell type ID in the style-object. The grid maintains the control pool.
Figure 119 – Sharing cell type objects among cells
As we mentioned earlier, a major difference comes with the concept of the current cell because when the user modifies its contents, all changes are stored in the cell type object directly. Two different types of interfaces were defined for this behavior:
1. Functions which accept external state as attributes.
2. Functions which operate and initialize the state of the current cell. The Draw function is an exception and falls in both of these groups.
Let us first explain why the Draw() function falls in both groups.
virtual void
Draw(CDC* pDC, CRect rect, ROWCOL nRow, ROWCOL nCol,
const CGXStyle& style, const CGXStyle* pStandardStyle);
When drawing a cell, the grid passes the rectangle, the row and column coordinates and the cell data object to the Draw() method of the cell type object. Draw() operates in two different ways. Draw() checks if the row (nRow) and column (nCol) coordinates passed to the function are the same coordinates as the current cell's coordinates. If the coordinates match with the current cell's coordinates, Draw() will show the edit window and set the focus to it. All other cells will only be drawn static based on the extrinsic state passed to the function.
External State Functions
One characteristic for functions that operate with extrinsic state is that the row and column coordinates are passed as attributes. Examples for this type of interface are:
virtual BOOL
GetControlText(CString& strResult, ROWCOL nRow, ROWCOL nCol,
LPCTSTR pszRawValue, const CGXStyle& style);
GetControlText()determines the text that will be displayed in the cell. The display text can be different from the cell value when the displayed text should be formatted with 2 digits, for example, or as international date based on the Windows System settings.
GetControlText() will be called when cells should be copied to the clipboard or exported to a text file.
virtual BOOL
SetControlText(ROWCOL nRow, ROWCOL nCol, const CString& str, ...);
SetControlText() is the counterpart to GetControlText(). It converts the display text to the cell value and stores it in the cell data object in the grid.
SetControlText() will be called when cells should be pasted from the clipboard or imported from a text file.
virtual BOOL
FindText(const GX_FR_STATE& find, ROWCOL nRow, ROWCOL nCol, ...);
This function is called to find text in the cell data object at the given row.
Current Cell State Functions
The main characteristic for functions that operate on the current cell state is that no row and column coordinates are passed as attributes. Examples for this type of interface are:
 
virtual BOOL Store();
virtual BOOL OnValidate();
virtual BOOL LButtonDown(UINT nFlags, CPoint pt, UINT nHitState);
virtual BOOL KeyPressed(UINT nMessage, UINT nChar,
UINT nRepCnt = 1, UINT flags = 0);
The following function accepts row and column coordinates because it stores the row and column coordinates into the cell type object.
 
virtual void Init(ROWCOL nRow, ROWCOL nCol);
While this control-sharing architecture may introduce run-time costs associated with transferring, finding, and/or computing extrinsic state, especially if it was formerly stored as intrinsic state. However, such costs are offset by space savings, which increase as more objects are shared. With Objective Grid, increased run-time costs can be the initialization of the control every time a cell needs to be drawn. You can't simply apply a large choice list with many hundreds items to one combo box. All the items will be transferred to the control and the control will be initialized with this list every time it is drawn. But, especially in conjunction with the cell attribute inheritance (See “Objective Grid Style Architecture.”) discussed later, space savings can be enormous if that one large choice list can be applied to a base style which is used among many cells.