ArcFM Desktop Developer Guide
Programming ArcFM Lists

Resource Center Home

1. Overview

This technical white paper provides an overview of how lists and list items are programmed in ArcFM. It is written for business partners, clients, or other parties interested in learning how to programmatically access this portion of our object model. This document assumes that the reader is familiar with the use of ArcFM, has knowledge of COM programming, and has prior exposure to general ArcGIS and ArcFM programming.

This information is current as of version 9.1 of the ArcFM Solution. Subsequent versions of the software may affect some of the information presented here.

 

2. Introduction: Lists in the ArcFM Solution

The ArcFM Solution object model has a concept called a "List". Each list contains zero or more child items, called list items. A list item may also act as a list, containing children (additional list items). Using correct computer science terminology, a "List" is really a tree. List items are nodes in the tree.

Lists are pervasive in the ArcFM Solution product. The Selection tab of the Attribute Editor, as well as the QA, Design, and Target tabs, contains Lists. The Features tab and the CU tab are also lists. Internally, information such as ArcFM Properties is stored in Lists.

Let's take the Selection tab as an example. Internally, this tab refers to the D8FeSelTopLevel list object. This list contains list items that are D8Layer objects. These list items in turn contain list items that are D8Feature objects. The UML diagram below shows this relationship:

In the user interface, the D8Layer refers to the layer node in the selection tree, and D8Feature objects are the individual feature nodes under the layer node. The D8FeSelTopLevel object is not shown in the user interface.

The rest of this document contains descriptions of the interfaces and methods that implement list items. No attempt is made to describe each of the specific objects that implement these interfaces (e.g., D8Feature).

 

3. The Basic Interfaces: ID8List, ID8ListItem, and ID8GeoAssoc

Most list functionality is provided by two interfaces: ID8List and ID8ListItem. ID8List is implemented on all objects that can act as a list, and ID8ListItem is implemented on all objects that can act as a list item. In practice, virtually all list objects implement both of these interfaces.

A third important interface is ID8GeoAssoc, which represents an “association to a geodatabase object.” This interface is discussed in a later section of this document. For now, consider the following UML class diagram, which diagrams the relationship between these interfaces:

3.1 Displaying List Items

Each list item has a DisplayName string property. This read-only property returns a string that is used to identify the list item. Each class that implements ID8ListItem is free to decide what string to output. For example, the D8Feature class outputs a string that is the value of the feature’s ArcFM display field.

3.2 Navigating Lists

Navigating a list is similar to using a COM enumerator. Begin by calling the Reset() method, and continue calling Next() until finished.

The following code example demonstrates stepping through a list and printing out the name of each item:

Copy Code
Sub PrintList(ByVal pList As ID8List, ByVal sPrefix As String)
    pList.Reset

    Dim pListItem As ID8ListItem
    Set pListItem = pList.Next
    While Not pListItem Is Nothing
        Debug.Print sPrefix & pListItem.DisplayName
        Set pListItem = pList.Next
    Wend
End Sub

The code above merely steps through the top level of the list. If you wanted to traverse the entire tree, you would add the extra lines below:

Copy Code
Sub PrintList(ByVal pList As ID8List, ByVal sPrefix As String)
    pList.Reset
   
    Dim pListItem As ID8ListItem
    Set pListItem = pList.Next
    While Not pListItem Is Nothing
        Debug.Print sPrefix & pListItem.DisplayName
        If TypeOf pListItem Is ID8List Then
            Dim pChildList As ID8List
            Set pChildList = pListItem
            PrintList pChildList, sPrefix & "   "
        End If
        Set pListItem = pList.Next
    Wend
End Sub

If you selected a single pole in ArcMap, and ran this code on the FeSelTopLevel object, you would get the following output:

Support Structure
   SUPPT1296 
      Assembly
      Dynamic Protective Device
      Fuse
      Joint Use Attachment
      Network Feature
      PF Correcting Equipment
      Streetlight
      Switch
      Transformer
      Voltage Regulator

This is exactly what you would see in the Selection tab.

You can determine whether a list is empty by using the HasChildren() property.

 

3.2.1 Dynamic Lists

A careful look at the Next() method will show an optional bUseDynamic Boolean parameter. Some kinds of List objects support the concept of a "dynamic list." These lists load their children into memory on demand. The concept here is to defer the load time until it is absolutely needed.

D8Layer is, in fact, one of these objects. If you select 1000 poles, we do not build the entire list of selected objects until you click the "+" sign in the Attribute Editor. Only at that time do we iterate through the selected features, building D8Feature objects and adding them to the tree. This sometimes leads to odd behavior, such as an empty list (no children) after clicking a "+" sign in ArcFM. This occurs because we don't actually know if there are children until after we try to load the children, and we don’t load the children until needed for performance reasons.

