Objective Grid : PART II Programmer’s Guide : Chapter 12 Advanced Design Overview : Objective Grid Style Architecture
Objective Grid Style Architecture
Some applications require that the end-user apply specific settings to certain data objects in a document, while other settings of these data objects should retain their default settings. The default settings may be stored in independent objects allowing the end-user or programmer to create a "gallery" of default types and change these default settings at run time. Whenever default settings are changed at run time, all data objects that did not override the default settings should reflect the new settings.
For example, in a typical word processing system the end user should be able to format individual paragraphs, words or characters in a document with certain formatting like font type, weight, size, alignment and text color. Certain categories of information should always look the same way. For example, this document follows some documentation conventions where the name of the paragraph, the title and the individual category titles all have their own characteristic formatting, which are used throughout the document. Category titles are outlined by using a bold font. In a word processing system, the settings for category titles, paragraph title and standard text are all stored in a document template. Whenever formatting for one of these styles is changed, all text in the document will reflect the new changes. For example, if the user changes the font size for the standard text style, all text will be drawn with a bigger font.
The styles architecture in the Objective Grid library is very similar to this. A grid consists of several cells that display information in the grid window. Each cell is identified through a unique row and column number pair. Cells can have certain attributes like the text, the font, borders and some other attributes which let you specify the appearance of the cell. All these attributes are stored in the cell data object, which is an instance of the CGXStyle class. Only changes need to be stored in the cell data object. Unchanged attributes will be determined at run time and loaded from base styles.
When the grid draws specific cells, all cell attributes must be determined for these cells (and not only the changed attributes for the cells). Therefore, whenever the grid needs to draw cells, cell data objects are created for each cell to be drawn and filled by calling virtual member functions. A programmer can override these virtual functions and specify the cell attributes at run time depending on the current context of the application or simply let the grid load the default settings from base styles. We call this the ‘Style Gallery’. This is the plumbing for the implementation of cell attribute inheritance in Objective Grid.
Cell attribute inheritance lets you group specific kinds of cells and make them have similar attributes. For example, all row headers can be changed through the "Row Header Style" and all column headers can be changed through the "Column Header Style". Default cell attributes can be changed through the "Standard Style". You can easily add your own base styles and let certain cells inherit their cell attributes from these base styles.
Typical style objects in the Objective Grid style gallery are:
Style name
Initialized attributes
Standard
Font size, alignment, white background, …
Row Header
Left aligned, bold, gray background
Column Header
Centered, bold, gray background
The following figure shows the main relations between the grid and the cell data objects. When the grid requests cell information, it calls LookupStyleRowCol(), which creates a style-object and initializes the cell's attributes. The base style settings will be loaded by calling the LoadBaseStyle() member function. GetParam()->GetStylesMap() is a pointer to a CGXStylesMap object which holds all the base style objects in Objective Grid and represents the style gallery.
Figure 123 – LookupStyleRowCol
As you can see, LookupStyleRowCol() not only lets you load settings from base styles in the CGXStylesMap, but also from row styles and column styles. Suppose you want to change the font for a whole row of cells. Normally you would have to loop through all cells in that row and change the font attribute, but with the above approach you only have to change the font in the row style. All cells in the affected row will automatically inherit the font settings from the row style. Only cells that have already stored individual font settings in the cell data object will not be affected.
For each attribute, the style class stores information whether an attribute is changed or if it is unchanged (and therefore retains its default value). This information is used to determine which attributes should be copied from a base style. Each attribute which is a member variable of the CGXStyle class has a corresponding include-bit in the CGXStyle class and each attribute can be accessed through four methods. An example is the text color:
GetIncludeTextColor() - returns the include bit.
SetIncludeTextColor(BOOL bInclude) - changes the include bit.
GetTextColor() - returns the text color.
SetTextColor(COLORREF rgbColor) - changes the color. This method automatically calls SetIncludeTextColor(TRUE).
One style object can be applied to another style object. Following operations are supported:
Applying only new attributes: Only those attributes are copied from the source style to the destination style that are included in the source style and not yet included in the destination style.
Overriding attributes: Those attributes are copied from the source style to the destination style which are included in the source style no matter if they are included in the destination style.
Copying the style: All attributes are copied from the source style to the destination style. Attributes that are not included in the source style will be removed from the destination style.
Removing attributes: Attributes that are included in the source style will be removed from the destination style.
Here is how a programmer can apply formatting to some columns and specify that the columns should load their default values from a specific base style (BS_NUMERIC):
 
