ArcGIS SDK  

Extending ArcObjects

InfoTextElement Example

In this section:

  1. The case for a custom Graphic Element
  2. Creating the Info Text Element
  3. Plugging your custom element into ArcMap
  4. Creating a new InfoTextElement in ArcMap
  5. Creating a property page for the InfoTextElement

Info text element example

Object Model Diagram Click here

Example Code Click here

Description The project provides a graphic element, which adds text automatically to a page layout or map. The text can report the current user, computer name, map document path, author of the document, and list of templates. The property pages allow the user to select what text is required and to change the appearance of the text.

Design InfoTextElement is a subtype of the Element abstract class, with accompanying property page coclass InfoTextPropertyPage. A command is also included (NewInfoTextCommand) to add the element to the active view

License ArcView or above.

Libraries ArcMapUI, Carto, Display, DisplayUI, Framework, Geometry, System, and SystemUI.

Languages Visual Basic.

Categories ESRI Element Property Pages, ESRI Mx Commands

Interfaces IElement, IElementProperties, IBoundsProperties, IGraphicElement, ITextElement, IClone, IPersistVariant, and ITransform2D.

How to use

    1. If using VB, register InfoTextElementVB.dll and double-click the InfoTextElementVB.reg file to register to component categories.
    2. If using VC++, open and build the project InfoTextElementVC.dsp to register the DLL and register to component categories.
    3. Open ArcMap.
    4. Open the Customize dialog box, click the Commands tab, click 'Extending ArcObjects' in the left-hand list, and drag the NewInfoText tool onto a toolbar. Close the Customize dialog box.
    5. Click the New InfoText tool and click-and-drag a rectangle on the map.

      This will create a new InfoTextElement on the map. Right-click the element to see the property page—try changing the settings to change the information displayed or the font used.


The case for a custom Graphic Element

A typical map consists of many different elements. As well as the geographical features of a map, many additional elements help the map to communicate its purpose. North arrows, legends, scalebars, and titles are all common elements of a well-annotated map.

In addition to these, you may want to add other items to help explain a map's purpose and content—explanatory text, diagrams, flow charts, arrows, and so forth.

In many applications such as Microsoft Word and Excel, it is also possible to add header and footer information to a view or page, which allows you to easily add information such as the location of a document on disk, the current user and computer, and so on, which can be especially helpful when printing out maps in large organizations.

In ArcGIS, you can add a graphic element containing text to your map or page layout to list such information.

However, a standard text element simply draws a static string of text. If the document is moved or saved to a different location, the text stored in the element will not reflect these changes. If another user opens the document, they will need to update the information.

If the document is opened on a different machine, these changes will also need to be made to the text.

You could create a command or macro that updates this information. However, you would either need to ensure the macro was run as appropriate by customizing the normal template or ensure the update function was run when opening the document.

Alternatively, you could create a custom graphic element, which automatically adds the required text and keeps the information up-to-date.



Creating the Info Text Element

To solve the requirements of this example, you will create a subtype of GraphicElement, called InfoTextElement. This class adds a piece of text to a map or page layout, reporting the current user, computer name, document path, document author, and the templates used. You will provide the ability to switch off each piece of information independently.


You will implement IElement, IElementProperties, IBoundsProperties, and ITransform2D, as well as the standard interfaces for cloning and persistence. For maximum flexibility, the element you will create should be able to appear in either page layout or data view and will, therefore, create a class that implements IGraphicElement. As you will be drawing text, a separate frame is not requiredTextSymbols have their own backgrounds. Therefore, you will not implement IFrameElement.

The InfoTextElement will add information automatically to a map.

