Stingray® Foundation : Chapter 5 Properties Package : Property Containers
Property Containers
The IPropertyContainer interface provides an interface to an object’s properties. You can use it to retrieve an IProperty pointer to each property supported by the object. The IProperty interface only describes the property, it doesn’t give access to the value of the property. The IPropertyContainer interface has methods for getting and setting the value of a given property.
PutPropertyValue() takes a VARIANT and sets the value of property identified by the given property ID.
GetPropertyValue() returns a VARIANT given a property ID.
Example 23 demonstrates how to display each of the properties for a given property container.
Example 23 – Using a property container to display an object’s properties
void ShowProperties(IPropertyContainer* pContainer, CDC* pDC)
{
BSTR buf;
TCHAR tBuf[20];
COleVariant val;
USES_CONVERSION;
 
int i;
for (i=0; i < pContainer->GetPropertyCount(); i++)
{
// Get a pointer to the property at position i
// in the container
IProperty* pProp = pContainer->GetPropertyAt(i);
 
// Get the property ID
PropertyId propId = pProp->GetId();
pDC->TextOut(10, (i*90), _T("Property ID:"));
_itot(propId, tBuf, 10);
pDC->TextOut(150, (i*90), tBuf);
 
// Get the property name
pProp->GetName(buf);
pDC->TextOut(10,(i*90)+20,_T("Property Name:"));
pDC->TextOut(150,(i*90)+20,OLE2T(buf));
 
// Get the property description
pProp->GetDescription(buf);
pDC->TextOut(10,(i*90)+40,_T("Property Description:"));
pDC->TextOut(150, (i*90)+40, OLE2T(buf));
 
// Get the property value
pContainer->GetPropertyValue(propId, val);
val.ChangeType(VT_BSTR);
pDC->TextOut(10, (i*90)+60,
_T("Property Value:"));
pDC->TextOut(150, (i*90)+60, OLE2T(val.bstrVal));
}
}
 
A Property Container Implementation
Any C++ object can expose properties at run time by implementing the IPropertyContainer interface. However, the CPropertyContainer class provides an implementation of the IPropertyContainer interface that is sufficient for most applications. Properties must be registered with CPropertyContainer objects using one of several RegisterProperty() methods. The most basic RegisterProperty() method takes an IProperty pointer. There are several variations of RegisterProperty() that create a CProperty object using the parameters passed in and register it.
The Property Map
The CPropertyContainer class stores the properties in a map, which is keyed on the property ID. The nested class CPropertyContainer::Map implements the property map. To avoid the overhead of maintaining a separate map of properties for each instance of CPropertyContainer, the CPropertyContainer class calls the virtual method GetPropertyMap() whenever it needs to access the property map. This gives derived classes the opportunity to implement GetPropertyMap() in an efficient manner. For example, they can return a statically declared CPropertyContainer::Map object. The SFL_PROPERTY_MAP macro is provided to do this. The SFL_PROPERTY_MAP macro implements GetPropertyMap() as shown in Example 24.
Example 24 – Expansion of SFL_PROPERTY_MAP
virtual CPropertyContainer<_PropertyAccessor>::Map&
GetPropertyMap() const
{
static
CPropertyContainer<_PropertyAccessor>::Map propMap;
return propMap;
}
 
Example 25 shows a class that uses the SFL_PROPERTY_MAP macro.
Example 25 – Using the SFL_PROPERTY_MAP macro in a class definition
class CFoobar : public CPropertyContainer
{
public:
SFL_PROPERTY_MAP(CFoobar)
};
Classes that derive from CPropertyContainer can implement GetPropertyMap() any way they like. The SFL_PROPERTY_MAP macro is a convenient way to do so.
Property Accessors
The CPropertyContainer class is a template that takes an accessor class as its parameter. CPropertyContainer uses accessor objects to implement the GetPropertyValue() and PutPropertyValue() methods it inherits from IPropertyContainer. It creates an accessor object for each property and stores it in the property map along with the IProperty pointer. To store or retrieve a property value, CPropertyContainer does a quick lookup in the property map to get the accessor object and then invokes either the GetValue() function or PutValue() function on the accessor. A pointer to the derived class is passed to the accessor, so that it can invoke the appropriate get or put function. The accessor object is an essential get and put functor for the property.
The only requirement for an accessor class is that it define an embedded typedef called _SourceClass and implement the following methods.
 
void GetValue(_SourceClass* pObj, VARIANT& propVal)
void PutValue(_SourceClass* pObj, const VARIANT& propVal)
Fortunately, there is a default accessor implementation called CPropertyAccessor, which is suitable for most applications. The CPropertyAccessor uses function pointers to implement GetValue() and PutValue(), and it uses a template parameter to define SourceClass. CPropertyAccessor defines a set of function signatures that you must use for the get and put functions. The functions assigned to the accessor must match one of those signatures. Example 26 shows how to create and use an accessor for storing and retrieving a floating-point value in a class called CFoo.
Example 26 – Creating and using an accessor
class CFoo
{
protected:
float m_bar;
 
public:
float GetBar()
{
return m_bar;
}
 
void SetBar(const float bar)
{
m_bar = bar;
}
}
 
void UseFoo(CFoo& foo1, CFoo& foo2)
{
CPropertyAccessor<CFoo> barAccessor(&CFoo::GetBar,&CFoo::PutBar);
VARIANT val;
barAccessor.GetValue(&foo1, val);
barAccessor.PutValue(&foo2, val);
}
Property accessors allow containers to get and set values using simple accessor functions without knowing the names of those functions and without knowing the memory address and type of the data. The container simply needs to map the property ID to an accessor to store and retrieve values. Class CFoo doesn’t need to perform any special functions.
Property Container Example
Example 27 shows a class that inherits CPropertyContainer and registers some properties.
NOTE >> Each property must have a numeric identifier associated with it. The only rule for assigning property identifiers is that they must be unique within the container.
 
Example 27 – Implementing a property container
#define PROP_HORSEPOWER 100
#define PROP_COLOR 101
 
class CCar : public CPropertyContainer<CPropertyAccessor<CCar> >
{
public:
SFL_PROPERTY_MAP(CCar)
 
CCar()
{
RegisterProperty(
PROP_HORSEPOWER,
_T("HorsePower"),
_T("Horse power of the car"),
_PropertyAccessor(&CCar::GetHorsePower,
&CCar::PutHorsePower));
 
RegisterProperty(
PROP_COLOR,
_T("Color"),
_T("Color of the car"),
_PropertyAccessor(&CCar::GetColor,
&CCar::PutColor));
}
 
int GetHorsePower()
{
return nHorsePower;
}
 
void SetHorsePower(const int nHorsePower)
{
m_nHorsePower = nHorsePower;
}
 
LPCTSTR GetColor()
{
return m_color;
}
 
void PutColor(LPCTSTR lpszColor)
{
m_color = lpszColor;
}
 
private:
int m_nHorsePower;
CString m_color;
}
With the exception of the SFL_PROPERTY_MAP macro and the calls to RegisterProperty() in the constructor, this class looks and acts like any C++ class.