Extending ArcObjects  

Timestamper Class Extension Example

This topic is relevant for the following:
Product(s): ArcGIS Desktop: All
Version(s): 9.0, 9.1, 9.2, 9.3
Language(s): VB6, VC++
Experience level(s): Intermediate to advanced



In this section:

  1. The case for a Timestamper class extension
  2. Implementing a class extension with extension properties
  3. Implementing a feature class property page

Timestamper class extension example

Object Model Diagram Click here.

Example Code Click here.

Description This extension writes date and time information whenever an object is created or changed. The field names that are used to store the information are kept as properties of the extension, configurable via a property page. A new feature class or table with this extension can be created with the ArcCatalog user interface.

Design Subtype of ObjectClassExtension abstract class

License required ArcEditor

Libraries System, Geodatabase, and Framework

Languages Visual Basic, Visual C++

Categories ESRI GeoObject ClassExtensions, ESRI GeoObject ClassDescriptions, ESRI Table Property Pages, and ESRI Feature Class Property Pages

Interfaces IClassExtension, IObjectClassExtension, IObjectClassEvents, IObjectClassDescription, IFeatureClassDescription, and IComPropertyPage

How to use

    1. If using VB, register TimestamperVB.dll, and double-click the TimestamperVB.reg file to register to component categories.

      If using VC++, open and build the project TimestamperVC.dsp, to register the DLL and register to component categories.

    2. Using ArcCatalog, create a new feature class in a geodatabase of your choice. On the first page of the New Feature Class dialog box, there is a combo box for the type of custom object you will store in the feature class, choose 'Timestamped Feature Class'.
    3. After your feature class is created, right-click on it and choose Properties. Go to the Timestamping tab of the Properties dialog box and inspect the settings.
    4. In ArcMap, start editing the feature class and digitize some new features. Inspect the attributes of the features to see the timestamp details.

The case for a Timestamper class extension

Imagine that you would like to keep records on who has been editing your data. For your most important feature classes and tables, you would like to know who created or modified any feature, and at what time the edit was made. In summary, you would like to timestamp data changes.

Whenever a table is edited, you would like to record the date, the time, and the person who made the change.

Timestamping can be achieved by customizing the editor to handle creation and modification events, but there may be several different applications editing the data—each would need to be customized. It may also be awkward to add new feature classes to the scheme.

It is often preferable to implement this kind of business rule as close to the data as possible, to guarantee the rule is enforced. Class extensions offer a way of implementing this kind of rule in the geodatabase. In this example, a class extension solution is presented which uses IObjectClassEvents to trap creation and modification events. The solution also presents the use of class extension properties and a property page to administrate the timestamping. The IObjectClassDescription and IFeatureClassDescription interfaces are used so that a new timestamped table or feature class can easily be created.

It is recommended that you run the Timestamper sample before reading the rest of the text, particularly to see the property page in ArcCatalog.


Implementing a class extension with extension properties

You would like each timestamped object class to be able to use differently named fields to store the information. For example, in one feature class, a field called CREATION_DATE may be specified, whereas in another the field may be named CREATED. Additionally, you would like to be able to configure whether all the information is recorded. For example, you may want to record the creation date, but not the modification date or username. This configuration information belongs with the object class to which the extension is applied, so you need to store it within the geodatabase in the same way as the object class's metadataextension properties are provided for this purpose. The extension properties are stored within the geodatabase in the GDB_OBJECTCLASSES table.

A class extension may be applied to many different object classes. The extension properties allow each of those object classes to be configured differently.

In the example's Init routine, the extension properties are copied into class-level variables so they can be manipulated easily.

[Visual Basic 6]
Private Sub IClassExtension_Init( _
 ByVal pClassHelper As esriGeodatabase.IClassHelper, _
 ByVal pExtensionProperties As esriSystem.IPropertySet)
  
  Set m_pClassHelper = pClassHelper
  m_sUsrName = Environ("USERNAME")
  ' If object class has been just created then,
  ' if the default fields are present, use them
  If pExtensionProperties Is Nothing Then
    Call TryDefaultProperties
  Else
    ' Load extension properties into the module variables
    ' Turn off errors so that if the property is not present 
    ' the module variables will remain as a null string
    On Error Resume Next
    m_sCreFieldName = _
     pExtensionProperties.GetProperty(c_sCreFieldPropName)
    m_sModFieldName = _
     pExtensionProperties.GetProperty(c_sModFieldPropName)
    m_sUsrFieldName = _
     pExtensionProperties.GetProperty(c_sUsrFieldPropName)
    On Error GoTo 0
  End If