Although the element will display text, you will not implement ITextElement the Text property page (displayed for classes that implement ITextElement) should not apply to the InfoTextElement, as users should not be able to change the actual text of the element themselves. However, like the existing TextElement, you will sink the ITransformEvents events interface. This will allow you to provide correct scaling behavior of your element when the view scale changes; see the `Implementing ITransformEvents' section for more details.

To add the custom functionality and to allow the element to be identified programmatically, you will also create and implement a custom interface called IInfoElement.

To allow users to add an InfoTextElement to a dataframe, you will create an ArcMap Command. To allow users to change the properties of an InfoTextElement in the UI, you will also create a property page for your element.

Now you will look in more detail at each interface and see how to implement the important members of the InfoTextElement coclass.

The example project also includes a property page for the element and a custom tool to allow users to create new InfoTextElements in ArcMap.

Creating and Implementing IInfoElement

Your InfoTextElement needs to be able to calculate the required information automatically. You must also provide a way for users to specify which bits of information should be included in the displayed text and to change the TextSymbol used to draw the text.

To achieve these goals, create an interface called IInfoElement. Add five read-write boolean properties to the interface, called ShowUser, ShowComputer, and so on. Add another read-write property to allow clients access to the Symbol and a read-only property to allow quick access to the current Text for convenience.

The custom IInfoElement interface will allow clients to specify which information is displayed by the element. It also allows clients to identify instances of InfoTextElement.

Now implement IInfoElement on the InfoTextElement class. Create member variables to store the values of its properties. Implement each property to store or return the appropriate variable as shown in the ShowAuthor property below.

[Visual Basic 6]
Private Property Let IInfoElement_ShowAuthor(ByVal RHS As Boolean)
  m_bShowInfo(3) = RHS
End Property

The value of the Show properties and the Symbol property are initialized in the class initialization code and later will be set by the property page you will create in the 'Plugging your custom element into ArcMap' section later in this topic.

Calculating the text values

Next, you will calculate the automatic text of your custom element—for efficiency, the values will be calculated as little as possible.

Next, you will create the GetAutoText function, which will return the automatic text of your custom element, based on the values of the IInfoElement properties. For example, if the ShowUser property is true, the first piece of text to appear will be the username of the current user, which is cached at initialization.

[Visual Basic 6]
Private Function GetAutoText() As String
  Dim sTemp As String
  If m_bShowInfo(0) Then
    sTemp = "User: " & m_sUser
    GetAutoText = GetAutoText & vbNewLine & sTemp
  End If
  ...

To complete the path, templates and author information, your element requires access to the currently running Application object. Add a member variable to the element class and set it in the class initialization code, after which the document path, templates, and author can be determined.

[Visual Basic 6]
Private m_pApplication As esriFramework.IApplication

If an InfoTextElement is instantiated outside ArcMap (for example, in a map control), there will be no running Application. See the `Coding Interface Members' section in Chapter 2, `Developing Objects', for more information about the technique used to identify the running process and obtain an Application reference safely.

AppRef is used to get a reference to the current document. As elements may be instantiated outside the ArcMap process, all the element code needs to account for this without causing errors.

To see how the rest of the information is calculated, see the GetUserString, GetComputerString, GetDocPath, GetTemplates, and GetAutoText functions in the accompanying example code.

Determining the available information options

Last, add a read-only property internal to the project called EnableAppOptions. You will use this later from the property page you will create.

[Visual Basic 6]
Friend Property Get EnableAppOptions() As Boolean
  EnableAppOptions = Not (m_pApplication Is Nothing)
End Property

Implementing IElement

IElement provides clients with access to the shape of an element. It also provides functions for drawing and performing hit tests on the element.

IElement provides properties and methods based on the shape of an element.

To begin, implement the Geometry property to simply store a reference to a clone of the geometry passed in—when a user interacts with an element (for example, by moving it around in a view), the system will set the element's Geometry property with the new shape.

Check that the geometry type is appropriate for the element—a Point is sufficient to locate an InfoTextElement, as the height and width of the element will be determined by the font.

[Visual Basic 6]
Private Property Let IElement_Geometry(ByVal pGeometry As esriGeometry.IGeometry)
  If TypeOf pGeometry Is esriGeometryType.esriGeometryPoint Then
    Set m_pGeometry = CloneMe(pGeometry)
  End If
End Property

User interaction with a SelectionTracker

Next, create a selection tracker object. This will be used by ArcMap to allow users to interact with your element in the ActiveView. The element will always be rectangular, unless it is rotated; therefore, you will use a PolygonTracker instead of an EnvelopeTracker, as an Envelope is not rotatable.


Add a member variable to hold a selection tracker object to your class. Then initialize the tracker in your class initialization code.

[Visual Basic 6]
Private m_pSelectionTracker As esriDisplay.ISelectionTracker
...
Private Sub Class_Initialize()
  Set m_pSelectionTracker = New PolygonTracker
  m_pSelectionTracker.Locked = False
  m_pSelectionTracker.ShowHandles = False
  ...

You will return this tracker object from the SelectionTracker property.

[Visual Basic 6]
Private Property Get IElement_SelectionTracker() As esriDisplay.ISelectionTracker
  Set IElement_SelectionTracker = m_pSelectionTracker
End Property

Whenever a change is made to the size, shape, or location of an element, this change must be reflected in its selection tracker. To do this, create a routine called RefreshTracker. First, update the Display property of the SelectionTracker from the cached Display m_pDisplay (see the Activate method for information on when this variable is set), as it may have changed since the last time the tracker was refreshed. There will be no cached Display until the initial call to Activate, so check this member before using it. Then use the QueryOutline method of IElement to calculate the new shape of the tracker. You will implement QueryOutline later.

