In this section:
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
This will create a new InfoTextElement on the map. Right-click the element to see the property pagetry changing the settings to change the information displayed or the font used.
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 contentexplanatory 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.
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.
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.
Private Property LetIInfoElement_ShowAuthor(ByValRHSAs Boolean) m_bShowInfo(3) = RHSEnd 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 elementfor efficiency, the values will be calculated as little as possible.
The GetUserName and GetComputerName Windows API call can be used to find the current Windows user name and computer name.
The document path, author, and list of templates are found through the running application, which relies on the element being inside an ArcMap process.
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.
Private FunctionGetAutoText()As String DimsTempAs String Ifm_bShowInfo(0)ThensTemp = "User: " & m_sUser GetAutoText = GetAutoText & vbNewLine & sTempEnd 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.
Privatem_pApplicationAsesriFramework.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.
Friend Property GetEnableAppOptions()As BooleanEnableAppOptions =Not(m_pApplicationIs Nothing)End Property
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 inwhen 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 elementa Point is sufficient to locate an InfoTextElement, as the height and width of the element will be determined by the font.
Private Property LetIElement_Geometry(ByValpGeometryAsesriGeometry.IGeometry)If TypeOfpGeometryIsesriGeometryType.esriGeometryPointThenSetm_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.
Privatem_pSelectionTrackerAsesriDisplay.ISelectionTracker ...Private SubClass_Initialize()Setm_pSelectionTracker =NewPolygonTracker m_pSelectionTracker.Locked =Falsem_pSelectionTracker.ShowHandles =False...
You will return this tracker object from the SelectionTracker property.
Private Property GetIElement_SelectionTracker()AsesriDisplay.ISelectionTrackerSetIElement_SelectionTracker = m_pSelectionTrackerEnd 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.
Private SubRefreshTracker()Ifm_pCachedDisplayIs Nothing Then Exit Sub Setm_pSelectionTracker.Display = m_pCachedDisplayDimpOutlineAsesriGeometry.IGeometrySetpOutline =NewesriGeometry.Polygon IElement_QueryOutline m_pCachedDisplay, pOutline m_pSelectionTracker.Geometry = pOutlineEnd 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.
Private SubIElement_Activate(ByValDisplayAsesriDisplay.IDisplay)Setm_pCachedDisplay = Display RefreshTrackerEnd Sub
In Deactivate you can release the reference to the cached Display.
Private SubIElement_Deactivate()Setm_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 completesimply draw the text to the display.
Private SubIElement_Draw(ByValDisplayAsesriDisplay.IDisplay, _ByValTrackCancelAsesriSystem.ITrackCancel) Display.SetSymbolNothingDisplay.SetSymbol m_pTextSym Display.DrawText m_pPointGeometry, m_sAutoTextEnd Sub
The Display passed to Draw will be a reference to the same object that was passed to Activateunless 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.
Outline.SetEmpty m_pTextSym.Text = GetAutoTextDimpTransformAsesriDisplay.IDisplayTransformationSetpTransform = 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.
DimpSymAsesriDisplay.ISymbolSetpSym = 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 methodcall QueryOutline, and populate the Bounds parameter by using the IGeometry::QueryEnvelope method.
Private SubIElement_QueryBounds(ByValDisplayAsesriDisplay.IDisplay, _ByValBoundsAsesriGeometry.IEnvelope)DimpOutlineAsesriGeometry.IGeometrySetpOutline =NewesriGeometry.Polygon IElement_QueryOutline m_pCachedDisplay, pOutline pOutline.QueryEnvelope BoundsEnd 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.
Private FunctionIElement_HitTest(ByValXAs Double,ByValYAs Double, _ByValToleranceAs Double)As Boolean DimpPtAsesriGeometry.IPointSetpPt =NewesriGeometry.Point pPt.PutCoords X, YDimpOutlineAsesriGeometry.IRelationalOperatorSetpOutline =NewesriGeometry.Polygon IElement_QueryOutline m_pCachedDisplay, pOutline IElement_HitTest =NotpOutline.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 Workspaceif the workspace is currently being edited, then Locked should return false, and the Geometry of the element should be updatable.
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.
Privatem_pNativeSpatialRefAsesriGeometry.ISpatialReference ...Private Property SetIGraphicElement_SpatialReference(ByVal_ SpatialReferenceAsesriGeometry.ISpatialReference)' pSpatialReference may be nullSetm_pNativeSpatialRef = SpatialReference UpdateElementSpatialReferenceEnd 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.
Private SubUpdateElementSpatialReference()If Notm_pNativeSpatialRefIs Nothing Then If Notm_pPointGeometryIs Nothing Then Ifm_pPointGeometry.SpatialReferenceIs Nothing Then Setm_pPointGeometry.SpatialReference = _ m_pCachedDisplay.DisplayTransformation.SpatialReferenceEnd Ifm_pPointGeometry.Project m_pNativeSpatialRef RefreshTrackerEnd If End If End Sub
The SpatialReference of an element may not be set on the first call to this propertyin 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 usedyour code needs to account for this.
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 elementby 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 valueyou will use it later when implementing ITransform2D.
Private Property LetIElementProperties_AutoTransform(ByValAutoTransform _As Boolean) m_bAutoTrans = AutoTransformEnd Property
IElementProperties2 should always be implemented on a custom Elementnewer 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 scalethis value will be accounted for by the DisplayTransformation.
Privatem_dRefScaleAs Double
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.
Private Property GetIBoundsProperties_FixedSize()As BooleanIBoundsProperties_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.
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.
Private SubITransform2D_Move(ByValdxAs Double,ByValdyAs Double)DimpTransform2DAsesriGeometry.ITransform2DSetpTransform2D = m_pPointGeometry pTransform2D.Move dx, dy RefreshTrackerEnd 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.
Private SubITransform2D_Rotate(ByValOriginAsesriGeometry.IPoint, _ByValRotationAngleAs Double)DimpTransform2DAsesriGeometry.ITransform2DSetpTransform2D = m_pPointGeometry pTransform2D.Rotate Origin, RotationAngleIfm_bAutoTransThenm_pTextSym.Angle = NewRotateAngle(RotationAngle)End IfRefreshTrackerEnd Sub
If AutoTransform is True then the Symbol should also be rotatedset the Angle of the TextSymbol by adding the new rotation value to the existing Angle.
Private FunctionNewRotateAngle(ByValdAngleAs Double)As DoubleNewRotateAngle = m_pTextSym.Angle + RAD2DEG(dAngle)End Function Private FunctionRAD2DEG(ByValRadiansAs Double)As DoubleRAD2DEG = Radians * (180# / PI)End Function
ITransform2D methods should check the value of the AutoTranform propertyif 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.
Private SubITransform2D_Scale(ByValOriginAsesriGeometry.IPoint, _ByValsxAs Double,ByValsyAs Double)DimpTransform2DAsesriGeometry.ITransform2DSetpTransform2D = m_pPointGeometryWithpTransform2D .Scale Origin, sx, syEnd With Ifm_bAutoTransThen Ifsy <> 1Thenm_pTextSym.Size = m_pTextSym.Size * syElseIfsx <> 1Thenm_pTextSym.Size = m_pTextSym.Size * sxEnd If End IfRefreshTrackerEnd Sub
The Transform method needs to account for translation, scaling, and rotation.
The majority of elements determine not only their location but their size and shape by their Geometry. Text-based elements are differentthe 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.
Private WithEventsm_pDisplayTransAsDisplayTransformation
Now hook up this variable to the DisplayTransformation in Activate.
Private SubIElement_Activate(ByValDisplayAsesriDisplay.IDisplay)Setm_pCachedDisplay = DisplaySetm_pDisplayTrans = Display.DisplayTransformation RefreshTrackerEnd Sub
Now refresh the tracker in the BoundsUpdate event.
Private Subm_pDisplayTrans_BoundsUpdated(ByValsenderAs_ esriDisplay.IDisplayTransformation) RefreshTrackerEnd Sub
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.
Private SubIPersistVariant_Save(ByValStreamAsesriSystem.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.
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.
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 elementthere 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.
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.
Privatem_pAppAsesriFramework.IApplication ...Private SubICommand_OnCreate(ByValHookAs Object)Setm_pApp = HookEnd 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.
IfButton = 1ThenDimpPointAsesriGeometry.IPoint, pMxAppAsesriArcMapUI.IMxApplicationSetpMxApp = m_pAppSetpPoint = pMxApp.Display.DisplayTransformation.ToMapPoint(X, Y)DimpMxDocAsesriArcMapUI.IMxDocument, pMapAsesriCarto.IMapSetpMxDoc = m_pApp.DocumentSetpMap = pMxDoc.ActiveView.FocusMapSetpPoint.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.
DimpElementAsesriCarto.IElement, pInfoElAsIInfoElementSetpElement =NewGraphicElementVB.InfoTextElement pElement.Geometry = pPointSetpInfoEl = pElementDimpDefaultSymbolsAsesriArcMapUI.IDocumentDefaultSymbolsSetpDefaultSymbols = pMxDocSetpInfoEl.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.
DimpGContAsesriCarto.IGraphicsContainerDimpGContSelectAsesriCarto.IGraphicsContainerSelectSetpGCont = pMxDoc.ActiveView.GraphicsContainer pGCont.AddElement pElement, 0SetpGContSelect = 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.
DimpItemAsesriFramework.ICommandItem, uAs NewesriSystem.UID u = "{C22579D1-BC17-11D0-8667-0000F8751720}"SetpItem = m_pApp.Document.CommandBars.Find(u)Setm_pApp.CurrentTool = pItem
Provide standard implementations of all other members of ICommand and ITool. Register the command to the ESRI Mx Commands component category.
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.
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.
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 elementthis page will assume that any element it receives also supports ITransform2D.
All elements will have the Size and Position property pagethis 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.
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.
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.
DimpObjAs Variant, iAs LongObjects.ResetFori = 0ToObjects.Count - 1SetpObj = Objects.NextIf NotpObjIs Nothing Then If TypeOfpObjIsGraphicElementVB.IInfoElementThenIComPropertyPage_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.
ElseIf(TypeOfpObjIsesriCarto.IEnumElement)Then DimpElementsAsesriCarto.IEnumElement, pCurrElAsesriCarto.IElementDimbAppliesAs Boolean SetpElements = pObj pElements.Reset bApplies =True Do SetpCurrEl = pElements.NextIf Not(pCurrElIs Nothing)ThenbApplies = (bAppliesAnd_ (TypeOfpCurrElIsGraphicElementVB.IInfoElement))End If Loop While NotpCurrElIs NothingIComPropertyPage_Applies = bAppliesExit 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.
Privatem_pElementCollAsInfoElementsCollection ...Public Property GetInfoElements()AsInfoElementsCollectionSetInfoElements = m_pElementCollEnd 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.
Private SubIComPropertyPage_SetObjects(ByValObjectsAsesriSystem.ISet) ...Setm_frmPage.InfoElements =NewInfoElementsCollection
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.
If(TypeOfpObjIsIInfoElement)Thenm_frmPage.InfoElements.Add pObj, Str(m_frmPage.InfoElements.Count)ElseIf(TypeOfpObjIsesriCarto.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 receivedfor example, if two elements were received, unequal properties can be indicated by graying-out the check box (not disabling it).
DimpInfoElAsGraphicElementVB.IInfoElement, valChecked(5)As Integer For EachpInfoElInm_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.
Private SubSetShowUser(ByValbShowAs Boolean)DimpInfoElAsGraphicElementVB.IInfoElementFor EachpInfoElInm_pElementColl pInfoEl.ShowUser = bShowNextpInfoElEnd 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.
Go to example code
See Also Creating other types of custom Element, Creating Custom Elements, and Creating Cartography.