If a list supports dynamic loading, use the optional bUseDynamic parameter. Set it to false to force a full load, or set it to true to only load the children after the "+" sign is clicked.

Note that HasChildren() also has an optional bUseDynamic parameter that is used the same way.

 

3.2.2 Navigating Up

Navigating up the list is fairly simple. The ContainedBy property on the ID8ListItem interface returns an ID8List interface pointer to the parent object that contains the list item. Of course, if the list item does not have a parent, this property returns Nothing.

3.3 Modifying Lists

List items can be added and removed from lists.

 

3.3.1 Adding ListItems to Lists

There are a number of routines to help you add list items onto lists.

  • Add(pListItem as ID8ListItem) – adds the specified list item to the end.
  • AddEx(vSafeArrayOfItems as Variant) – takes a SafeArray of list items to add to the end. More information about AddEx is included in the "Advanced List Programming" section below.
  • AddFront(pListItem as ID8ListItem) – adds the specified list item to the front
  • Insert(pListItem as ID8ListItem, position as Long) – adds the specified list item to the list, at the location specified by the position parameter. 0 is used to insert at the front of the list.

Another method used to add list items, AddSorted(), is discussed in a section 4.3.1 of this document.

Not all kinds of list items can be added to a specified list. For example, you cannot add an MMAutoValue object, which represents an autoupdater assignment, to a D8Layer object, which represents a map layer in ArcMap. The WillAcceptItem() method can be used to determine if the given list item may be added to the current list.

 

3.3.2 Removing ListItems from a List

There are two routines that remove list items from a list.

  • Remove(pListItem as ID8ListItem) – removes the specified list item from the list
  • Clear() – removes all list items from the list

 

3.3.3 Simultaneously Traversing and Editing a List

If you modify a list while iterating through the list, you MUST call the Reset method before any subsequent calls to Next. Failure to do so will not lead to an error (except maybe a logical error), but you cannot guarantee that the iterator will be positioned where you expect.

3.4 Searching Lists

Searching lists for a particular list item is also fairly easy to do. The building block for this functionality is the SameAs() function on the ID8ListItem interface:

Copy Code
SameAs(pListItem as ID8ListItem, bCompareGeoObject as Boolean, bCompareAttributes as Boolean) as Boolean

This routine compares two list items. There are two sets of information that you may choose to compare on. You may compare the associated geoobject (if the list item has a geoobject), or the attributes of the list item itself, or both. The routine returns true if the list items are the same.

The ID8List interface provides a Find method, defined as follows:

Copy Code
Find(pSearchItem as ID8ListItem, bRecurse as Boolean, bCompareGeoObject as Boolean, bCompareAttributes as Boolean) as ID8ListItem

If the bRecurse method is false, only the current level is searched. Otherwise, a depth-first traversal is used to search the list. This routine calls out to ID8ListItem::SameAs() to determine equality. The routine returns the first matching item.

3.5 ListItems with Geodatabase Rows

There are many types of list items that contain references to rows in a geodatabase. For example, a D8Feature list item points to a feature or object in a feature class or object class. These list items implement the ID8GeoAssoc interface. This interface has a single property, AssociatedGeoRow, which allows you to get and set the row.

Further investigation, however, reveals a number of hidden properties called OID, TableName, and Workspace. Typically they are used internally by ArcFM, but they can be used by external developers as well. The important thing to realize about these properties is that they are stored internally, but a row is not read from the database until the AssociatedGeoRow property is called. This can be used to gain some performance benefits.

 

4. Advanced List Programming

4.1 List Persistence: Geodatbase Blobs

4.1.1 Persisting a List into a Blob

A list can be stored in a COM persistence IStream if it implements the IMMSimplePersistentListItem interface. This interface provides the following methods:

Copy Code
SaveToStream(pStream as IStream)

Writes the current list into a COM persistence IStream. This IStream object can later be stored in a geodatabase or stored elsewhere.

Copy Code
LoadFromStream(pStream as IStream)

Reads the current list from a COM persistence IStream. This gets a little complicated, because you need to know which COM object forms the root of the tree, create an instance of this type, and then call LoadFromStream() on it.

Copy Code
GetDirty() as Boolean

Returns true if the list item (or its children) have changed since the time it was loaded from a stream.

Copy Code
SetDirty() as Boolean

Sets the dirty flag. This is used to signal that the list is "dirty" and needs to be saved. Note that this flag is set on the current list item, and propagated upwards. To mark the tree dirty you only need to call SetDirty(True) on any item within the tree, but to make the tree clean, you must call SetDirty(False) on the root node.

 