[Visual Basic 6]
Private Sub RefreshTracker()
  If m_pCachedDisplay Is Nothing Then Exit Sub
  
  Set m_pSelectionTracker.Display = m_pCachedDisplay
  Dim pOutline As esriGeometry.IGeometry
  Set pOutline = New esriGeometry.Polygon
  IElement_QueryOutline m_pCachedDisplay, pOutline
  m_pSelectionTracker.Geometry = pOutline
End Sub

To reflect changes in the element's shape, RefreshTracker needs to be called when the Geometry property is set and also in the Activate method. Later, you will also use RefreshTracker in the members of ITransform2D.

The Geometry held by a SelectionTracker is set by value. Therefore, each time the shape or location of an element changes, the tracker's Geometry must be updated.
You, therefore, need to update the geometry of the tracker when the element's Geometry or Symbol changes. You will also need to update the tracker from other interface members you will implement later.

Element activation and deactivation

When a user activates a new view, elements are informed of the change by the Activate and Deactivate methods. For example, when you switch from data view to page layout view, Deactivate will be called on all elements in the data view, and Activate will be called on all elements in the page layout view; this process happens in reverse when the views are switched back. Activation and deactivation also occur at other points, for example, when you activate a different dataframe. These methods give elements the opportunity to allocate or deallocate resources, connect to other objects, or cache display settings.

When a user selects a new view, Deactivate is called on the elements in the previous view, then Activate is called on the elements in the selected view. At this point, elements can allocate or deallocate resources.
In Activate, the main action you should take is to store a reference to the currently activated screen display which is passed in. You will need to use this later in other members of IElement and other interfaces.

For the InfoTextElement, the Activate method just needs to cache the passed-in Display, then update the selection tracker with this new reference.

[Visual Basic 6]
Private Sub IElement_Activate(ByVal Display As esriDisplay.IDisplay)
  Set m_pCachedDisplay = Display
  RefreshTracker
End Sub

In Deactivate you can release the reference to the cached Display.

[Visual Basic 6]
Private Sub IElement_Deactivate()
  Set m_pCachedDisplay = Nothing
End Sub

After the view in which an element resides is activated, the element will be drawn as the view is refreshed. Draw is straightforward to complete—simply draw the text to the display.

[Visual Basic 6]
Private Sub IElement_Draw(ByVal Display As esriDisplay.IDisplay, _
 ByVal TrackCancel As esriSystem.ITrackCancel)
  Display.SetSymbol Nothing
  Display.SetSymbol m_pTextSym
  Display.DrawText m_pPointGeometry, m_sAutoText
End Sub

The Display passed to Draw will be a reference to the same object that was passed to Activate—unless the view is currently being exported or printed. Therefore, always use the passed-in reference to perform the Draw.

You will also need to use an IDisplay reference to complete the Draw, QueryOutline, and QueryBounds members. These members receive an IDisplay reference directly, which should be used in preference to the cached reference, as the element may be requested to draw to a printer or output file, instead of the currently active view.

Boundaries and outline of an Element

In the QueryOutline client-side storage property, you need to populate a Polygon with the shape of the element, accounting for its current Text, Symbol and Geometry. First, clear any existing shape from the Outline parameter, update the element text, and set a reference to the current DisplayTransformation.

[Visual Basic 6]
Outline.SetEmpty
m_pTextSym.Text = GetAutoText
Dim pTransform As esriDisplay.IDisplayTransformation
Set pTransform = Display.DisplayTransformation

If you are working in VB, take particular care with your object references when coding the client-side storage members QueryOutline and QueryBoundary.

To return the outline of the element, you will need to QI for ISymbol on the TextSymbol. Use the QueryBoundary method and the Geometry of the element to calculate the outline of the element.

[Visual Basic 6]
Dim pSym As esriDisplay.ISymbol
Set pSym = m_pTextSym
pSym.QueryBoundary frmResource.hDC, pTransform, m_pGeometry, Outline

Use ISymbol::QueryBoundary method to calculate the outline of an element.

You can make use of the QueryOutline method when you complete the QueryBounds method—call QueryOutline, and populate the Bounds parameter by using the IGeometry::QueryEnvelope method.

[Visual Basic 6]
Private Sub IElement_QueryBounds(ByVal Display As esriDisplay.IDisplay, _
  ByVal Bounds As esriGeometry.IEnvelope)
  Dim pOutline As esriGeometry.IGeometry
  Set pOutline = New esriGeometry.Polygon
  IElement_QueryOutline m_pCachedDisplay, pOutline
  pOutline.QueryEnvelope Bounds
