Extending ArcObjects  

Vertex Line Symbol Example

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



In this section:

  1. Case for a custom Line symbol
  2. Creating a subtype of LineSymbol
  3. Creating the VertexLineSymbol
  4. Symbol Property Pages

Vertex line symbol example

Object Model Diagram Click here.

Example Code Click here

Description This project provides a custom symbol to draw a line and its vertices. Simple custom functionality is provided to alter both the symbol used to draw the basic shape of the line, and the symbol used to draw its vertices. A property page is also provided to allow users to edit the properties of the symbol using the user interface.

Design Coclass VertexLineSymbol is a subtype of the LineSymbol abstract class, with an accompanying property page coclass.

License required ArcView or above

Libraries Framework, Display, DisplayUI, Geometry, and System

Languages Visual Basic (some restrictions)

Categories Line Symbols, ESRI Property Pages, and Line Property Pages

Interfaces ISymbol, ILineSymbol, IMapLevel, IDisplayName, IPropertySupport, IClone, IPersist, IComPropertyPage, IPropertyPageContext, and ISymbolPropertyPage

How to use

    1. If using VB, register VertexLineSymbolVB.dll and double-click the VertexLineSymbolVB.reg file to register to component categories.
    2. Open ArcMap and add a feature layer with line features or add a line graphic element. Open the Symbol Selector for the item.

      For a line feature layer, right-click the layer in the ArcMap table of contents, click Properties, and in the Layer Properties dialog box, click the Symbology tab. The Single Symbol renderer should be selected by default. Click the Symbol button to show the Symbol Selector.

      For a line element, right-click the element and click Properties. In the Properties dialog box, make sure the Symbol tab is selected and click the Change Symbol button.

    3. In the Symbol Selector dialog box, click the Properties button to display the Symbol Editor.
    4. In the Symbol Editor dialog box, pull down the Type list box and click Vertex Line Symbol.

      You can now set the properties of a VertexLineSymbol. Click OK to select the symbol and return to the Symbol Selector.


Case for a custom Line symbol

Sometimes when using line feature or graphics, it helps to be able to clearly see the vertex points of the line.

For example, when editing a feature layer in ArcMap, the vertices of a line are highlighted with separate symbols. This type of display helps particularly on lines with numerous vertices, and also those with curved segments, as it is more difficult to identify the location of the vertices on lines with parametric curved segments.

No existing line symbol can be used to display a line or polygon feature by highlighting its vertices. (Marker symbols can be added to a line by using LineDecorations, but such decorations display at certain measurements along the line and cannot be used to draw the vertices of a line.)

As no symbol coclass provides the ability to display a line as required, you will create a custom line symbol to meet the requirements.

This example demonstrates how to construct a custom symbol to draw lines and to highlight each vertex of the line.


Creating a subtype of LineSymbol

All types of custom symbols are based on the Symbol abstract class, the details of which can be found in the previous example, LogoMarkerSymbol. Refer to this example for general details of how to create a custom symbol.

Additional issues relevant to LineSymbols in particular are discussed throughout this section.

In the Display object model diagram, you can see that each coclass for drawing line features also inherits from the LineSymbol abstract class.

Therefore, to create a LineSymbol, you should implement ILineSymbol, IMapLevel, and IPropertySupport. All LineSymbols also implement IDisplayName, which provides a string description of each type of symbol and is used in the Symbol Properties Editor dialog box.

By looking at the other LineSymbols, you can see that many of them also implement ICartographicLineSymbol, which controls the line cap and join styles, and the miter limit with which a line is drawn. ICartographicLineSymbol is not an appropriate interface to implement in this example, as cartographic lines can be used anyway and you will reuse existing LineSymbols in your custom symbol.


IPropertySupport cannot be implemented in VB

Note that it is not possible to implement IPropertySupport in VB. However, this will not affect the main functionality ArcMap does not assume that this interface is implemented, but may check for its presence on any LineSymbol.

