I have a custom button control created using ATL. This control is used by some composite controls and lots of dialog boxes. I just added some new properties to the button control and found that I then had to update all the controls and dialogs that used it. This is a really poor situation so I wondered if I could be doing something better.
Here are some relevant bits of code:
class ATL_NO_VTABLE CSMButton :
public CComObjectRootEx<CComSingleThreadModel>,
public CStockPropImpl<CSMButton, ISMButton>,
public IPersistStreamInitImpl<CSMButton>,
public IOleControlImpl<CSMButton>,
public IOleObjectImpl<CSMButton>,
public IOleInPlaceActiveObjectImpl<CSMButton>,
public IViewObjectExImpl<CSMButton>,
public IOleInPlaceObjectWindowlessImpl<CSMButton>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<CSMButton>,
public IConnectionPointImpl<CSMButton, &DIID__ISMButtonEvents>,
public CProxy_ISMButtonEvents<CSMButton>,
public IPersistStorageImpl<CSMButton>,
public ISpecifyPropertyPagesImpl<CSMButton>,
public IQuickActivateImpl<CSMButton>,
#ifndef _WIN32_WCE
public IDataObjectImpl<CSMButton>,
#endif
public IProvideClassInfo2Impl<&CLSID_SMButton, &__uuidof(_ISMButtonEvents), &LIBID_BaseControlsLib>,
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
public IObjectS开发者_运维技巧afetyImpl<CSMButton, INTERFACESAFE_FOR_UNTRUSTED_CALLER>,
#endif
public CComCoClass<CSMButton, &CLSID_SMButton>,
public CComControl<CSMButton>
{
...
BEGIN_COM_MAP(CSMButton)
COM_INTERFACE_ENTRY(ISMButton)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IViewObjectEx)
COM_INTERFACE_ENTRY(IViewObject2)
COM_INTERFACE_ENTRY(IViewObject)
COM_INTERFACE_ENTRY(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(IOleInPlaceObject)
COM_INTERFACE_ENTRY2(IOleWindow, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY(IOleInPlaceActiveObject)
COM_INTERFACE_ENTRY(IOleControl)
COM_INTERFACE_ENTRY(IOleObject)
COM_INTERFACE_ENTRY(IPersistStreamInit)
COM_INTERFACE_ENTRY2(IPersist, IPersistStreamInit)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
COM_INTERFACE_ENTRY(IConnectionPointContainer)
COM_INTERFACE_ENTRY(ISpecifyPropertyPages)
COM_INTERFACE_ENTRY(IQuickActivate)
COM_INTERFACE_ENTRY(IPersistStorage)
#ifndef _WIN32_WCE
COM_INTERFACE_ENTRY(IDataObject)
#endif
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
#ifdef _WIN32_WCE // IObjectSafety is required on Windows CE for the control to be loaded correctly
COM_INTERFACE_ENTRY_IID(IID_IObjectSafety, IObjectSafety)
#endif
END_COM_MAP()
BEGIN_PROP_MAP(CSMButton)
PROP_DATA_ENTRY("_cx", m_sizeExtent.cx, VT_UI4)
PROP_DATA_ENTRY("_cy", m_sizeExtent.cy, VT_UI4)
PROP_ENTRY_TYPE("Caption", DISPID_CAPTION, CLSID_NULL, VT_BSTR)
PROP_ENTRY_TYPE("Colour", DISPID_COLOUR, CLSID_NULL, VT_COLOR)
PROP_ENTRY_TYPE("ButtonType", DISPID_BUTTONTYPE, CLSID_NULL, VT_I4)
PROP_ENTRY_TYPE("Toggle", DISPID_TOGGLE, CLSID_NULL, VT_BOOL)
PROP_ENTRY_TYPE("Down", DISPID_DOWN, CLSID_NULL, VT_BOOL)
// Example entries
// PROP_ENTRY_TYPE("Property Name", dispid, clsid, vtType)
// PROP_PAGE(CLSID_StockColorPage)
END_PROP_MAP()
The two VT_BOOLs are what I added. After doing that and building the control, I got an assert at runtime when showing a dialog and when I opened the dialog in visual studio I got a warning and I had to resave the dialog box. The dialog then worked ok at runtime.
I believe the reason for this is that the dialog has saved the properties as a binary stream in the resource and when new properties are added there is no longer enough data to fully initialize the control. The behaviour I would like in this case is for property values that are present to be set and any remaining properties should have default values. How can this be achieved?
And no, I can't use .net :-(
I used to slightly modify ATL persistence classes to implement a simple thing. When loading from stream, before starting each property the implementation checks if we are already at the end of stream and if so, it exits the loop assuming that unloaded properties are left initialized by default.
From there on, you can add new properties and be backward compatible. If you need to remove a property, do not take it off the map. Instead, you need to leave the entry but you can point it to fake property setter, so that if you have a persistence value for the property saved by earlier version of control, it would be discarded without causing any errors.
Hope this helps.
精彩评论