End Sub

It is worth noting that in many cases, QueryBounds is used by clients as an alternative to QueryOutline. For many elements, QueryBounds gives a rougher approximation of the shape of an element and is correspondingly more efficient to call. If a custom element has a QueryOutline method, which may be time-consuming (especially if called frequently in a loop), consider creating a more efficient QueryBounds method if an approximation can be easily calculated.

If possible, consider coding QueryBounds as a faster, rougher approximation of the same of an element than QueryOutline. You may find it useful to use Windows API calls to quickly approximate the extent of a piece of text.

You can also make use of the QueryOutline method when coding the HitTest method. First, create a Point object from the x and y coordinates passed in. Then retrieve the outline of the element, and use the IRelationalOperator's Disjoint method to find out if the Point lies inside the outline.

[Visual Basic 6]
Private Function IElement_HitTest(ByVal X As Double, ByVal Y As Double, _
 ByVal Tolerance As Double) As Boolean
  Dim pPt As esriGeometry.IPoint
  Set pPt = New esriGeometry.Point
  pPt.PutCoords X, Y
  
  Dim pOutline As esriGeometry.IRelationalOperator
  Set pOutline = New esriGeometry.Polygon
  IElement_QueryOutline m_pCachedDisplay, pOutline
  IElement_HitTest = Not pOutline.Disjoint(pPt)
End Function

ArcMap determines if a user is trying to select a particular element by calling the HitText method to see if the mouse coordinates lie inside the outline of the element.

The Locked property was designed for use with graphic elements that are stored in a read-only geodatabase, and therefore, the InfoTextElement ignores the value passed to Locked.

Annotation elements (elements that implement IAnnotationElement) can implement the Locked property by retrieving the Feature associated with the annotation element, and checking the associated Workspace—if the workspace is currently being edited, then Locked should return false, and the Geometry of the element should be updatable.

Implementing IGraphicElement

Implementing IGraphicElement allows an element to appear in a dataframe, as it can account correctly for the coordinate system of the dataframe. Features store their Geometry and SpatialReference independently and are reprojected on-the-fly when drawn to a dataframe that has a different SpatialReference; however, elements are assumed to be in the same SpatialReference as the view in which they are displayed.

IGraphicElement allows an element to be projected to any coordinate system. It provides correct behavior for an element in the data view.

When the SpatialReference property is set, project the Geometry to the new SpatialReference; there is no need to check if the two SpatialReference values are equal, as Project will perform this check internally. Note that the SpatialReference property of an Element is held separately to the SpatialReference of the Element's Geometry, in the member variable m_pNativeSpatialRef.

[Visual Basic 6]
Private m_pNativeSpatialRef As esriGeometry.ISpatialReference
...
Private Property Set IGraphicElement_SpatialReference(ByVal _
 SpatialReference As esriGeometry.ISpatialReference)
  ' pSpatialReference may  be null
  Set m_pNativeSpatialRef = SpatialReference
  UpdateElementSpatialReference
End Property

An element's Geometry should always have a SpatialReference the same as the current DisplayTransformation.

Now add the UpdateElementSpatialRef routine to perform the projection.

[Visual Basic 6]
Private Sub UpdateElementSpatialReference()
  If Not m_pNativeSpatialRef Is Nothing Then
    If Not m_pPointGeometry Is Nothing Then
    
      If m_pPointGeometry.SpatialReference Is Nothing Then
        Set m_pPointGeometry.SpatialReference = _
        m_pCachedDisplay.DisplayTransformation.SpatialReference
      End If
      m_pPointGeometry.Project m_pNativeSpatialRef

      RefreshTracker
    End If
  End If
End Sub

The SpatialReference of an element may not be set on the first call to this property—in this case, you can use the SpatialReference of the cached Display as the initial native spatial reference of the Element.

The SpatialReference property of an element may not always be set before it is used—your code needs to account for this.

Implementing IElementProperties

All elements, frames, and graphics implement the IElementProperties interface; it provides functionality generally used by developers to identify elements and store custom properties on the element.

IElementProperties mainly provides ways for a programmer to add different types of information to an element. However, the AutoTransform property is used by the ITransform2D interface.

For the Name, Type and CustomProperty members, you should store and return data as required. For the Name property, simply allow a user to store a string. To be consistent with existing elements, this property is null by default. From the Type property return a string indicating the class of element—by default return "InfoTextElement". CustomProperty should hold an empty variant by default.

AutoTransform indicates which aspects of the element should be affected by using the ITransform2D interface. If AutoTransform is False, a transformation should only affect the Geometry of an element; if True, an element should also transform its Symbol or other properties as appropriate. For the AutoTransform property, simply return or store a boolean value—you will use it later when implementing ITransform2D.