The discussion for this example centers on the VB example project, as the approach taken is the same, regardless of development environment. The implementation of IPropertySupport is discussed in the previous example, LogoMarkerSymbol. The same principles can be applied for the VertexLineSymbol.


Creating the VertexLineSymbol

To solve the requirements of this example, you will create a subtype of LineSymbol, called VertexLineSymbol, implementing ISymbol, ILineSymbol, IMapLevel, and IDisplayName, as well as the standard interfaces for cloning and persistence.

To create a flexible class, with maximum reuse of existing code, your VertexLineSymbol will draw a line by using any LineSymbol, then draw the vertices of the line using any MarkerSymbol. To add this custom functionality, you will also create and implement a custom interface, IVertexLineSymbol.


Techniques for drawing

In the previous example, LogoMarkerSymbol, a variety of techniques that can be used for the actual drawing were discussed, and the approach used was to call Windows GDI functions.

In this example, however, you will reuse ArcGIS LineSymbols and MarkerSymbols and can, therefore, make use of the ISymbol::Draw method to perform the drawing, which reduces the complexity of the code you need to write.

To draw the VertexLineSymbol, you will use the existing LineSymbols and MarkerSymbols.

In the VertexLineSymbol class, declare member variables to hold references to the ISymbol interface of the LineSymbol and MarkerSymbol which you will use to perform the drawing.

[Visual Basic 6]
Private m_pSymLine As ISymbol
Private m_pSymMarker As ISymbol

You will also need references to the more specific ILineSymbol and IMarkerSymbol interfaces so you can change the properties of these symbols.

[Visual Basic 6]
Private m_pLineSym As ILineSymbol
Private m_pMarkerSym As IMarkerSymbol

Create a function called InitializeMembers, and in this function, create the default symbols with which your VertexLineSymbol will draw. This is also a good opportunity to initialize any other member variables.

[Visual Basic 6]
Private Sub Class_Initialize()
  m_lhDC = 0
  Set m_pLineSym = New SimpleLineSymbol
  Set m_pMarkerSym = New SimpleMarkerSymbol
  Set m_pSymLine = m_pLineSym
  Set m_pSymMarker = m_pMarkerSym
End Sub

Call this function from your class initialization code.

Implementing ISymbol

In the SetupDC method, you only need to store as member variables the references to the parameters passed in (hDC and displayTransformation), which will be used later in the Draw method.

[Visual Basic 6]
Private Sub ISymbol_SetupDC(ByVal hdc As esriSystem.OLE_HANDLE, _
 ByVal Transformation As esriGeometry.ITransformation)
  Set m_pTrans = Transformation
  m_lhDC = hdc

As you are not using GDI functions or other drawing libraries, you do not need to set up any objects for drawing.

A general discussion of how to implement the ISymbol interface can be found in the previous example, LogoMarkerSymbol.

In the Draw method, check the passed in Geometry parameter contains a valid object before drawing the basic shape of the line. Call the SetupDC method of the ILineSymbol member variable to set the LineSymbol as the current symbol for the display, passing in the device context handle and transformation you received in the SetupDC method of your custom symbol. Then call Draw, passing in the Geometry parameter, and finally call ResetDC.

[Visual Basic 6]
Private Sub ISymbol_Draw(ByVal Geometry As esriGeometry.IGeometry)
  If Geometry Is Nothing Then Exit Sub
  m_pSymLine.SetupDC m_lhDC, m_pTrans
  m_pSymLine.Draw Geometry
  m_pSymLine.ResetDC

To draw the individual vertices of the line, QI for the IPointCollection interface of the Geometry parameter. Set the MarkerSymbol as the current symbol for the display in the same way as you did previously for the LineSymbol.

[Visual Basic 6]
If TypeOf Geometry Is IPointCollection Then
  Dim ptColl As IPointCollection
  Set ptColl = Geometry
  m_pSymMarker.SetupDC m_lhDC, m_pTrans

