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:
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
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.
You can now set the properties of a VertexLineSymbol. Click OK to select the symbol and return to the Symbol Selector.
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.
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.
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.
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.
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.
Privatem_pSymLineAsISymbolPrivatem_pSymMarkerAsISymbol
You will also need references to the more specific ILineSymbol and IMarkerSymbol interfaces so you can change the properties of these symbols.
Privatem_pLineSymAsILineSymbolPrivatem_pMarkerSymAsIMarkerSymbol
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.
Private SubClass_Initialize() m_lhDC = 0Setm_pLineSym =NewSimpleLineSymbolSetm_pMarkerSym =NewSimpleMarkerSymbolSetm_pSymLine = m_pLineSymSetm_pSymMarker = m_pMarkerSymEnd Sub
Call this function from your class initialization code.
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.
Private SubISymbol_SetupDC(ByValhdcAsesriSystem.OLE_HANDLE, _ByValTransformationAsesriGeometry.ITransformation)Setm_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.
Private SubISymbol_Draw(ByValGeometryAsesriGeometry.IGeometry)IfGeometryIs Nothing Then Exit Subm_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.
If TypeOfGeometryIsIPointCollectionThen DimptCollAsIPointCollectionSetptColl = 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.
DimiAs Integer Fori = 0To(ptColl.PointCount - 1) m_pSymMarker.Draw ptColl.Point(i)Nexti m_pSymMarker.ResetDCEnd 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.
Setm_pTrans =Nothingm_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 nullfor 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.
Private Property LetISymbol_ROP2(ByValRHSAsesriDisplay.esriRasterOpCode) m_pSymLine.ROP2 = RHS m_pSymMarker.ROP2 = RHSEnd 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.
DimpSegs_FromAsISegmentCollection, pSegs_ToAsISegmentCollectionSetpSegs_From = GeometrySetpSegs_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.
pSegs_To.AddSegmentCollection pSegs_From Boundary.CloseDimpTopoBoundaryAsITopologicalOperatorSetpTopoBoundary = 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.
Privatem_pQBLineAsISimpleLineSymbol
Instantiate the new SimpleLineSymbol in your class initialization code.
Setm_pQBLine =NewSimpleLineSymbol
In the QueryBoundary method, first ensure the Boundary passed in is valid, and clear any preexisting shape by calling SetEmpty.
If NotBoundaryIs Nothing ThenBoundary.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.
Ifm_pLineSym.Width > m_pMarkerSym.SizeThenm_pQBLine.Width = m_pLineSym.WidthElsem_pQBLine.Width = m_pMarkerSym.SizeEnd If DimpQBSymAsISymbol pQBSym.QueryBoundary hdc, displayTransform, Geometry, Boundary
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.
Private Property LetIVertexLineSymbol_VertexSymbol(ByValRHS _AsIMarkerSymbol)If NotRHSIs Nothing Then Setm_pMarkerSym = CloneMe(RHS)Setm_pSymMarker = m_pMarkerSymEnd If End Property
IVertexLineSymbol is a custom interface defined to allow clients to change the appearance of a VertexLineSymbol.
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.
Private Property LetILineSymbol_Color(ByValRHSAsesriDisplay.IColor)If NotRHSIs Nothing Thenm_pLineSym.Color = RHSEnd If End Property Private Property LetILineSymbol_Width(ByValRHSAs Double)IfRHS > 0Thenm_pLineSym.Width = RHSEnd If End Property
Allow the ILineSymbol interface to alter the color and width of the contained LineSymbol.
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.
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.
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.
Private SubIPersistVariant_Load(ByValStreamAsIVariantStream)DimlSavedVersAs LonglSavedVers = Stream.ReadIf(lSavedVers > m_lCurrPersistVers)Or(lSavedVers <= 0)ThenErr.Raise E_FAILExit Sub ElseInitializeMembersIflSavedVers >= 1Then Setm_pLineSym = Stream.ReadSetm_pMarkerSym = Stream.ReadSetm_lMapLevel = Stream.ReadSetm_pSymLine = m_pLineSymSetm_pSymMarker = m_pMarkerSymEnd If End If End Sub
Next, you can create a property page to accompany your custom line symbol.
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.
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.
Fori = 0ToObjects.Count - 1SetpObj = Objects.NextIf TypeOfpObjIsVertexLineSymbolThen SetpAppliesClone = pObj IComPropertyPage_Applies =True Exit Function End If Nexti
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.
DimpObjAs Variant, iAs LongObjects.ResetFori = 0ToObjects.Count - 1SetpObj = Objects.NextIf NotpObjIs Nothing Then If TypeOfpObjIsVertexLineSymbolThen Setm_pObjectVertexLine = pObj' hold on to the symbolEnd If End If NextiIf Notm_pObjectVertexLineIs Nothing Then Setm_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.
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.
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.
Go to the VB6 example code
See Also Customizing the Display, Creating custom symbols, and Logo Marker Symbol Example.