[Visual Basic 6]
Private Property Let IElementProperties_AutoTransform(ByVal AutoTransform _
 As Boolean)
  m_bAutoTrans = AutoTransform
End Property

Implementing IElementProperties2

IElementProperties2 should always be implemented on a custom Element—newer commands and tools may use this interface. This interface duplicates all the members of IElementProperties and adds two new ones. Return True from CanRotate, because text can be rotated to any angle, by setting the ITextSymbol::Angle property.

IElementProperties2 replicates the members of IElementProperties, and adds properties to determine how an element's Symbol is treated.

For the ReferenceScale property, return or store a double value indicating the reference scale—this value will be accounted for by the DisplayTransformation.

[Visual Basic 6]
Private m_dRefScale As Double

Implementing IBoundsProperties

IBoundsProperties is used to determine how an element can be scaled. Return True from the read-only FixedSize property indicates that the InfoTextElement is an element whose size is determined not by its Geometry, but by its Symbol.

[Visual Basic 6]
Private Property Get IBoundsProperties_FixedSize() As Boolean
  IBoundsProperties_FixedSize = True
End Property

You should also return True from the FixedAspectRatio property (and ignore any attempts to set the property), because if an element has a fixed size, its aspect ratio must also be fixed.

If FixedSize returns False, the Fixed Aspect Ratio check box on the Size and Position property page will be enabled; if FixedAspectRatio is True, the check box will be checked. The property page will calculate size and position changes based on these settings.

The Fixed Aspect Ratio check box on the Size and Position property page uses the IBoundsProperties interface to determine its availability and value.

Implementing ITransform2D

The Size and Position property page uses an element's ITransform2D interface to change an element; ITransform2D is also used in the element's context menu by the Nudge, Rotate and Flip, Align, and Distribute context-menu commands.

The ITransform2D interface allows an element to be moved, rotated, and scaled. The Size and Position property page uses the ITransform2D::Transform method to change height, width, and origin of an element. The other ITransform2D members are used by other ArcMap commands and tools.

For the Move and MoveVector methods you can simply forward the call to the ITransform2D interface of the element's Geometry and refresh the tracker after the transformation.

[Visual Basic 6]
Private Sub ITransform2D_Move(ByVal dx As Double, ByVal dy As Double)
  Dim pTransform2D As esriGeometry.ITransform2D
  Set pTransform2D = m_pPointGeometry
  pTransform2D.Move dx, dy
  RefreshTracker
End Sub

As the InfoTextElement has a Point Geometry, rotation of the Element will not affect the orientation of the text itself; therefore, you should also check the value of the AutoTransform property you stored when implementing IElementProperties.

[Visual Basic 6]
Private Sub ITransform2D_Rotate(ByVal Origin As esriGeometry.IPoint, _
 ByVal RotationAngle As Double)
  Dim pTransform2D As esriGeometry.ITransform2D
  Set pTransform2D = m_pPointGeometry
  pTransform2D.Rotate Origin, RotationAngle
  
  If m_bAutoTrans Then
    m_pTextSym.Angle = NewRotateAngle(RotationAngle)
  End If
  RefreshTracker
End Sub

If AutoTransform is True then the Symbol should also be rotated—set the Angle of the TextSymbol by adding the new rotation value to the existing Angle.

[Visual Basic 6]
Private Function NewRotateAngle(ByVal dAngle As Double) As Double
  NewRotateAngle = m_pTextSym.Angle + RAD2DEG(dAngle)
End Function
  