Then iterate through the IPointCollection, passing each individual Point to the ISymbol::Draw method of the MarkerSymbol. Finally, call ResetDC on the MarkerSymbol.

[Visual Basic 6]
Dim i As Integer
  For i = 0 To (ptColl.PointCount - 1)
    m_pSymMarker.Draw ptColl.Point(i)
  Next i
  m_pSymMarker.ResetDC
End If

To perform the drawing, simply draw the basic line shape, then iterate each vertex of the line, drawing each in turn.

In the ResetDC method, simply release the transformation, and set the device context handle back to zero.

[Visual Basic 6]
Set m_pTrans = Nothing
m_lhDC = 0

You must ensure your calls to the LineSymbol and MarkerSymbol SetupDC, Draw, and ResetDC methods are made inside the ISymbol::Draw method of your class, as these methods can only be called when the device is currently in drawing mode.

In some situations, the Transformation parameter received in SetupDC may be null—for example, when drawing to the table of contents. This will not affect your Draw method, as you simply pass the parameter on to another Symbol, which will account for the null transformation.

If the ROP2 property of your VertexLineSymbol is set, change the ROP2 properties of both the LineSymbol and MarkerSymbol members.

[Visual Basic 6]
Private Property Let ISymbol_ROP2(ByVal RHS As esriDisplay.esriRasterOpCode)
  m_pSymLine.ROP2 = RHS
  m_pSymMarker.ROP2 = RHS
End Property

In the previous example, the QueryBoundary method was relatively complex to calculate, as the shape of the symbol was stored in device coordinates, which required a conversion to Map coordinates to return the boundary. However, in the VertexLineSymbol example, you could calculate a boundary more simply, as you always work in Map coordinates.

One method would be to create the shape of the boundary by QIing to the IPointCollection interface of both the Geometry and Boundary parameters.

[Visual Basic 6]
Dim pSegs_From As ISegmentCollection, pSegs_To As ISegmentCollection
Set pSegs_From = Geometry
Set pSegs_To = Boundary

Then add the vertices from the Geometry to the Boundary. Don't forget that the result of QueryBoundary must be closed (the Geometry you received may not have been closed). Last, ensure the Boundary is simplified.

[Visual Basic 6]
pSegs_To.AddSegmentCollection pSegs_From
Boundary.Close
 
Dim pTopoBoundary As ITopologicalOperator
Set pTopoBoundary = Boundary
pTopoBoundary.Simplify

However, if you investigate the value of QueryBoundary for existing symbols, FillSymbols return a Boundary that follows the shape drawn, but LineSymbols actually return a rectangular Boundary polygon. This is actually the Envelope of the Geometry when drawn with the LineSymbol, accounting for the Width.

Here you can see the Boundary returned from various symbols; the symbols are purple, and the red hatched area shows the Boundary.

For a QueryBoundary implementation closer to the behavior of the existing symbols, you can make use of the QueryBoundary method on the existing SimpleLineSymbol class.

First, declare a member variables to store a SimpleLineSymbol, so you do not have to instantiate the symbol each time QueryBoundary is called.

[Visual Basic 6]
Private m_pQBLine As ISimpleLineSymbol

Instantiate the new SimpleLineSymbol in your class initialization code.

[Visual Basic 6]
Set m_pQBLine = New SimpleLineSymbol

In the QueryBoundary method, first ensure the Boundary passed in is valid, and clear any preexisting shape by calling SetEmpty.

[Visual Basic 6]
If Not Boundary Is Nothing Then
  Boundary.SetEmpty

To ensure the Boundary includes the vertex MarkerSymbols, calculate which is greater: the Width of the LineSymbol or the Size of the MarkerSymbol. Then set the Width of the SimpleLineSymbol, m_pQBLine, to this value, and call its QueryBoundary method, passing in the same parameters you received.