SetStyleRange(CGXRange().SetCols(2,4),
CGXStyle()
.SetFont(CGXFont()
.SetSize(10)
.SetBold(TRUE)
)
.SetBaseStyle(BS_NUMERIC)
, gxOverride
);
The BS_NUMERIC style could be specified with
 
BaseStyle(BS_NUMERIC)
.SetHorizontalAlignment(DT_RIGHT) // right aligned
.SetFormat("###.##"); // numeric format
Figure 124 – Class Structure for this approach in Objective Grid
Style Gallery introduces some run-time costs associated with loading default settings from base styles on demand. However, such costs can be taken into account compared with the run-time costs when you would have to loop through all style objects in your document when a base style object from the gallery is changed.
On the other hand, Style Gallery can save you lots of memory by sharing base style objects among cells. If you have many style objects referring to the same information (e.g. a large string), you can make this style objects refer to one base style object where you store the large text.
Let us now take a look at some of the implementation issues with this architecture:
We encapsulate all state information for document data in one style class. The document itself is implemented as collection of these style objects.
For each attribute in the style class, we add an include-bit so that you can later determine whether a specific attribute is changed or not.
We implement a method (ChangeStyle()) in the style class which lets you merge style objects together. This method has to check for each attribute the include bit of the other style and copy the attribute when the same attribute in the current style object is uninitialized.
 
void CGXStyle::ChangeStyle(const CGXStyle& pOther)
{ // HorizontalAlignment
if (!GetIncludeHorizontalAlignment()) &&
pOther.GetIncludeHorizontalAlignment())
{
SetHorizontalAlignment(pOther.GetHorizontalAlignment());
}
 
// VerticalAlignment
if (!GetIncludeVerticalAlignment()) &&
pOther.GetIncludeVerticalAlignment())
{
SetVerticalAlignment(pOther.GetVerticalAlignment());
}
 
...
}
We provide a method that can fill up all the default settings for a style object. This method needs to have access to the style gallery. Before you fill up the style object with base style information, you should copy the style object, so that you do not overwrite the cells individual style object:
 
// Create an empty style object
CGXStyle* pStyle = CreateStyle();
 
// Copy the style settings for the specific cell
GetStyleRowCol(nRow, nCol, *pStyle, gxCopy, 0);
 
// Inherit attributes from base styles
pStyle->LoadBaseStyle(*GetParam()->GetStylesMap());
An extension to only loading style objects from style objects in the gallery object is to let style objects load default settings also from style objects outside the style gallery. For example, in a grid, you may want to apply changes to a column or row and not loop through all the cells in that row or column. By extending the Style Gallery pattern, you can also load data from row or column base styles as shown in the following code snippet:
 
const CGXStyle& ComposeStyleRowCol(ROWCOL nRow, ROWCOL nCol,
CGXStyle* pStyle)
{
...
// First, get changes for the individual cell
GetStyleRowCol(nRow, nCol, *pStyle, gxCopy, 0);
 
// Next, load column and row styles
if (nRow > 0 && nCol > 0)
{
// Apply row style
GetStyleRowCol(nRow, 0, *pStyle, gxApplyNew, -1);
 
// Apply column style
GetStyleRowCol(0, nCol, *pStyle, gxApplyNew, -1);
 
// Apply table style
if (nRow > 0 || nCol > 0)
GetStyleRowCol(0, 0, *pStyle, gxApplyNew, -1);
}
 
// Finally inherit attributes from base styles
pStyle->LoadBaseStyle(*GetParam()->GetStylesMap());
...
}
We have a collection of user attributes in the style class, each identified through a unique integer number. This supports data that is unique to every style without having to allocate unused space.
 