The example uses a convention that an empty string for any of the class-level variables indicates that the timestamp field is not in use (for example, information about username is not being collected).

Extension properties can be used to persist almost any kind of data. For example, you may want to store a symbol, perhaps for custom rendering. The symbol would be kept in the database with the feature class and could be modified by the owner of the feature class.

Implementing your own custom interface

You can implement your own interfaces to provide functionality that is particular to your class extension. In the example, a new non-ESRI interface has been created, ITimestampClassExtension, which provides facilities to manage which timestamp fields are in use. This interface has been modelled after the IAnnoClassAdmin and IDimensionClassExtension interfaces. It has various read and write properties to alter the configuration of the timestamp fields and an UpdateProperties method to apply the changes to the geodatabase.

[Visual Basic 6]
Public Sub ITimestampClassExtension_UpdateProperties()
  ' Note that user should have an exclusive schema lock
  ' before calling this method
  
  ' Check if the specified fields exist
  Call GetFieldPositions
  
  ' Make the property set
  Dim pPropSet As IPropertySet
  Set pPropSet = New esriSystem.PropertySet
  pPropSet.SetProperty c_sCreFieldPropName, m_sCreFieldName
  pPropSet.SetProperty c_sModFieldPropName, m_sModFieldName
  pPropSet.SetProperty c_sUsrFieldPropName, m_sUsrFieldName
  
  ' Update the schema
  Dim pClassSchemaEdit2 As IClassSchemaEdit2
  Set pClassSchemaEdit2 = m_pClassHelper.Class
  pClassSchemaEdit2.AlterClassExtensionProperties pPropSet
End Sub

A developer using the class extension could choose to ignore this interface and make changes to the extension properties directly, but the methods and properties on ITimestampClassExtension make it easier. The developer should use ISchemaLock to gain an exclusive lock before changing the extension properties, since effectively the structure of the object class is being changed.

Implementing the IObjectClassEvents interface

IObjectClassEvents lets you catch the creation, modification, and deletion of objects. In the example, this is where the timestamps are made. The coding is simple, as is shown by the OnCreate event:

[Visual Basic 6]
Private Sub IObjectClassEvents_OnCreate(ByVal obj As esriGeodatabase.IObject)
  ' Set the creation date and user name 
  ' For Enterprise geodatabases, it is preferable to use the database 
  ' date and username, but for simplicity this sample will just use the 
  ' client OS date and username.
  Dim pRow As IRow
  Set pRow = obj
  
  If Len(m_sCreFieldName) > 0 Then
    pRow.Value(m_lCreField) = Now
  End If
  
  If Len(m_sUsrFieldName) > 0 Then
    pRow.Value(m_lUsrField) = m_sUsrName
  End If
End Sub

The methods on IObjectClassEvents will be called by an object class before notifying other related and external objects.

Note that there is no need to call IRow::Store after making changes to the object within any of these methods. Indeed, it is Store or Delete that actually causes these events to fire. If, however, you make changes to objects other than the passed one, you will need to call Store as usual.

Note also that the field positions and username have been precalculated in global variables. If the user were to update a few thousand rows at once, this could save significant processor time.

For best performance, minimize the amount of processing in IObjectClassEvents.

The editor's undo and redo events do not cause object class events to fire. Any data edits made by the class extension will be undone or redone automatically, since they form part of the edit operation which is in progress when the object class event fires.

If you are using the OnCreate, OnDelete or OnChange methods to validate edit operations, do not call AbortEditOperation on the workspace if your logic indicates that the edit operation is invalid. Instead, raise an HRESULT error, which will be propagated to the application that is performing the edit on the class. It is the responsibility of the editing application that receives the error to abort the edit operation. This is especially true when editing with ArcMap. If you call AbortEditOperation from within the class extension, the ArcMap undo/redo edit stack will become unsynchronized.

Even if your class extension is concerned with data edits, you should not make any reference to the editor or any other application objects in your code, since they are not guaranteed to be present when your object class is opened.

Implementing the IObjectClassDescription and IFeatureClassDescription interfaces

An ObjectClassDescription provides information for ArcCatalog to use when creating a new object class or feature class. In the New Table or New Feature Class wizards, the ObjectClassDescription results in an entry in the custom object type combo box.

ArcCatalog's New Feature Class wizard uses feature class descriptions.

For a new table you implement just IObjectClassDescription. For feature classes you implement both IObjectClassDescription and IFeatureClassDescription. These interfaces can be implemented on a class extension or on a separate coclass.

In the example, you want to let ArcCatalog users create both new timestamped tables and new timestamped feature classes.

Two new coclasses have been made: on the first, just IObjectClassDescription is implemented; on the second, both IObjectClassDescription and IFeatureClassDescription.