[Visual Basic 6]
If m_pLineSym.Width > m_pMarkerSym.Size Then
  m_pQBLine.Width = m_pLineSym.Width
Else
  m_pQBLine.Width = m_pMarkerSym.Size
End If
Dim pQBSym As ISymbol
pQBSym.QueryBoundary hdc, displayTransform, Geometry, Boundary

Creating and implementing the IVertexLineSymbol interface

You need to provide a way to change the LineSymbol and MarkerSymbol you use to draw the VertexLineSymbol. Create an interface called IVertexLineSymbol, with two read-write properties, LineSymbol and VertexSymbol. For more information on how you can create a new interface, see the topic, 'Coding Interfaces'.

Implement IVertexLineSymbol in the VertexLineSymbol coclass. When each property is set, clone the incoming Symbol, and store the reference as the appropriate member variable. Also, ensure the reference to the ISymbol interface, m_SymMarker or m_pSymLine, is also updated.

[Visual Basic 6]
Private Property Let IVertexLineSymbol_VertexSymbol(ByVal RHS _
 As IMarkerSymbol)
  If Not RHS Is Nothing Then
    Set m_pMarkerSym = CloneMe(RHS)
    Set m_pSymMarker = m_pMarkerSym
  End If
End Property

IVertexLineSymbol is a custom interface defined to allow clients to change the appearance of a VertexLineSymbol.

Implementing ILineSymbol

ILineSymbol provides basic LineSymbol properties, allowing the Width and Color of the LineSymbol to be altered. This interface is commonly used in ArcGIS, for example by the Element Properties dialog box.

Code both of these properties to call only the LineSymbol of the VertexLineSymbol, leaving the MarkerSymbol unaffected.

[Visual Basic 6]
Private Property Let ILineSymbol_Color(ByVal RHS As esriDisplay.IColor)
  If Not RHS Is Nothing Then
    m_pLineSym.Color = RHS
  End If
End Property
  
Private Property Let ILineSymbol_Width(ByVal RHS As Double)
  If RHS > 0 Then
    m_pLineSym.Width = RHS
  End If
End Property

Allow the ILineSymbol interface to alter the color and width of the contained LineSymbol.

Implementing IMapLevel, IDisplayName, and IPropertySupport

Implement the IMapLevel and IDisplayName interfaces. From IDisplayName::DisplayName, return 'Vertex Line Symbol'. If you are working in VC++, you can also implement the optional IPropertySupport interface if you require.

For more details of the implementation of IMapLevel, IDisplayName, and IPropertySupport, refer to the previous example, LogoMarkerSymbol; the implementations follow the same principles as shown previously.

Implementing cloning and persistence

As discussed in the previous example, cloning and persistence are essential for any Symbol. You should provide a standard implementation of IClone and either IPersistVariant or IPersist and IPersistStream. See the 'Developing Objects' section for more information on cloning and persistence.

The only member variables that you need to persist are the LineSymbol and MarkerSymbol with which the VertexLineSymbol is drawn, and the current value of IMapLevel::MapLevel. The current persistence version number should also be written to the stream, allowing for backward compatibility.

[Visual Basic 6]
Stream.Write m_lCurrPersistVers
 
Stream.Write m_pLineSym
Stream.Write m_pMarkerSym
Stream.Write m_lMapLevel

When you Load a VertexLineSymbol from a stream, first call the InitializeMembers function you created earlier to set default values for the VertexLineSymbol. Next, set the member variables m_pLineSym and m_pMarkerSym from the stream. Then set the member variables m_pSymLine and m_pSymMarker to refer to the newly loaded objects.