Private Function RAD2DEG(ByVal Radians As Double) As Double
  RAD2DEG = Radians * (180# / PI)
End Function

ITransform2D methods should check the value of the AutoTranform property—if True, the element's Symbol needs to be transformed as well as the element's Geometry.

You can complete the Scale and Transform methods in a similar manner by first transforming the Geometry and accounting for the AutoTransform value. Note that the Scale method cannot scale both height and width, as the InfoTextElement has a fixed aspect ratio.

[Visual Basic 6]
Private Sub ITransform2D_Scale(ByVal Origin As esriGeometry.IPoint, _
 ByVal sx As Double, ByVal sy As Double)
  
  Dim pTransform2D As esriGeometry.ITransform2D
  Set pTransform2D = m_pPointGeometry
  With pTransform2D
    .Scale Origin, sx, sy
  End With
  If m_bAutoTrans Then
    If sy <> 1 Then
      m_pTextSym.Size = m_pTextSym.Size * sy
    ElseIf sx <> 1 Then
      m_pTextSym.Size = m_pTextSym.Size * sx
    End If
  End If
  
  RefreshTracker
End Sub

The Transform method needs to account for translation, scaling, and rotation.

Implementing ITransformEvents

The majority of elements determine not only their location but their size and shape by their Geometry. Text-based elements are different—the location is determined by the Geometry, but the shape and size are determined by the current TextSymbol. This results in unexpected behavior for the SelectionTracker of a text-based element when the map scale is changed, as the SelectionTracker after the scale change will have a Geometry that is incorrect for the new map scale.

To correct this behavior, you can process the BoundsUpdated event of the current DisplayTransformation.


By sinking the outbound ITransformEvents interface of the DisplayTransformation, you can update your element to reflect changes such as the dataframe being rotated. You can also use ITransformEvents to update the tracker geometry correctly when the map scale changes.

Add a member variable to store the default outbound interface of DisplayTransformation, ITransformEvents.

[Visual Basic 6]
Private WithEvents m_pDisplayTrans As DisplayTransformation

Now hook up this variable to the DisplayTransformation in Activate.

[Visual Basic 6]
Private Sub IElement_Activate(ByVal Display As esriDisplay.IDisplay)
  Set m_pCachedDisplay = Display
  Set m_pDisplayTrans = Display.DisplayTransformation
  RefreshTracker
End Sub

Now refresh the tracker in the BoundsUpdate event.

[Visual Basic 6]
Private Sub m_pDisplayTrans_BoundsUpdated(ByVal sender As _
 esriDisplay.IDisplayTransformation)
  RefreshTracker
End Sub

Implementing IClone, IPersistStream, and IPersistVariant

Cloning and persistence functionality are essential for any element. Your InfoTextElement should, therefore, implement IClone. If you are working in VC++, you should also implement IPersist and IPersistStream; if working in VB, implement IPersistVariant.

Elements must be clonable and persistable. See Chapter 2, 'Developing Objects', for general information on coding cloning and persistence methods.

In the Save persistence method, don't forget to update the document path, as saving the document may change this value. There is no need to persist the references to the current Application, SelectionTracker, or cached Display, as these will be set when the Element is re-created.

[Visual Basic 6]
Private Sub IPersistVariant_Save(ByVal Stream As esriSystem.IVariantStream)
  m_sDocPath = GetDocPathString
  m_pTextSym.Text = GetAutoText
  
  Stream.Write m_lCurrVers
  Stream.Write m_pPointGeometry
  Stream.Write m_sElementName
  Stream.Write m_sElementType
  Stream.Write m_dRefScale
  Stream.Write m_pNativeSpatialRef
  Stream.Write m_pTextSym
  Stream.Write m_bShowInfo(0)
  ...
End Sub

You may also want to persist the IElementProperties CustomProperty method, in which case you would need to check if the set variant contains a persistable data type.


Plugging your custom element into ArcMap

Your custom element class is now ready to be used programmatically. However, to improve the usability of the element, there are two more issues you should consider. Using the ArcMap user interface, users should be able to create your element, add it to a document, and edit the properties of the element.


Creating a new InfoTextElement in ArcMap

If you are working in data view, you can add standard graphic elements to a document by selecting the appropriate shape from the Drawing Tools button on the Drawing toolbar, then tracking the element's shape onto the view as required. Alternatively, to add text or callouts, use the Text Tools button.

Elements are created in ArcMap by using either the Drawing Tools or Text Tools tool on the Drawing toolbar.

If you are working in layout view, you can again use the drawing toolbar to add graphic elements, or use the Insert menu to add other types of elements such as a Neatline (FrameElement).

These commands and tools are hardcoded to create each type of graphic or frame element—there is no component category that contains elements. You must, therefore, create a new command or tool to add a custom element to the ActiveView.

As the InfoTextElement is a graphic element, you will create a new tool that allows users to click on the ActiveView at the point they want to place an InfoTextElement. This behavior is similar to that used by the New Text tool.

Creating the NewInfoTextTool

Add a new class to your project called NewInfoTextTool and implement the ICommand and ITool interfaces in that class. In the ICommand::OnCreate method, store a reference to the Application.


[Visual Basic 6]
Private m_pApp As esriFramework.IApplication
...
Private Sub ICommand_OnCreate(ByVal Hook As Object)
  Set m_pApp = Hook
End Sub

You should perform the majority of the work for this tool in the ITool::OnMouseDown method. If the left button has been clicked, create a Point in Map units. Set the Point's SpatialReference property to that of the Map.

[Visual Basic 6]
If Button = 1 Then
  Dim pPoint As esriGeometry.IPoint, pMxApp As esriArcMapUI.IMxApplication
  Set pMxApp = m_pApp
  Set pPoint = pMxApp.Display.DisplayTransformation.ToMapPoint(X, Y)
  
  Dim pMxDoc As esriArcMapUI.IMxDocument, pMap As esriCarto.IMap
  Set pMxDoc = m_pApp.Document
  Set pMap = pMxDoc.ActiveView.FocusMap
  Set pPoint.SpatialReference = pMap.SpatialReference

Create a new InfoTextElement in the tool's OnMouseDown method. This can be used as the Geometry of a new InfoTextElement.

Next, create a new InfoTextElement, set its Geometry to the Point you just created, and QI for the IDocumentDefaultSymbols interface of the current MxDocument to set the IInfoElement::Symbol property.

[Visual Basic 6]
Dim pElement As esriCarto.IElement, pInfoEl As IInfoElement
Set pElement = New GraphicElementVB.InfoTextElement
pElement.Geometry = pPoint
Set pInfoEl = pElement

Dim pDefaultSymbols As esriArcMapUI.IDocumentDefaultSymbols
Set pDefaultSymbols = pMxDoc
Set pInfoEl.Symbol = pDefaultSymbols.TextSymbol

Default symbols for any new graphic element should generally be taken from the IDocumentDefaultSymbols interface.

Add the InfoTextElement to the GraphicsContainer of the ActiveView, select the new element, and use a PartialRefresh to redraw that area of the view.

[Visual Basic 6]
Dim pGCont As esriCarto.IGraphicsContainer
Dim pGContSelect As esriCarto.IGraphicsContainerSelect
Set pGCont = pMxDoc.ActiveView.GraphicsContainer
pGCont.AddElement pElement, 0
Set pGContSelect = pGCont
pGContSelect.UnselectAllElements
pGContSelect.SelectElement pElement
pMxDoc.ActiveView.PartialRefresh esriViewGraphics, pElement, Nothing

Last, to be consistent with other tools that create new elements, set the CurrentTool in ArcMap to be the Select Elements tool.

[Visual Basic 6]
Dim pItem As esriFramework.ICommandItem, u As New esriSystem.UID
u = "{C22579D1-BC17-11D0-8667-0000F8751720}"
Set pItem = m_pApp.Document.CommandBars.Find(u)
Set m_pApp.CurrentTool = pItem

Provide standard implementations of all other members of ICommand and ITool. Register the command to the ESRI Mx Commands component category.

Using the NewInfoTextTool

In ArcMap, add the command to a toolbar by opening the Customize dialog box and dragging the command onto any toolbar. Select the NewInfoTextTool, then click a location on a view to add a new InfoTextElement at that location.

Right-click the element and choose Properties to view the element properties dialog box.


Creating a property page for the InfoTextElement

To edit the properties of any graphic element in ArcMap, click the pointer tool and right-click the graphic element in either data or layout view. Alternatively, select a number of graphic elements before right-clicking to see the properties common to all the selected elements.

Existing element property pages

The Properties dialog box will check for property pages in up to three component categories. Pages registered to ESRI Element Property Pages are always checked. If the element or elements being edited implement IMapFrame, the dialog box will also check pages registered to ESRI Map Property Pages; if the element or elements implement IFrameElement, the dialog box will also check pages registered to ESRI Frame Element Property Pages. The dialog box will return, containing all the pages for which Applies returns True.

By implementing IElement, the Size And Position property page will apply to your element—this page will assume that any element it receives also supports ITransform2D.

All elements will have the Size and Position property page—this relies on the IElement and ITransform2D interfaces.

As the user may have selected a number of elements, all element property pages should be able to cope when passed an IEnumElement reference instead of an IElement reference. In this case, the properties that have been changed in the page are applied to all the elements in the enumeration.

Creating the InfoPropertyPage

At this point, your users are only able to alter the properties of the IInfoElement interface programmatically. You will now create a simple property page to allow users to change which items of text are shown on the element (ShowUser, and so on) and the Symbol used to draw the text.


Add a class called InfoPropertyPage and a Form called frmInfoTextPropertyPage to your project. Register the class to the ESRI Element Property Pages component category.

Add check boxes to allow users to set the ShowUser, ShowComputer, ShowDocPath, ShowAuthor, and ShowTemplates properties individually. Also add a button to change the Symbol, and a text box to display the current font information.

You can use the EnableAppOptions property of the InfoTextElement to selectively disable the Document path, Author name, and Templates options if the element does not currently reside in the ArcMap application.


The property page form should contain controls allowing users to set all the properties of IInfoElement.

For full details of the code behind the Form, see the accompanying example code.

Implementing property page interfaces for the InfoPropertyPage

InfoPropertyPage is a standard implementation of a property page. See 'Property Pages' in Chapter 2 for more information on implementing a property page.

In the Applies method, iterate through the Objects SafeArray parameter and return True if you find an object that implements IInfoElement.

[Visual Basic 6]
Dim pObj As Variant, i As Long
Objects.Reset
For i = 0 To Objects.Count - 1
     Set pObj = Objects.Next
     If Not pObj Is Nothing Then
          If TypeOf pObj Is GraphicElementVB.IInfoElement Then
               IComPropertyPage_Applies = True

If the parameter contains an IEnumElement reference, iterate each of the elements in the enumerations, and return True only if all of the elements in the enumeration implement IInfoElement.

[Visual Basic 6]
ElseIf (TypeOf pObj Is esriCarto.IEnumElement) Then
  Dim pElements As esriCarto.IEnumElement, pCurrEl As esriCarto.IElement
  Dim bApplies As Boolean
  Set pElements = pObj
  pElements.Reset
  bApplies = True
  Do
    Set pCurrEl = pElements.Next
    If Not (pCurrEl Is Nothing) Then
      bApplies = (bApplies And _
       (TypeOf pCurrEl Is GraphicElementVB.IInfoElement))
    End If
  Loop While Not pCurrEl Is Nothing
  IComPropertyPage_Applies = bApplies
  Exit Function
End If

In the Applies and SetObjects property page methods, an element property page may receive either a reference to a single element or a reference to an enumeration of elements.
An element property page should only apply (Applies = True) if it can be used to edit all the elements passed to it.

To manage references to a number of elements and their properties, add a class called InfoElementsCollection to your project. This will act as a custom collection class; for details of the class, see the code in the accompanying project.


In the property page Form class, declare a member variable m_pElementColl, and provide access to this via a property.

[Visual Basic 6]
Private m_pElementColl As InfoElementsCollection
...
Public Property Get InfoElements() As InfoElementsCollection
  Set InfoElements = m_pElementColl
End Property

Create the InfoElementsCollection class to help the property page manage multiple references to elements; an element property page may be displayed for more than one element.

In the SetObjects method, create a new InfoElementsCollection.

[Visual Basic 6]
Private Sub IComPropertyPage_SetObjects(ByVal Objects As esriSystem.ISet)
  ...
  Set m_frmPage.InfoElements = New InfoElementsCollection

Again, iterate the Objects to check for objects that implement IElement or IEnumElement. If you receive an IEnumElements reference, add each element from the enumeration to the ElementCollection of the Form; otherwise, just add the single IElement to the collection.

[Visual Basic 6]
If (TypeOf pObj Is IInfoElement) Then
  m_frmPage.InfoElements.Add pObj, Str(m_frmPage.InfoElements.Count)
ElseIf (TypeOf pObj Is esriCarto.IEnumElement) Then
  ..

In the Form, apply property changes to each of the members in this collection. If the property page is cancelled, the changes will be discarded by the property sheet.

SetObjects will also receive references to the Transformation, GraphicsContainer, and PageLayout (if applicable) of the element or elements. Your property page can use these objects, if required, to help edit the properties of an element.

You should add code to your Form to display the properties applicable to all the elements received—for example, if two elements were received, unequal properties can be indicated by graying-out the check box (not disabling it).

[Visual Basic 6]
Dim pInfoEl As GraphicElementVB.IInfoElement, valChecked(5) As Integer
For Each pInfoEl In m_pElementColl
  valChecked(0) = valChecked(0) + Int(pInfoEl.ShowUser)
  valChecked(1) = valChecked(1) + Int(pInfoEl.ShowComputer)
  ...

In the code, changes are made directly to the elements. This means that there is no need to work out which controls have been changed and, hence, which properties need to be updated. Also, the Apply property page method does not need to do anything.

[Visual Basic 6]
Private Sub SetShowUser(ByVal bShow As Boolean)
  Dim pInfoEl As GraphicElementVB.IInfoElement
  For Each pInfoEl In m_pElementColl
    pInfoEl.ShowUser = bShow
  Next pInfoEl
End Sub

Element property pages are not displayed as embedded pages; therefore, there is no need to implement IComEmbeddedPropertyPage. If you are working in VC++, you can return E_NOTIMPL from CreateCompatibleObject, although it is good practice to provide a complete implementation.

Compile and register the project again, and you will be able to set the properties of the InfoTextElement using this new user interface.


Back to top

Go to example code

See Also Creating other types of custom Element, Creating Custom Elements, and Creating Cartography.