In this example it would not have been possible to implement both interfaces on the class extension, since what is returned by the IObjectClassDescription::RequiredFields property is different for new tables than it is for new feature classes, as feature classes need a geometry field. Moreover, the InstanceCLSID property is different in each case.

[Visual Basic 6]
Private Property Get IObjectClassDescription_RequiredFields() _
 As esriGeodatabase.IFields
  ' Get the required fields for a feature class
  Dim pOCDescription As IObjectClassDescription
  Set pOCDescription = New esriGeodatabase.FeatureClassDescription
  Dim pFieldsEdit As IFieldsEdit
  Set pFieldsEdit = pOCDescription.RequiredFields
  ' Now add the timestamp fields
  Call basUtil.AddTimestampFields(pFieldsEdit)
  Set IObjectClassDescription_RequiredFields = pFieldsEdit
End Property

The code excerpt above makes use of the standard FeatureClassDescription coclass to provide the default ObjectID and geometry fields.

IObjectClassDescription::InstanceCLSID should return the UID of a geodatabase Object, Feature, network feature, custom object or custom feature, as appropriate.

IObjectClassDescription::ClassExtensionCLSID should return the UID of your class extension.

[Visual Basic 6]
Private Property Get IObjectClassDescription_ClassExtensionCLSID() _
 As esriSystem.IUID
  Dim pUID As esriSystem.IUID
  Set pUID = New UID
  pUID.Value = "Timestamper.TimestampClassExtension"
  Set IObjectClassDescription_ClassExtensionCLSID = pUID
End Property

The class that implements the description interfaces (whether it is the class extension or a separate coclass) must be registered to the ESRI GeoObject Class Descriptions component category.

Note the ModelName and ModelNameUnique properties on IObjectClassDescription. In fact model names for object classes must be always be unique within the geodatabase. Because of this fact, they are unsuitable for this kind of ObjectClassDescription, which can be applied to more than one object class. In the example the model name has been set to an empty string. It does not matter what value is returned for ModelNameUnique, as this property is now deprecated. The main use of model names is for UML modelling. Actually, if you create your object classes from a UML model with the Schema Wizard, ObjectClassDescriptions are unnecessary.

The ModelName property can normally be set to an empty string.

It is not possible to further customize either the New Table or New Feature Class wizard; for example, by adding a new page to configure your class extension. The timestamp class extension provides default configuration inside its Init routine, which is guaranteed to run after the object class is created.

[Visual Basic 6]
Private Sub IClassExtension_Init(ByVal pClassHelper As _
 esriGeodatabase.IClassHelper, ByVal pExtensionProperties As _
 esriSystem.IPropertySet)
  
  Set m_pClassHelper = pClassHelper
  
  ' If object class has been just created then, 
  ' if the default fields are present, use them
  If pExtensionProperties Is Nothing Then
    Call TryDefaultProperties
  Else
    ...

Implementing a feature class property page

The timestamper example has a property page that can be used to configure which fields are being used for timestamping. This property page is a client to the custom interface implemented on the class extension, ITimestampClassExtension.

The vital code is in the IComPropertyPage_Applies function. This ensures that the property page is only displayed for object classes that have the timestamper class extension. See 'Developing Objects' for advice on the standard implementation of a property page class.

[Visual Basic 6]
Private Function IComPropertyPage_Applies(ByVal Objects As _
 esriSystem.ISet) As Boolean
  IComPropertyPage_Applies = False
  'Apply if object class has a timestamp extension
  If (Objects.Count < 1) Then
    Exit Function
  End If
  
  Objects.Reset
  Dim pObject As IUnknown
  Set pObject = Objects.Next
  Do Until pObject Is Nothing
    If (TypeOf pObject Is IObjectClass) Then
      Dim pObjectClass As IObjectClass
      Set pObjectClass = pObject
      If Not pObjectClass.Extension Is Nothing Then
        If TypeOf pObjectClass.Extension Is _
         ITimestampClassExtension Then
          IComPropertyPage_Applies = True
          Exit Function
        End If
      End If
    End If
    Set pObject = Objects.Next
  Loop
  
End Function

Your property page should be registered to the ESRI Feature Class Property Pages component category and, if appropriate, to nonspatial data as well such as ESRI Table Property Pages.

The timestamping property page appears when you view the properties of the feature class in ArcCatalog.


Back to top

Go to the VB6 example code or the VC++ example code.

See Also About Class Extensions, Managing Class Extensions, PipeValidation Class Extension Example, Class Extensions and Relationship Classes, and Class Extensions for Annotation and Dimensions.