[Visual Basic 6]
Private Sub IPersistVariant_Load(ByVal Stream As IVariantStream)
  Dim lSavedVers As Long
  lSavedVers = Stream.Read
  If (lSavedVers > m_lCurrPersistVers) Or (lSavedVers <= 0) Then
    Err.Raise E_FAIL
    Exit Sub
  Else
  
    InitializeMembers
    If lSavedVers >= 1 Then
      Set m_pLineSym = Stream.Read
      Set m_pMarkerSym = Stream.Read
      Set m_lMapLevel = Stream.Read
      Set m_pSymLine = m_pLineSym
      Set m_pSymMarker = m_pMarkerSym
    End If
  End If
End Sub

Next, you can create a property page to accompany your custom line symbol.


Symbol Property Pages

Each symbol has a property page, displayed in the Symbol Editor dialog box, which allows the user to edit the properties of the symbol in the user interface. Symbol property pages are discussed in the previous example, LogoMarkerSymbol.

To complete the VertexLineSymbol example, you will create a VertexLinePropertyPage coclass, which implements the ISymbolPropertyPage interface, as well as the standard property page interfaces. You will register the property page coclass to the Symbol Property Pages component category.


You will create a separate Form class to provide the GUI component of the property page, and link this Form to the VertexLinePropertyPage coclass by a number of properties.

The VertexLinePropertyPage follows both the design used in the previous example and the general rules for property page implementation; see the topic 'Creating Property Pages' for more information.

The VertexLinePropertyPage allows a user to alter the properties of a VertexLineSymbol by using the ArcGIS UI.

Implementing property page interfaces for a VertexLinePropertyPage

In the VertexLinePropertyPage coclass, implement either IComPropertyPage and IPropertyPageContext if you are working in VB or IPropertyPage and IPropertyPageContext if working in VC++.

You will also implement ISymbolPropertyPage, regardless of the development environment.

In the Applies method of IPropertyPageContext or IComPropertyPage, return True if you receive a VertexLineSymbol.

[Visual Basic 6]
For i = 0 To Objects.Count - 1
  Set pObj = Objects.Next
  If TypeOf pObj Is VertexLineSymbol Then
    Set pAppliesClone = pObj
    IComPropertyPage_Applies = True
    Exit Function
  End If
Next i

Add a property named VertexLineSymbol to the Form class. In the SetObjects method of IPropertyPage or IComPropertyPage, check that you receive a VertexLineSymbol object, then set the VertexLineSymbol property of the Form to this object.

[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 VertexLineSymbol Then
      Set m_pObjectVertexLine = pObj      ' hold on to the symbol
    End If
  End If
Next i
If Not m_pObjectVertexLine Is Nothing Then
  Set m_frmPage.VertexLineSymbol = m_pObjectVertexLine
  m_frmPage.UpdateControls
  m_frmPage.IsPageDirty = False
End If

The SetObjects method of the property page should check that one of the objects received is a VertexLineSymbol.

Add a property called IsPageDirty to the form, and link this to the IComPropertyPage::IsPageDirty property of the VertexLinePropertyPage coclass.

This allows events from controls on the Form to directly change properties of the Symbol. The changes can then be seen in the Preview box, after calling IComPropertyPageSite::PageChanged to refresh the dialog box.

Implementing ISymbolPropertyPage

The details of ISymbolPropertyPage and its use are discussed previously in the LogoMarkerSymbol example.

For the VertexLinePropertyPage, you should implement ISymbolPropertyPage in a similar way. First, add a read-write property to the Form called Units. When this property is changed, call the UpdateControls method.

The UpdateControls method must account for the units of measurement currently selected in the user interface, when setting the Width of the VertexLineSymbol's LineSymbol or the Size of the VertexLineSymbol's MarkerSymbol.

[Visual Basic 6]
txtWidth.Text = PointsToUIValue(m_pLineSym.Width)
txtSize.Text = PointsToUIValue(m_pVertexLine.VertexSymbol.Size)

The PointsToUIValue function is the same as that shown previously for the LogoMarkerSymbol.

 


Back to top

Go to the VB6 example code

See Also Customizing the Display, Creating custom symbols, and Logo Marker Symbol Example.