class CGXStyle
{
...
 
CGXUserAttributeMap* // The programmer can use extra
// attributes for derived CGXControls
m_pmapUserAttributes; // key is an String-Resource-Id,
// value is a CGXAbstractUserAttribute
// access methods:
public:
BOOL GetIncludeUserAttribute(WORD nID) const;
CGXStyle& SetIncludeUserAttribute(WORD nID, BOOL b);
const CGXAbstractUserAttribute& GetUserAttribute(WORD nID) const;
void GetUserAttribute(WORD nID, CString& s) const;
CGXStyle& SetUserAttribute(WORD nID,
const CGXAbstractUserAttribute& attribute);
CGXStyle& SetUserAttributePtr(WORD nID, CGXAbstractUserAttribute* pValue);
CGXStyle& SetUserAttribute(WORD nID, const CString& s);
CGXStyle& SetUserAttribute(WORD nID, LPCTSTR pszValue);
CGXStyle& SetUserAttribute(WORD nID, DWORD value);
CGXStyle& SetUserAttribute(WORD nID, LONG value);
CGXStyle& SetUserAttribute(WORD nID, double value);
CGXStyle& SetUserAttribute(WORD nID, GXBYTE8 value);
};
m_pmapUserAttributes is the collection which holds all initialized user attributes for a style object. GetIncludeUserAttribute() detemines if the attribute can be found in the collection. SetUserAttribute() initializes the attribute in the collection.
This lets us store any kind of data in the style object and manipulate it in a standard format. The data stored will have to be derived from CGXAbstractUserAttribute so that the grid can manipulate it. This object can have extra methods that are specific to that attribute. The grid uses user attributes extensively to store data that is specific to certain cell types.
The styles map
The CGXStylesMap is the object that holds all the base style objects.
 
class CGXStylesMap: public CObject
{
public:
// Base Style
// Register a new style (or change an already registered style)
WORD RegisterStyle(LPCTSTR pszName, const CGXStyle& style);
BOOL LookupStyle(WORD wStyleId, CGXStyle*& style) const;
 
protected:
// base styles
CMap<WORD, WORD, CGXStyle, CGXStyle&>
m_BaseStyleMap;
// associative map with unsigned integer as key
};
The RegisterStyle() method adds new style objects to the CGXStylesMap and returns an unsigned integer which can be used in the application as an unique identifier to refer to the base style object. LookupStyle() returns the base style object for the give identifier. m_BaseStyleMap() holds all the base style objects. Each style object is identified through an unsigned integer (which is returned by RegisterStyle()).
By default, three base styles are registered in the grid: Standard, Row Header and Column Header. The Standard style is the base style for all style objects.
The CGXStyle::LoadBaseStyle() method should be called from the grid on demand when it needs to have knowledge about a specific cells state. LoadBaseStyle() loads the default settings from the base style and loops through the base styles until all uninitialized data is resolved.
 
void CGXStyle::LoadBaseStyle(const CGXStylesMap& stylesmap)
{
CGXStyle* pBaseStyle = this;
 
while (pBaseStyle->GetIncludeBaseStyle())
{
// check out pointer to new basestyle
if (!stylesmap.LookupStyle(pBaseStyle->GetBaseStyle(), pBaseStyle))
break;
 
// load default settings from base style
ChangeStyle(*pBaseStyle, gxApplyNew);
}
 
...
}
By using this unique style architecture, we could provide the functionality to let end-users categorize different types of cells and reuse this information among cells. We have also added dialogs to our grid library, so that the end-user can change add, remove, or change base styles. Any change of existing base styles will be reflected in all cells.