4.1.2 Persisting a List into a Geodatabase

Most of the time it is not enough to store a list into an IStream. To store a list into a geodatabase, use the IMMPersistentListItem interface. This interface has all the routines found in IMMSimplePersistentListItem interface and adds the following:

Copy Code
SaveToField(pRow as IRow, lColumnIndex as Long)
LoadFromField(pRow as IRow, lColumnIndex as Long)

These routines save and load from a particular field in a particular database row.

Copy Code
SaveToDB(bstrTreeName as String, pWorkspace as IWorkspace)
LoadFromDB(bstrTreeName as String, pWorkspace as IWorkspace)

These routines save and load from a row in the MM_SYSTEM_PERSIST_INFO table in the given workspace. It uses the row from this table that has the specified TreeName value.

Copy Code
SaveToUserDB(bstrTreeName as String, bstrUserName as String, pWorkspace as IWorkspace)
LoadFromUserDB(bstrTreeName as String, bstrUserName as String, pWorkspace as IWorkspace)

This works like SaveToDB/LoadFromDB, but uses the MM_PERSIST_INFO table and the specified user name.

Example Title
Copy Code
CanSaveToDB(pWorkspace as IWorkspace) as Boolean
CanLoadFromDB(pWorkspace as IWorkspace) as Boolean
CanSaveToUserDB(pWorkspace as IWorkspace) as Boolean
CanLoadFromUserDB(pWorkspace as IWorkspace) as Boolean

These routines check to see if the currently logged in user has rights to access the MM_PERSIST_INFO and MM_SYSTEM_PERSIST_INFO tables.

4.2 List Persistence: XML

Persisting to a blob or to a database record is great, but sometimes you need to translate a list to and from XML.

All of the XML formats take an mmXMLFormat parameter. This parameter specifies the type of XML to be dealt with (e.g., design XML, ArcFM properties, favorites library, etc.)

XML list persistence in the ArcFM Solution uses Microsoft XML 3.

 

4.2.1 Loading from XML

Copy Code
LoadFromDOM(eFormat as mmXMLFormat, pXMLDoc as IXMLDOMDocument)

Loads the list structure using the specified XML file. As with blob persistence (described in section 5.1 above), create the root node first, and then call LoadFromDOM on this node to hydrate the entire tree.

 

4.2.2 Saving to XML

Copy Code
SaveToDOM(eFormat as mmXMLFormat, pXMLDoc as IXMLDOMDocument)

Writes the list structure to the specified XML file.

 

4.2.3 IMMPersistentXML2 Interface

The IMMPersistentXML2 interface adds a property set to the routines defined above. This is used to pass additional information to those components, such as Network Adapter, that require more information before they can import and export to XML.

4.3 Ordered Lists

Sometimes it is necessary to order a list. The ArcFM Solution allows you to do this with the routines listed here.

 

4.3.1 Sort

The Sort routine on ID8List puts the list in sorted order. The Sort routine takes an integer that represents the kind of sorting. Currently, the following sort types are supported:

mmSTAscending

Default alphanumeric sort. Note that "2" sorts before "11" using this method.

mmSTWorkLocation

Sorts using a work location sort order. The work location name is assumed to have the format {text}{number}[-{number}]

mmSTAscendingQAQC

Sorts in ascending order, but puts validation error nodes at the top of the list.

mmSTDuct

Sort used by the Conduit Manager tools.

mmNumericAscending

Correctly sorts numbers. The format is assumed to have the format [{text}][{number}][{text}]. The items are sorted by each part, in order. First by the text prefix, then by the number, then by any remaining text.

Once the list is properly sorted, AddSorted() may be used to add items to this list, retaining the correct sort order.

 

4.3.2 Ordered Lists

Sometimes you need a user to control the way a list is to be ordered, and not sort it based on some pre-defined algorithm. This is accomplished through two interfaces, IMMOrderedList and IMMOrderedListItem.

Items may be moved up and down inside the list either by calling routines on IMMOrderedListItem, or by using routines on IMMOrderedList, passing in the list item to be moved. The routines for these two interfaces are listed below, and should be fairly self-explanatory. The index returned by the Index property is zero-based (i.e., the first element in the list has an Index of zero).

IMMOrderedList
Copy Code
GetFirstItem() as IMMOrderedListItem
GetLastItem() as IMMOrderedListItem
MoveItemDown(pListItem as IMMOrderedListItem)
MoveItemToBottom(pListItem as IMMOrderedListItem)
MoveItemToTop(pListItem as IMMOrderedListItem)
MoveItemUp(pListItem as IMMOrderedListItem)
IMMOrderedListItem
Copy Code
Index as Long
IsBottom() as Boolean
IsTop() as Boolean
MoveDown()
MoveToBottom()
MoveToTop()
MoveUp()

