This topic is relevant for the following:
Product(s): ArcGIS Desktop: All
Version(s): 9.0, 9.1, 9.2
Language(s): VB6, VC++
Experience level(s): Intermediate to advanced
In this section:
Persistence is a general term, referring to the process by which information indicating the current state of an object is written to a persistent storage medium such as a file on disk.
Persistence is used in ArcGIS to save the current state of documents and templates. By interacting with the ArcGIS user interface, you can change the properties of many of the objects that belong to a map document, for example, a renderer. When the map document is saved and closed, the instance of the renderer class is terminated; when the document is reopened, you can see that the state of the renderer object has been preserved.
Map documents and their contents are saved using a technique known as structured storage.
Structured storage is one implementation of persistence defined by a number of standard COM interfaces. Prior to structured storage, only a single file pointer was used to access a file. In structured storage however, a compound file model is used, whereby each file contains storage objects and streams. Storage objects provide structurelike folders on your operating system, they can contain other storage and stream objects. Stream objects provide storagelike traditional files, they can contain any type of data in any internal structure. When the stream is later reopened, a new object can be initialized and its state set from the information in the stream, re-creating the state of the previous object.
In this way, a single compound file can act as a mini file systemit can be accessed by many file pointers. Benefits of structured storage include incremental file read/write and a standardization of file structure, although larger file sizes may also result.
ArcGIS uses structured storage to persist the current state of all the objects used by an application, although other persistence techniques are also used. Structured storage is only used for non-GIS data.
The structured storage interfaces specified by COM are implemented extensively throughout the ArcGIS framework. Understanding when persistence is used within the ArcGIS framework will help you to implement correct persistence behavior in classes you create. The following sections explain when to implement persistence, which interfaces to implement, and also review a number of issues that you may encounter when persisting objects.
Although persistence is used throughout the ArcGIS framework, it is not ubiquitousnot every object will always be given the opportunity to persist itself.
ArcGIS applications use the compound document structure to store documentsmap documents, map templates, normal templates, and scene documents. All the objects currently running within a document or template are persisted to streams in the compound file when the document is saved.
Take the example of a map documentwhen a user chooses Save in ArcMap, the MxApplication first creates streams as required, associates them with the existing .mxd file (if the document has previously been saved), then asks the document to persist itself to these streams. If there are changes to the normal template or map template, then this process is repeated for the appropriate .mxt file. This process allows the current state of a document to be recreated when the file is reopened.
From ArcGIS 9.1, you can save map documents so you can open and work with them in previous versions of ArcGIS. See the later sections of this topic on version compatibility for more information on handling this kind of persistence in your custom components.
If any object referenced by the map document is expected to support persistence and does not, errors may be raised to a user and the completion of the save may be prevented, rendering the document unusable.
You should, therefore, always be clear whether or not your class needs to implement persistence, and implement correct persistence behavior if required.
When an object is asked to persist itself, it will write the current value of its member variables to the stream. If one of the members references another object, and that object is also persistable, it will most likely delegate the persistence work by asking the member object to persist itself. This 'cascading' effect ensures that all the referenced objects are given a chance to persistthis may include your own custom objects, if they are referenced by an object that is persisted.
A persistence event 'cascades' through the document, as each object asks its members to persist themselves in turn.
As seen previously in document persistence, each class decides what defines its own state and persists only this data (in most cases, the values of its private member variables).
If for some reason you decide your custom class does not need to save any information about its state to the stream, but is expected to support persistence, then you still must implement persistence, although you don't necessarily need to write any data to the stream.
For most custom classes you will create, objects will be persisted to one of the streams created by ArcMap; it is unlikely you will need to create a new storage or stream yourself.
During the Save process, the application checks all currently loaded extensions to see if they implement persistence. If so, each extension is asked to persist itself. An extension, therefore, does not necessarily have to support persistenceno errors will be raised if it does notit depends on whether or not the extension needs to persist the state when a document is closed. Extensions are persisted in the order they are referenced, which is the order of their CLSIDs.
The Application object creates a separate stream for the persistence of each extension; the new streams are stored in the same compound file as the other document streams. A separate ObjectStream is also created for the extensionsee below for more information about ObjectStreams.
An object's state is not always defined by value typesyou have already seen how an MxDocument persists itself by calling other objects to persist themselves.
Often multiple references are held to the same object, for example, the same Layer in a Map may be referenced by IMap::Layer and ILegendItem::Layer. If each of these properties were called to persist, two separate copies of the Layer would be persisted in different sections of the stream. This would bloat file size and would also corrupt object references.
To avoid this problem, ObjectStreams are used in ArcObjects to persist objects and maintain object references correctly when persisted.
When an ArcObjects object initiates a persist, that object will create a stream for the persistence. It will also create an ObjectStream, and associate it with the stream; one ObjectStream can be associated with one or more streams. The ObjectStream maintains a list of objects that have been persisted to that stream.
The first time a particular object is encountered, it is persisted in the usual manner. If the same object is encountered again, the ObjectStream will ensure that instead of persisting the object a second time, a reference to the existing saved object is stored instead.
In addition to ensuring the integrity of object references, this helps to keep file sizes to a minimum. Only COM objects, supporting IUnknown and IPersist can be stored in this way.
To create a persistable class, you should implement either IPersist and IPersistStream or IPersistVariant. Both interfaces specify three basic pieces of functionality.
The choice of IPersistStream or IPersistVariant depends on the development environment you are usingthese interfaces are discussed in turn below. You do not need to implement both interfaces.
When a document is persisted, the client writes the identity of the class to the stream (using ID or GetClassID). Then it calls the Save method to write the actual class data to the stream. When a document is loaded, the identity is read first, allowing an instance of the correct coclass to be created. At this point, the rest of the persisted data can be loaded into the new instance of the class.
If you wish to implement version-specific persistence code, see the Version Compatibility section later in this topic for more information.
When you implement a persistent class, the decision of what constitutes the persistent state for your class is yours to makeexactly what data you choose to write to a stream is up to you.
Ensuring that your code can re-create the state of an instance may include storing data about public properties and any internal members of the class.
You may decide that certain items of state are not persisted. For example, a Map does not persist the IMap::SelectedLayer property; upon opening a map document, the SelectedLayer property is null. You should also decide exactly how a newly instantiated instance of the class is initialized from the data stored in the stream.
The IPersistVariant interface should be implemented by Visual Basic classes that need to be persistable. This interface was specifically designed for use by VB programmers.
Implements esriSystem.IPersistVariantIn the ID property, create a UID and set the object to the fully qualified class name of your class.
Private Property Get IPersistVariant_ID() As esriSystem.IUID
Dim pUID As New esriSystem.UID
pUID.Value = "<LibraryName>.<ClassName>" ' or a GUID
Set IPersistVariant_ID = pUID
End PropertyA basic implementation of Save and Load is shown below.
Private Sub IPersistVariant_Save(ByVal Stream As esriSystem.IVariantStream)
Stream.Write m_sValue1
Stream.Write m_sValue2
End Sub
Private Sub IPersistVariant_Load(ByVal Stream As esriSystem.IVariantStream)
m_sValue1 = Stream.Read
m_sValue2 = Stream.Read
End SubIn the example, the two string member variables, m_sValue1 and m_sValue2, are persisted. Note that streams are sequential; the Load method must read the data from the stream in the same order the data was written to the stream in the Save method. Ensure that your data is saved and loaded in the correct order so that the correct data is written to the correct member.
Coding the Save and Load methods may be considerably more complex if you have a large, complex class.
The stream passed to the IPersistVariant interface is a specialist stream class which implements IVariantStream. Using this interface, any value type, or any COM object can be written to a stream. This stream class is internal to ArcObjects.
The IVariantStream interface allows you to write COM objects and value data types to a stream using the same semantics.
If you are developing in VC++, you may already be familiar with the IPersistStream interface, an interface defined as part of COM which inherits the IPersist base interface. You would generally implement this interface in preference to IPersistVariant, as it is more flexible, and the VC++ environment offers better support for this interface.
IPersistStream inherits from IPersist, and therefore, cannot be implemented in VB. Neither is it suitable for use by VB clients, as it also uses data types not supported by VB.
IPersist and IPersistStream are standard COM persistence interfaces and can be implemented in VC++.
A simple implementation of Save and Load, shown below, persists a string (m_bstrValue) and a long (m_lNum). The code makes use of the WriteToStream and ReadFromStream methods that are available on the CComBSTR smart type. These methods are also available on CComVariant.
STDMETHODIMP CPersistClass::Save(IStream * pStm, BOOL fClearDirty)
{
if (m_bDirty)
{
m_bstrValue.WriteToStream(pStm);
pStm->Write(&m_longValue, sizeof(m_longValue), 0);
}
// reset dirty flag
m_bDirty = false;
return S_OK;
}STDMETHODIMP CPersistClass::Load(IStream * pStm)
{
m_bstrValue1.m_str = NULL;
m_bstrValue1.ReadFromStream(pStm);
pStm->Read(&m_longValue, sizeof(m_longValue), 0);
m_bLoadedSettings = true;
return S_OK;
}The Save method only writes to the stream if the parameter m_bDirty indicates that the object has changed since the last save.
The IPersistStream interface includes the IsDirty method, indicating whether or not an object has changed since it was last saved. The private member, m_bDirty, is used by this class to indicate the state of the object and is set to True when any change is made to the class and reset as False at the end of the Save method.
The GetSizeMax method should return the maximum size of the persisted data. In many cases it is not possible to calculate this in advance, for example, when persisting a variable size array or collection. In this case, return E_NOTIMPL.
STDMETHODIMP MyClass::GetSizeMax(_ULARGE_INTEGER * pcbSize)
{
if (pcbSize == NULL)
return E_POINTER;
return E_NOTIMPL;
}The IPersistStreamInit interface is an alternative to IPersistStream. It provides one extra method, InitNew, which clients may call to initialize the object to default values. This interface is more relevant to persistence of ActiveX controls where there is a large range of possible clientsyou will not need to implement this interface for a custom class for ArcGIS.
The IPersistStorage interface is implemented by objects that persist themselves directly to a structured storage container, rather than a stream. MXD documents are implemented with this kind of persistence. This method is essential for objects which are to be embedded in OLE containers such as Microsoft Word. You will not need to implement this interface to create a persistable custom ArcGIS class.
If your object can be saved to a previous version of ArcGIS, but you need to account for this in your persistence code by having different persistence code for different ArcGIS versions, then you should adapt your implementation of IPersistVariant or IPersist/IPersistStream to identify the document version that your component is being persisted to.
Within a call to load or save, you can find out the version of the document by QIing to the IDocumentVersion interface on the stream object as shown below; this applies to both the variant stream reference passed to a VB6 component implementing IPersistVariant, and also to the object stream reference passed to a VC++ component implementing IPersist/IPersistStream.
Private Sub IPersistVariant_Save(ByVal Stream As esriSystem.IVariantStream)
If TypeOf Stream Is IDocumentVersion Then
Dim pDocVersion As IDocumentVersion
Set pDocVersion = Stream
If pDocVersion.DocumentVersion = esriArcGISVersion83 Then
' Load object as 8.3 version of itself.
Else
' Load object.
End If
Else
' Installed client must be 9.0 or previous version.
...STDMETHODIMP CPersistClass::Load(IStream * pStm)
{
IDocumentVersionPtr ipDocumentVersion = pStm;
if (ipDocumentVersion!=0)
{
enum esriArcGISVersion docVer;
ipDocumentVersion->get_DocumentVersion(&docVer);
if (docVer == esriArcGISVersion83)
{
// Load object as 8.3 version of itself
}
else
{
// Load object.
}
else
{
// Installed client must be 9.0 or previous version.If your code may be installed to machines with an installation of ArcGIS previous to 9.1, then you cannot guarantee that the stream passed to the persistence methods will support IDocumentVersion. As shown above, you should always QI for this interface and take appropriate action if this interface is not found. You may wish to provide your own functions to discover the installed version of ArcGIS, or may wish to rely on your own internal persistence version number - see the Coding Backward Compatibility section below for more details.
The following sections give advice on persisting certain types of data to a stream for implementors of both IPersistVariant and IPersistStream.
If you are using IPersistVariant and working in VB, coding the persistence of an object is syntactically the same as coding the persistence of a value type. When you pass an object reference like this, the stream uses the ObjectStream, associated internally with the stream to persist the object.
Stream.Write m_pMyColorObjectThe object is reloaded in a similar way.
Dim pColor as IColorSet pColor = Stream.ReadIf using the IPersistStream interface in Visual C++, a few more lines of code are required. First, you should check if an ObjectStream is associated with the stream by performing a QI for IObjectStream. If the QI succeeds, you can call SaveObject on the IObjectStream pointer, passing the object you wish to persist directly to the ObjectStream.
IObjectStreamPtr ipObjectStream(pStream);
if (ipObjectStream !=0)
{
HRESULT hr;
hr = ipObjectStream->SaveObject(m_ipColor);
if (FAILED(hr)) return hr;
}Always check the return value, as if the save fails, this can produce a corrupt stream. If you are working within the ArcGIS framework, an ObjectStream will already be associated with the stream you receive. Again, be sure to check the return value when you reload the object.
IObjectStreamPtr ipObjectStream(CLSID_ObjectStream);
ipObjectStream->putref_Stream(pStm);
hr = ipObjectStream->LoadObject((GUID*) &IID_IColor, 0, &pUnk);
if (FAILED(hr))
return hr;Often, a class member may be a dynamic array having a variable number of members. In this case, write the value of the member directly to a stream in its entirety, as it is not a COM object.
You can write each array member in turn to the stream, as long as you include extra information about the size of the array, since the Load method needs to be able to size the array and read the correct number of members from the stream to assign to the array.
The example code below demonstrates how this technique can be used in VB, where the variable m_pArrayMember is a member of the class and also a dynamic array.
Dim i As Long, lCount As Long
lCount = UBound(m_pArrayMember) + 1
Stream.Write lCount
For i = 0 To lCount - 1
Stream.Write m_pArrayMember(i)
Next iThe array can now be initialized correctly in the Load method.
Dim i as Long, lCount as Long
lCount = Stream.Read
ReDim m_pArrayMember(lCount -1)
For i = 0 to lCount - 1
m_pArrayMember(i) = Stream.Read
Next iInstead of using a standard dynamic array, you could store object references in an ESRI Array class and persist each of these references in the same way (the Array class is not persistable itself).
You can make use of the ESRIPropertySet coclass to persist a class's member data, as this class is persistable. Maximum efficiency will be gained during a save if you already use the PropertySet to store your class data internally.
Some of the persistable examples in the ArcGIS Developer Help show examples of this technique.
The Version Compatibility and Coding Save A Copy functionality sections describe how to deal with the persistence of your object at different versions of ArcGIS. If during your component's persistence code you persist object references, you should also consider that those objects too need to deal with the document version correctly.
All core ArcObjects deal correctly with document version persistencethey do not implement the IDocumentVersionSupportGEN interface, but instead deal with this issue internally. If you are persisting an object to an object stream, all core ArcObjects therefore can be relied upon to either persist correctly regardless of version, or to convert themselves to suitable replacement objects using methods similar to the IDocumentVersionSupportGEN::ConvertToSupportedObject method.
If you encounter an error when you attempt to read a stream, you must propagate the error to the client. As streams are sequential, your code should not attempt to continue reading, as the stream pointer will not be positioned correctly, and therefore, the next value cannot be read correctly.
For this reason, you should always be particularly careful when writing and testing persistence code.
Review the following section on persistence version compatibilityyou can avoid many errors in your persistence code if you correctly create backward-compatible components.
In some cases, ArcGIS may be able to continue loading a document despite an error in your code, due to the use of safe loading techniques.
The effects of the error may vary according to the type of component. For example, if ArcGIS attempts to load a Layer from a document and fails, ArcMap will continue to load the remainder of the document, but the failed layer will be missing. You should code your component regardless of this functionality and raise an error to the calling function if you cannot complete the Load, before exiting the Load function.
You are responsible for ensuring that your component is registered on a machine, which may open a document with a persisted version of your component.
If you develop a new version of a persistable component, it is quite likely that you will need to persist additional state informationthis will mean you need to change the persistence signature of your class. However, your component may still maintain binary compatibility and have the same ClassID.
By coding your persistence methods to be adaptable from the beginning of your development cycle, you can ensure your component is compatible with other versions of itself when persisted. This will allow you to fully utilize the ability when using COM to upgrade a component without needing to recompile the component's clients.
Custom components should be coded with the version compatibility model of ArcGIS in mind.
ArcGIS document files work on the principle of backward compatibility; probably the most common form of persistence version compatibility. This means that ArcGIS clients can open documents that were created with an earlier version of ArcGIS.
It is possible to write forwardly-compatible components, for example, a client can load and save a component with a more recent version than that with which it was originally compiled. Implementing forward compatibility requires much care and can give rise to long, complex persistence code.
Although ArcGIS does not implement general forwards compatibility (and therefore this is not generally a requirement for your components), from ArcGIS 9.1 onwards it is possible for users to save their documents as specific previous ArcGIS versions, using the Save A Copy command. The saved documents can then be opened with a version of ArcGIS previous to that with which the document was created. At ArcGIS 9.1, you can only save to ArcGIS 8.3. ArcGIS 9.1 map documents are directly compatible with ArcGIS 9.0, so there is no option to save them to version 9.0 specifically.
If your component works, without recompilation, with both the current ArcGIS version and also to previous ArcGIS versions, then you do not need to adapt your component to ensure 'Save A Copy' functionality.
However, if your object cannot be persisted to a previous version of ArcGIS, you should implement IDocumentSupportVersionGENthis interface will allow you to provide an alternative object instead. See the Coding Save A Copy Functionality section below.
If your object can be saved to a previous version of ArcGIS, but you may need to account for this in your persistence code, then you should adapt your implementation of IPersistVariant or IPersist/IPersistStream to identify the version being persisted to, and make any necessary changes. You can find out more information on this in the 'Identifying the document version' section under Implementing Persistence.
You will now look at an example of creating a backwardly compatible class, by creating three different versions of the class. The example code is built up step-by-step, showing you how to code the persistence methods each time. The code is shown here in VB, although you can use the same principles if you are developing in VC++.
You will create a custom watermark layer, which simply displays a faint picture over a map to indicate map copyright for exporting or printing purposes.
It can be added programmatically to a map and provides limited layer functionality. For more information on creating custom layers, see 'Creating Cartography'.
For the first version of your layer class, WatermarkLayer, implement ILayer and ILayer2 to provide basic layer functionality. You will need to store the following member variables.
Private m_sName As String ' ILayer::Name
Private m_bCached As Boolean ' ILayer::Cached
Private m_bVisible As Boolean ' ILayer::Visible
Private m_pDisplayFilter As ITransparencyDisplayFilter ' Used for drawing
Private m_dRatio As Double ' Ratio of picture width to heightNow implement IPersistVariant, as a custom layer must be persistable. Before coding the persistence members of the WatermarkLayer class, add a private constant called m_iCurrPersistVers. You will use this constant throughout the persistence code to store the version of the class. As this is the first version of the class, set the constant to 1.
Private Const m_iCurrPersistVers As Integer = 1 The use of this value is the key to version compatibilityyou should code the Save and Load methods dependent on this number. The first thing written to the stream in the Save method is this persistence version value.
Private Sub IPersistVariant_Save(ByVal Stream As esriSystem.IVariantStream)
Stream.Write m_iCurrPersistVers
Then you can write the class state to the stream.
Stream.Write m_sName
Stream.Write m_bCached
Stream.Write m_bVisible
Stream.Write m_pDisplayFilter
Stream.Write m_dRatio
End SubIn the Load method, start by reading the version number of the persisted classstore this value in the iSavedVers local variable. If this version number indicates a version of the persisted class that is newer than the current version of the class (m_iCurrPersistVers), the class will not know how to load the persisted information correctly. If iSavedVers = 0, there is an error somewhere, as the minimum expected value is one. Both cases are errors and may cause a corrupt streamin these cases, raise an error back to the calling function.
Private Sub IPersistVariant_Load(ByVal Stream As esriSystem.IVariantStream)
Dim iSavedVers As Long
iSavedVers = Stream.Read
If (iSavedVers > m_iCurrPersistVers) Or (iSavedVers <= 0) Then
Err.Raise E_FAIL
Exit Sub
End IfAs Load may be called sometime after an object has been instantiated, you should ensure initialize default values for the class at the start of the Load.
InitializeMembers Now you can read the persisted class state and set the members of the current objectafter first checking that the saved persistence version is the version you expect (this check will come in useful later when you produce a new version of your component).
If iSavedVers = 1 Then
m_sName = Stream.Read
m_bCached = Stream.Read
m_bVisible = Stream.Read
Set m_pDisplayFilter = Stream.Read
m_dRatio = Stream.Read
End If
End SubNow that you have the first version of your class, you can compile and deploy the component. At this point, users may have map documents that contain persisted WatermarkLayer objects.
You can use the AddWMLayer command included in the project to add a new WatermarkLayer to a documentcompile the project and register the AddWMLayer command to ESRI Mx Commands.
You are now asked to add functionality to allow people to scale the size of the watermark image, change its location relative to the full extent, and also to change the level of transparency.
To achieve these requirements, you must adapt your component. Implement ILayerEffects and return True from the SupportsTransparency property. Create and implement a new interface, IWatermarkLayer, to add properties to scale the image and set its relative location. Again, see the sample code for full details of the implementation.
Add new member variables to your class to store ILayerEffects::Transparency and also the values of the members of IWatermarkLayer.
Private m_iTransparency As Integer ' ILayerEffects::Transparency
Private m_ePosition As watermarkPosition ' IWatermarkLayer::Position
Private m_dScale As Double ' IWatermarkLayer::SymbolScale
As the data that needs to be persisted has now changed, increment the persist version number for the class by one.
Private Const m_iCurrPersistVers As Integer = 2When you change the persistence signature of a component, the new component should still read the original data from the first persistence version if the old version is encountered in Load.
The Save method is updated with the new data being written to the stream after the existing members.
Private Sub IPersistVariant_Save(ByVal Stream As esriSystem.IVariantStream)
...
Stream.Write m_dRatio
Stream.Write m_iTransparency
Stream.Write m_ePosition
Stream.Write m_dScale
End SubNow adapt the Load method to always read from the stream the data saved by both the new and old versions of the component.
If (iSavedVers > 0) And (iSavedVers <= 2) Then
... ' Load first persistence signature
If the data in the persist stream indicates there is new (second version only data), read these values as well.
If iSavedVers = 2 Then
m_iTransparency = Stream.Read
m_ePosition = Stream.Read
m_dScale = Stream.Read
End If
Note that if the loaded version does not have this second version data, these member variables (m_iTransparency, m_dScale, and m_ePosition) are set to default values in the InitializeData routine.
If you have loaded the first version of the persistence pattern, set the second version member variables to default values. If you have loaded the second version of the persistence pattern, read the additional members.
Now compile and deploy version 2 of the WatermarkLayer class. At this point, if the new version of the component encounters an older persisted version, it can load from the persisted data.
Use the ChangeWMPosition and ChangeWMScale commands in the version 2 sample code to change the new IWaterMarklayer properties; use the ArcMap Effects toolbar Adjust Transparency command to change the transparency of the WatermarkLayer.
Once the document is saved again (by the version 2 component), the persisted version will be version 2.
Finally, your customers ask to be able to specify their own choice of Symbol, as they are not happy with the display of the WatermarkLayer. They no longer want to be able to turn off the WatermarkLayer.
Create and implement another interface, IWatermarkSymbol, with a Symbol propertythis value must also be persisted.
Private m_pMarker As esriDisplay.IMarkerSymbol
...
Private Property Set IWatermarkSymbol_Symbol(ByVal RHS As esriDisplay.IMarkerSymbol)
Set m_pMarker = RHS
End PropertyAs this requirement changes the data that needs to be persisted, you should again update the m_iCurrPersistVersion constant to 3.
Private Const m_iCurrPersistVers As Integer = 3To ensure the WatermarkLayer is always Visible, you can remove the member variable m_bVisible and always return True from the Visible property. Don't forget to remove all references to m_bVisible from the rest of your code.
Private Property Get ILayer_Visible() As Boolean
ILayer_Visible = True
End PropertyNext, change the Save method. You no longer need to save the Visible value, but you do need to persist the new Symbol.
Stream CODE id="k">As esriSystem.IVariantStream) Stream.Write m_iCurrPersistVers Stream.Write m_sName Stream.Write m_bCachedPrivate SubIPersistVariant_Save(CODE id="k">ByVal' Stream.Write m_bVisible... Stream.Write m_pMarkerEnd Sub
Last, update your Load method. You still need to read all the items saved at versions 1 and 2; the boolean Visible value is no longer required at version 3, so you can discard the value once read; you must still read the value, however, to advance the stream pointer by the correct amount.
If (iSavedVers > 0) And (iSavedVers <= 3) Then
m_sName = Stream.Read
m_bCached = Stream.Read
If (iSavedVers <= 2) Then
Dim bVisible As Boolean
bVisible = Stream.Read
End If
Set m_pDisplayFilter = Stream.Read
m_dRatio = Stream.Read
If iSavedVers >= 2 Then
m_iTransparency = Stream.Read
m_ePosition = Stream.Read
m_dScale = Stream.Read
End If
End IfTo complete the new Load method, if the persisted version is 3, read the stream again to set the value of the Symbol property of IWatermarkSymbol::Symbol.
If (iSavedVers = 3) Then
Set m_pMarker = Stream.Read
End IfThe WatermarkLayer can now use any MarkerSymbol as its watermark. Attempting to change the visibility of the layer will not have any effect.
In many cases, a new component version will require adding new data to a persistence pattern. The WatermarkLayer example above demonstrates that sometimes a new version of a component may no longer need to save data to the stream.
In these cases, if your new component encounters the older version persisted to a stream, you should always read the obsolete data valuesotherwise, the stream pointer will be left at the wrong location, and the next value will not be read correctly. You can discard the obsolete values once read and save only the required data in the new Save method.
Another possibility is to create a class that does not update to the new persistence pattern if saved by a new version of the component. This would enable old components to Load the persisted object. Note that the persistence version number written at the beginning of the Save method should account for which persistence pattern is used.
To make the implementation of persistence versions more straightforward, you may want to consider the use of a PropertySet. Each version of your component can add more, or different properties as required. The Save and Load events then only need to persist the current PropertySet. If you choose this approach, you should make sure that all your class members are set to their default values at the beginning of a Load event, in case the values of certain class members cannot be found in the current PropertySet.
To allow your component to persist to a document at a particular version of ArcGIS (for example when a user chooses the Save A Copy command in ArcMap) you may wish to implement IDocumentVersionSupportGEN. This interface specifies two pieces of functionality:
If ArcGIS cannot QI to IDocumentSupportVersionGEN for a given persistable object, then it will assume that the object can be persisted, unchanged, to any version of ArcGIS document.
Take, for example, a situation where a custom symbol is applied to a layer in a document at ArcGIS 9.1 and the user chooses to save a copy of the document to an ArcGIS version 8.3 document. The persistence process for the symbol will follow these general steps to create the version-specific document:
The IsSupportedAtVersion method is where you determine to which ArcGIS document versions your component can be persisted. Return true or false from this method, depending on the document version indicated by the parameter passed in to this method. The parameter is an esriArcGISVersion enumeration value.
If, for example, your component can be used equally well at all versions of ArcGIS, you can simply return True from IsSupportedAtVersionalthough in this case you do not need to implement the interface at all.
If however your component relies upon the presence of core objects or functionality which exists only from 9.0 onwards, then you should return false to indicate that the object cannot be used in an ArcGIS 8.3 document. Take the custom logo layer example which is explained above; you can prevent the layer from being saved as it is to an 8.3 document using code like that below.
Private Function IDocumentVersionSupportGEN_IsSupportedAtVersion(ByVal docVersion As esriSystem.esriArcGISVersion) As Boolean
If docVersion = esriArcGISVersion83 Then
IDocumentVersionSupportGEN_IsSupportedAtVersion = False
Else
IDocumentVersionSupportGEN_IsSupportedAtVersion = True
End If
End FunctionIn order to allow users to save a copy of a document with the custom logo layer as an 8.3 document you can then use code like that below to create a basic RasterLayer as an alternative to the custom logo layer, and return this from ConvertToSupportedObject. (This may not be an appropriate solution for every custom layer of course, but the principle can be applied to similar code to create an object appropriate to your own customizations).
Private Function IDocumentVersionSupportGEN_ConvertToSupportedObject(ByVal docVersion As esriSystem.esriArcGISVersion) As Variant
If docVersion = esriSystem.esriArcGISVersion.esriArcGISVersion83 Then
' Note this code relies on the raster dataset file being available at the specified
' location and having an appropriate spatial reference applied.
Dim pWsFact As IWorkspaceFactory, pWs As IRasterWorkspace
Set pWsFact = New RasterWorkspaceFactory
Set pWs = pWsFact.OpenFromFile("C:\MyWorkspace", 0)
Dim pRasterDataset As IRasterDataset
Set pRasterDataset = pWs.OpenRasterDataset("MyLogo.bmp")
If Not pRasterDataset Is Nothing Then
Dim pRasterLy As IRasterLayer
Set pRasterLy = New RasterLayer
pRasterLy.CreateFromDataset pRasterDataset
Set IDocumentVersionSupportGEN_ConvertToSupportedObject = pRasterLy
End If
End If
End FunctionNote that for every esriArcGISVersion for which you return false from IsSupportedAtVersion, you should implement a suitable alternative in ConvertToSupportedObjectif you do not do this then you will prevent users from saving a copy of a document to that version, and the user may not receive any information about why exactly their attempt to save a copy failed. Do not return a null reference either, as ArcGIS may attempt to apply this null reference to a property, which may cause the saved document to become corrupted.
Review the notes below for a recap of your responsibilities when creating persistable components.
Remember that you control exactly what is written to the stream. If your class needs to persist a reference to another custom object, you have two options. First, you could implement persistence on the other custom class, and persist that class as you would any other object.
Alternatively, you could write the members of the secondary class to your persist stream for the primary class. Although the second option may be simpler, the first method is recommended, as it is more maintainable and scalable.
If you raise errors in a stream Load event, this may cause the current structured storage (for example, the current .Mxd file) to be unreadable. You must take care when writing persistence code to ensure you preserve the integrity of storage files.
As mentioned previously, a separate ObjectStream is created for each extension. This situation can lead to problems for components which do not account for this difference.
If you persist an object reference that is already persisted elsewhere in the document and, therefore, to a separate ObjectStream, this will result in two separate objects being persisted. When the document is reloaded, the object you persisted will no longer be the same object that was persisted in the main document ObjectStream. You may want to initialize such objects in the extension startup, rather than in persistence code.
You must ensure your components are consistent with the version compatibility used by ArcGIS. Ensure as a minimum that your components are backwardly compatiblethat a client (for example ArcMap) can open a document that was created with an earlier version of your component.
You should also consider implementing IDocumentVersionSupportGEN, if required, to ensure your component can be opened in a document which is saved as a previous version of ArcGIS. For each ArcGIS version that you return False from IsSupportedAtVersion, you should ensure you return a valid alternative object from ConvertToSupportedObject.