4.4 Copying Lists

List items can be individually copied using the Copy routine on the ID8ListItem interface, defined as follows:

Copy Code
Copy(CopyChildren as Boolean, CopyGeoObj as Boolean) as ID8ListItem

The CopyChildren parameter is used to specify whether to copy just the current list item, or to copy the list item and all of its children. If CopyGeoObj is true, any associated GeoObject is copied as well. Note that the GeoObject reference is copied from one list item to another; the geodatabase row itself is never copied.

At first glance, it would appear that there is no way to copy an entire list, but since every list is also a list item, it's a single function call.

4.5 Increasing Editing Performance with AddEx

The Add method in the ID8List interface is a convenient way to add items to an object that supports ID8List. However, the Add method fires an ItemAdded event every time it is called. This is problematic if you are adding hundreds or thousands of items.

The AddEx method supports adding multiple items by passing in a safearray. After adding all of the items in the safearray, the method will fire the ItemRebuilt event.

The method supports two different kinds of arrays:

ID8ListItem Array: Visual Basic Example
Copy Code
Dim ItemArray(0 To 2) As ID8ListItem
Dim pItem As ID8ListItem
   
Dim i As Integer
For i = LBound(ItemArray) To UBound(ItemArray)
Set pItem = New D8ListItem
Set ItemArray(i) = pItem
Next I

Dim pList as ID8ListEx
Set pList = New D8List
PList.AddEx ItemArray
ID8ListItem Array: C++ Example
Copy Code
SAFEARRAY* psa = NULL;
psa = SafeArrayCreateVectorEx(VT_UNKNOWN, 0, lNumItems, (void*)&IID_ID8ListItem);
CHECK_NULL_MSG(psa,"Creating safearray");

long* pElement;
hr = SafeArrayAccessData(psa, (void**)&pElement);
CHECK_FAIL_MSG(hr, "Accessing safearray data");

CComPtr<ID8ListItem> pItem;
for (long i=0; i<=lNumItems; ++i)
{
     hr = pItem.CoCreateInstance(CLSID_D8ListItem);
     CHECK_FAIL_MSG(hr,"Create a generic item");
    
     pElement[i] = (long)pItem.Detach();
     pItem = NULL;
}   

hr = SafeArrayUnaccessData(psa);
CHECK_FAIL_MSG(hr,"Unaccessing safearray data");

CComVariant vArray;
vArray.vt = VT_ARRAY | VT_UNKNOWN | VT_BYREF;
vArray.pparray = &psa;

hr = AddEx(vArray);
CHECK_FAIL_MSG(hr,"Adding features to layer");

vArray.vt = VT_EMPTY;
hr = SafeArrayDestroy(psa);
CHECK_FAIL_MSG(hr,"Destroy safearray");

ID8ListItem Variants: Visual Studio Example
Copy Code
Dim ItemArray(0 To 2) As Variant
Dim pItem As ARRAYTESTLib.Item
   
Dim i As Integer
For i = LBound(ItemArray) To UBound(ItemArray)
Set pItem = New ARRAYTESTLib.Item
pItem.Value = i
Set ItemArray(i) = pItem
Next I

Dim pList as ID8ListEx
Set pList = New D8List
PList.AddEx ItemArray

5. Appendix: Top-Level Objects in the ArcFM Solution

The ArcFM Solution contains several global lists that can be accessed by application developers. Their names are often historical, and in some cases no longer accurately describe the object’s function. The table below describes these lists and how to access them.

List Name

Contents

How to Access

ConfigTopLevel

ArcFM Properties Cache

Create a new MMConfigTopLevel object (it is a COM singleton)

CuSelTopLevel

Target tab in the ArcFM Attribute Editor

ArcMap extension with the name "CuSelTopLevel"

CuTopLevel

CUs Tab

ArcMap extension with the name "CuTopLevel"

FeatureTopLevel

Features Tab

ArcMap extension with the name "FeatureTopLevel"

FeSelTopLevel

Selection tab in the ArcFM Attribute Editor

ArcMap extension with the name "FeSelToplevel"

QAQCTopLevel

QA tab in the ArcFM Attribute Editor

ArcMap extension with the name "QAQCTopLevel"

D8TopLevel

Design tab in the ArcFM Attribute Editor

ArcMap extension with the name "DesignerTopLevel"

 

 

 


Send Comment to ArcFMdocumentation@schneider-electric.com