arcgis 创建自定义符号

Introducing custom symbols

The combination and manipulation of existing symbols results in a range of display options.  CharacterMarkerSymbolsCartographicLineSymbols, and  PictureFillSymbols in particular are flexible, and when you combine effects in a multilayer marker, line, or fill symbol, a wide range of effects can be achieved. If your drawing requirements are not met by these symbols, you can implement a custom symbol.
 
Knowledge of the available options in ArcMap will help you decide on the appropriate symbol, but you should also review the  Display objects model; you may be able to manipulate the existing symbols programmatically in a way that you cannot achieve using the ArcMap user interface (UI).
 
A custom symbol is a relatively low-level solution; for example, it can exist without the presence of an MxDocument. A custom symbol should never rely on the attributes of a particular feature. If required, consider a custom renderer instead. Also, a symbol does not generally change the location of an item—projections or the transformation of your data may be more appropriate.
 
Since you have programmatic access to  ScreenDisplay, it is possible to draw items directly to the display without using a symbol, feature, or element. This type of solution may be appropriate to temporarily highlight the result of an operation, for example, in the way that a feature is flashed on the display when you select that feature in the Identify dialog box.
 
If your drawings need to be persisted with the document or after refreshing the view, or if user interaction with the shape is required as with selection and editing, direct drawing may not be suitable.
 

Using custom symbols

Once you have decided on a custom symbol, you need to consider your implementation detail—that you can achieve the drawing effects you require.
 
When planning and testing your symbol, don't forget drawing efficiency and platform function support. Make sure you are familiar with the application programming interface (API) you're using, as well as surrounding issues. You may find it frustrating waiting for drawing to complete on complex maps. Also consider platform support for Windows Graphic Device Interface (GDI) and GDI+ functions—your symbol may be drawn to a screen, exported to a file, or output to any type of printer.
 
Windows GDI and GDI+ are mature platforms for developers, and you can find information in the references in the bibliography and MSDN for further reading on this extensive topic.
 
Similarly, if you choose alternative methods of drawing, efficiency and platform support should be considered in addition to any issues specific to the method you're using.
 
A custom MarkerSymbol, the simplest type of symbol, is used in the following example. Many issues of designing and implementing a custom symbol are common to implementing a marker, line, fill, text, or chart symbol and are discussed in this example.
 
Logo marker symbol example
  • Example Code: Logo Marker symbol
  • Description: This example provides a custom symbol that draws a company logo to symbolize a point. Simple custom functionality is provided to alter the colors of the different parts of the symbol, and a property page is provided to allow end users to edit the properties of the symbol.
  • Design class: LogoMarkerSymbol is a subtype of the MarkerSymbol abstract class. LogoMarkerPropertyPage is an accompanying property page class.

Case for a custom MarkerSymbol

Imagine that the fictitious company logo shown here must be used to symbolize point features or graphic elements. You need to use it repeatedly, as part of a renderer or graphic, and at a wide variety of scales including large format output. You must also add the ability to alter the color of each section of the logo to indicate different divisions of the company.
To create a symbol such as this using the core ArcObjects symbol classes, you have a couple of options.
 
You could create a  PictureMarkerSymbol, since it can be used effectively to portray any design. However, changing the colors of the logo sections would require a different bitmap for each possible color combination. Also, PictureMarkerSymbols may appear pixilated when zoomed in; using a high resolution bitmap can solve this problem but can increase memory requirements and slow draw speeds.
Alternatively, you could construct  MultiLayerMarkerSymbol, with separate CharacterMarkerSymbols to represent the different parts of the logo. Because the symbol is drawn with vectors, there would be no resolution problems. However, you would need to create a specialist TrueType font with glyphs designed to represent the different sections of the logo. Since no core symbol coclass provides the functionality you require, you can create a custom marker symbol.

Creating a subtype of MarkerSymbol

If you decide to create a custom symbol, start by reviewing the Display objects diagram. All Symbol classes—markers, lines, fills, text, and charts—inherit from a common abstract class called Symbol.
 
 
Therefore, any type of custom symbol you create must implement the  ISymbol interface, along with interfaces for cloning and persistence. Any class that implements ISymbol can be drawn to a device; however, classes specialize in the type of objects they can draw.

 
 
Looking again at the Display objects model diagram, you can see that each class that draws point features also inherits from the MarkerSymbol abstract class. Therefore, to create MarkerSymbol, you should also implement  IMarkerSymbolISymbolRotation, IMapLevel, and  IPropertySupport.

Many of the existing MarkerSymbol classes also implement  IMarkerMask. This interface provides the ability to draw a standard mask around MarkerSymbol, which can be useful when placing multicolored symbols on a multicolored background, as it helps identify the boundaries of the symbol more clearly. This interface is, therefore, also an appropriate interface to implement in this case.

 
A marker mask can help to distinguish symbols from a similarly colored background.
 
MarkerSymbols also implement  IDisplayName, which provides a string description of each type of symbol and which is used in the Symbol Properties Editor dialog box.

Creating LogoMarkerSymbol

In this example, you will create a subtype of MarkerSymbol, called LogoMarkerSymbol, registered to the Marker Symbols component category.
 
 
You will implement ISymbol, IMarkerSymbol, ISymbolRotation, IMapLevel, IMarkerMask, IDisplayName, and IPropertySupport, as well as the standard interfaces for cloning and persistence. To add the custom functionality, you will also create and implement a custom interface, ILogoMarkerSymbol.
 

Drawing techniques

There are a number of ways to draw a symbol, such as with the  GeometryDraw class or the  ISymbol.Draw or IDisplay.Draw methods. In this case, the shape of the logo would be stored as existing geometries (polygons, polylines, envelopes, and so forth). You are limited to drawing with existing geometries and symbols, but this approach does allow you to utilize the full functionality of ArcObjects to transform and adapt the shape and appearance of your symbol as required. This design may suit the production of a scale-dependent symbol, for example, that renders differently according to the current display scale.
 
You can perform drawing operations using third party drawing libraries or the low-level libraries available as part of the Windows platform. You may want to investigate the OpenGL standard or the Windows-specific DirectX libraries. Both were originally designed for use by C++ programmers and may not be a straightforward programming task in non-C++ environments.
 
In this example, you will use the Windows GDI functions to draw the symbol. Using GDI calls can produce efficient draw routines as well as flexibility in the type of drawing you can do. However, you need to be familiar with using GDI calls. Also, you may need to perform extensive mathematical calculations to transform your symbol's coordinates according to size, angle, and so on. Since Windows GDI functions require instructions in device coordinates, you will store the shape of the logo in device coordinates.
 

Implementing ISymbol

The ISymbol interface is responsible for drawing a geometry to the appropriate device context, using the correct appearance, shape, size, and location.
When a refresh event is called, ArcMap determines which shapes need to be drawn and in what order. ArcMap then uses the ISymbol interface to request that the shape draw itself.
Before ISymbol is drawn, its  SetupDC method is called, which receives information about the drawing device. Then the Draw method is called, which receives the shape and location (the geometry) of the item to be drawn. Finally, the  ResetDC method is called.
 
A general overview of the actions that should be performed by a custom symbol during each of these members is given below. This can be used as a guide for any symbol drawn using GDI functions.
If you use GDI calls to draw your symbol, you should use the SetupDC and ResetDC members of ISymbol to handle the addition and release of GDI objects, device contexts, and handles.
The actions performed in each of the draw methods are summarized here. You will use the CreatePen and CreateSolidBrush GDI functions to define the appearance of a LogoMarkerSymbol and the Chord and Polygon functions to draw the sections of the symbol to the device context. You will also use the SelectObject and DeleteObject GDI functions to maintain the device context objects correctly.
 
Add these declarations to your project (located in the Utility static class). Also, declare a user-defined type called POINTAPI, since GDI functions require coordinates to be defined as POINTAPI structures.

[C#]
public struct POINTAPI
{
   public int x;
   public int y;
}

[VB.NET]
Public Structure POINTAPI
   Public x As Integer
   Public y As Integer
End Structure
Now define an array of POINTAPI structures as a member variable of the LogoMarkerSymbol class. This array will hold the control points, which are the significant points you will use to define the shape and location of the logo in device coordinates.

[C#]
private Utility.POINTAPI[] m_coords = new Utility.POINTAPI[7];

[VB.NET]
Private m_coords As Utility.POINTAPI() = New Utility.POINTAPI(6) {}
The control points used by the drawing methods are stored in the m_coords array. They define the locations used for the Chord and Polygon GDI calls.
 
 
Now you can begin coding the ISymbol methods.
 
SetupDC method
In SetupDC, you need to prepare the class members to draw to the specific device, which is passed in as parameters to this method (hDC and displayTransformation).
 
  1. Store the passed-in information.

[C#]
m_trans = Transformation as IDisplayTransformation;
m_lhDC = hDC;

[VB.NET]
m_trans = TryCast(Transformation, IDisplayTransformation)
m_lhDC = hDC
  1. Set up the device ratio. See the Null transformations and resolution in the Draw and QueryBoundary section later for more information.

[C#]
SetupDeviceRatio(m_lhDC, m_trans);

[VB.NET]
SetupDeviceRatio(m_lhDC, m_trans)
  1. Calculate the size of the symbol in device coordinates. You will use these later in Draw.

[C#]
m_dDeviceRadius = (m_dSize / 2) * m_dDeviceRatio;
m_dDeviceXOffset = m_dXOffset * m_dDeviceRatio;
m_dDeviceYOffset = m_dYOffset * m_dDeviceRatio;

[VB.NET]
m_dDeviceRadius = (m_dSize / 2) * m_dDeviceRatio
m_dDeviceXOffset = m_dXOffset * m_dDeviceRatio
m_dDeviceYOffset = m_dYOffset * m_dDeviceRatio
  1. Store the rotation. You may need to rotate the symbol based on the ISymbolRotation interface.

[C#]
if (m_bRotWithTrans)
   m_dMapRotation = m_trans.Rotation;
else
   m_dMapRotation = 0;

[VB.NET]
If m_bRotWithTrans Then
   m_dMapRotation = m_trans.Rotation
Else
   m_dMapRotation = 0
End If
  1. Create the pens and brushes that you will use to fill and outline the sections of the symbol, and set up the ROP2 code used for the drawing. Save the existing values for all the GDI objects you will change so you can replace them in ResetDC.

[C#]
// Set up the pen that is used to outline the shapes.
// Multiplying by m_dDeviceRatio allows the pen size to scale.
m_lPen = Utility.CreatePen(0, Convert.ToInt32(1 * m_dDeviceRatio), System.Convert.ToInt32(m_colorBorder.RGB));

// Set the appropriate raster operation code for this draw according to the ISymbol interface.
m_lROP2Old = (esriRasterOpCode)Utility.SetROP2(hDC, System.Convert.ToInt32(m_lROP2));

// Set up three solid brushes to fill in the shapes with the different color fills.
m_lBrushTop = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorTop.RGB));
m_lBrushLeft = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorLeft.RGB));
m_lBrushRight = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorRight.RGB));

// Select the new pen, and store the old pen. This is essential during cleanup.
m_lOldPen = Utility.SelectObject(hDC, m_lPen);

[VB.NET]
' Set up the pen that is used to outline the shapes.
' Multiplying by m_dDeviceRatio allows the pen size to scale.
m_lPen = Utility.CreatePen(0, Convert.ToInt32(1 * m_dDeviceRatio), System.Convert.ToInt32(m_colorBorder.RGB))

' Set the appropriate raster operation code for this draw according to the ISymbol interface.
m_lROP2Old = CType(Utility.SetROP2(hDC, System.Convert.ToInt32(m_lROP2)), esriRasterOpCode)

' Set up three solid brushes to fill in the shapes with the different color fills.
m_lBrushTop = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorTop.RGB))
m_lBrushLeft = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorLeft.RGB))
m_lBrushRight = Utility.CreateSolidBrush(System.Convert.ToInt32(m_colorRight.RGB))

' Select the new pen, and store the old pen. This is essential during cleanup.
m_lOldPen = Utility.SelectObject(hDC, m_lPen)
Draw method
In the Draw method, determine the location of each control point for the symbol, and draw the symbol based on these locations.
 
  1. Check that the passed-in Geometry parameter contains a valid object, then cast it to a point.

[C#]
if (Geometry == null)
   return;
if (!(Geometry is ESRI.ArcGIS.Geometry.IPoint))
   return;
ESRI.ArcGIS.Geometry.IPoint point = (IPoint)Geometry;

[VB.NET]
If Geometry Is Nothing Then
   Return
End If
If Not (TypeOf Geometry Is ESRI.ArcGIS.Geometry.IPoint) Then
   Return
End If
Dim point As ESRI.ArcGIS.Geometry.IPoint = TryCast(Geometry, IPoint)
  1. Transform the point to device coordinates using the device context and DisplayTransformation you saved in SetupDC. Call the CalcCoords function. This function will calculate the location of each control point used by the GDI functions.

[C#]
int lCenterX = 0;
int lCenterY = 0;
Utility.FromMapPoint(m_trans, ref point, ref lCenterX, ref lCenterY);
double tempy1 = System.Convert.ToDouble(lCenterY);
CalcCoords(System.Convert.ToDouble(lCenterX), ref tempy1);

[VB.NET]
Dim lCenterX As Integer = 0
Dim lCenterY As Integer = 0
Utility.FromMapPoint(m_trans, point, lCenterX, lCenterY)
Dim tempy1 As Double = System.Convert.ToDouble(lCenterY)
CalcCoords(System.Convert.ToDouble(lCenterX), tempy1)
  1. Draw the separate sections of the symbol to the device.

[C#]
m_lOldBrush = Utility.SelectObject(m_lhDC, m_lBrushTop);
lResult = Utility.Chord(m_lhDC, m_coords[5].x, m_coords[5].y, m_coords[6].x, m_coords[6].y, m_coords[4].x, m_coords[4].y, m_coords[1].x, m_coords[1].y);
…
Utility.SelectObject(m_lhDC, m_lOldBrush);

[VB.NET]
m_lOldBrush = Utility.SelectObject(m_lhDC, m_lBrushTop)
lResult = Utility.Chord(m_lhDC, m_coords(5).x, m_coords(5).y, m_coords(6).x, m_coords(6).y, m_coords(4).x, m_coords(4).y, m_coords(1).x, m_coords(1).y)
…
Utility.SelectObject(m_lhDC, m_lOldBrush)
ResetDC method
Complete the drawing functions by selecting the original GDI pen and ROP code and releasing other GDI resources in the ResetDC method.

[C#]
m_lROP2 = (esriRasterOpCode)Utility.SetROP2(m_lhDC, System.Convert.ToInt32(m_lROP2Old));
Utility.SelectObject(m_lhDC, m_lOldPen);
Utility.DeleteObject(m_lPen);
…
m_trans = null;
m_lhDC = 0;

[VB.NET]
m_lROP2 = CType(Utility.SetROP2(m_lhDC, System.Convert.ToInt32(m_lROP2Old)), esriRasterOpCode)
Utility.SelectObject(m_lhDC, m_lOldPen)
Utility.DeleteObject(m_lPen)
…
m_trans = Nothing
m_lhDC = 0
If you use the Windows GDI to draw to the display, make sure you reselect the original GDI objects after drawing
 
QueryBoundary method
In the  QueryBoundary method, you must populate the passed-in Boundary parameter, which is a polygon, with the shape of your symbol in map coordinates.
 
The nonsymmetrical nature of the logo means that it is simpler to calculate the exact shape of the symbol, rather than approximating a shape. You can create the shape of the logo by determining the radius of the circular section of the logo (dRad) and the length of the triangular sections of the symbol (dVal).

[C#]
ESRI.ArcGIS.Geometry.IPointCollection ptColl = null;
ESRI.ArcGIS.Geometry.ISegmentCollection segColl = null;
double dVal = 0; // dVal is the measurement of the short side of a triangle.
double dRad = 0;
ptColl = (IPointCollection)boundary;
segColl = (ISegmentCollection)boundary;
dRad = dMapSize / 2;
dVal = System.Math.Sqrt((dRad * dRad) / 2);
object missing = System.Reflection.Missing.Value;
ptColl.AddPoint(Utility.CreatePoint(point.X + dVal, point.Y - dVal), ref missing, ref missing);
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y - dVal), ref missing, ref missing);
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y + dVal), ref missing, ref missing);

IPoint p = ptColl.get_Point(0);
segColl.AddSegment((ISegment)Utility.CreateCircArc(point, ptColl.get_Point(2), ref p), ref missing, ref missing);

[VB.NET]
Dim ptColl As ESRI.ArcGIS.Geometry.IPointCollection = Nothing
Dim segColl As ESRI.ArcGIS.Geometry.ISegmentCollection = Nothing
Dim dVal As Double = 0 ' dVal is the measurement of the short side of a triangle.
Dim dRad As Double = 0
ptColl = CType(boundary, IPointCollection)
segColl = CType(boundary, ISegmentCollection)
dRad = dMapSize / 2
dVal = System.Math.Sqrt((dRad * dRad) / 2)
Dim missing As Object = System.Reflection.Missing.Value
ptColl.AddPoint(Utility.CreatePoint(point.X + dVal, point.Y - dVal), missing, missing)
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y - dVal), missing, missing)
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y + dVal), missing, missing)

Dim p As IPoint = ptColl.Point(0)
segColl.AddSegment(CType(Utility.CreateCircArc(point, ptColl.Point(2), p), ISegment), missing, missing)
QueryBoundary is a client-side storage function; therefore, you should add Point objects to the  ISegmentCollection interface of the passed-in Boundary object.
 
ROP2 property
The  ROP2 property indicates which type of pen (or raster operation) is used to draw a symbol. The ROP2 code of the device can easily be changed using the GDI functions SetROP2 and GetROP2, but remember to change the ROP2 code back to its original value in ResetDC because other symbols will be sharing the same device.
 
The  esriRasterOpCodes enumeration defines the possible ROP2 codes. Changing the ROP2 code can dramatically alter the appearance of the symbol.
 
 

Null transformations and resolution in Draw and QueryBoundary
(converting from map to device units)

Because the scalar properties Size, XOffset, and YOffset hold values in points, you must convert from points to device units (pixels) before drawing the symbol (for example, during SetupDC) using device coordinates.
 
You can calculate a device resolution, m_dDeviceRatio, in pixels per point using   DisplayTransformation passed to the SetupDC method.
SetupDeviceRatio calculates the number of pixels on the device that equal one printer's point—this is used to transform Size, XOffset, and YOffset from points to device units. The ReferenceScale of the Transformation, if present, is also accounted for here.

[C#]
private void SetupDeviceRatio(int hDC, ESRI.ArcGIS.Display.IDisplayTransformation displayTransform)
{
  if (displayTransform != null)
  {
     if (displayTransform.Resolution != 0)
     {
         m_dDeviceRatio = displayTransform.Resolution / 72;
        // Check the ReferenceScale of the display transformation. If not zero, 
        // adjust the size, XOffset and YOffset of the symbol you hold internally before drawing.
        if (displayTransform.ReferenceScale != 0)
           m_dDeviceRatio = m_dDeviceRatio * displayTransform.ReferenceScale / displayTransform.ScaleRatio;
     }
  }

[VB.NET]
Private Sub SetupDeviceRatio(ByVal hDC As Integer, ByVal displayTransform As IDisplayTransformation)
  If Not displayTransform Is Nothing Then
    If displayTransform.Resolution <> 0 Then
       m_dDeviceRatio = displayTransform.Resolution / 72
       ' Check the ReferenceScale of the display transformation. If not zero, 
       ' adjust the size, XOffset and YOffset of the symbol you hold internally before drawing.
       If displayTransform.ReferenceScale <> 0 Then
          m_dDeviceRatio = m_dDeviceRatio * displayTransform.ReferenceScale / displayTransform.ScaleRatio
       End If
    End If
In some situations, your symbol may be required to draw to a device context for which this parameter is null—for example, when drawing to the table of contents (TOC). In this case, you can get the resolution directly from the screen by using the GetDeviceCaps Windows API call.

[C#]
  else
  {
     // If you dont have a display transformation, calculate the resolution
     // from the actual device.
     if (hDC != 0)
     {
        // Get the resolution from the device context hDC.
        m_dDeviceRatio = System.Convert.ToDouble(Utility.GetDeviceCaps(hDC, Utility.LOGPIXELSX)) / 72;
     }
     else
     {
        // If invalid hDC, assume you're drawing to the screen.
        m_dDeviceRatio = 1 / (Utility.TwipsPerPixelX() / 20); // 1 Point = 20 Twips.
     }
  }
}

[VB.NET]
  Else
     ' If you dont have a display transformation, calculate the resolution
     ' from the actual device.
     If hDC <> 0 Then
        ' Get the resolution from the device context hDC.
        m_dDeviceRatio = System.Convert.ToDouble(Utility.GetDeviceCaps(hDC, Utility.LOGPIXELSX)) / 72
     Else
        ' If invalid hDC, assume you're drawing to the screen.
        m_dDeviceRatio = 1 / (Utility.TwipsPerPixelX() / 20) ' 1 Point = 20 Twips.
     End If
  End If
End Sub
Once the device ratio is calculated, Draw can use the FromMapPoint function (see accompanying sample code) to convert the geometry at which the symbol is drawn from map units to device units.
SetupDeviceRatio and FromMapPoint function together to transform map units to points.

Converting from points to map units

In the QueryBoundary method, you need to convert size, XOffset, and YOffset from points to map units to construct a geometry in map units representing the boundary of your symbol. Add a function called PointsToMap to complete this conversion; if no DisplayTransformation is present, use the value from SetupDeviceRatio.

[C#]
private double PointsToMap(ESRI.ArcGIS.Geometry.ITransformation displayTransform, double dPointSize)
{
   double tempPointsToMap = 0;
   IDisplayTransformation tempTransform = null;
   if (displayTransform == null)
      tempPointsToMap = dPointSize * m_dDeviceRatio;
   else
   {
      tempTransform = (IDisplayTransformation)displayTransform;
      tempPointsToMap = tempTransform.FromPoints(dPointSize);
   }
   return tempPointsToMap;
}

[VB.NET]
Private Function PointsToMap(ByVal displayTransform As ESRI.ArcGIS.Geometry.ITransformation, ByVal dPointSize As Double) As Double
   Dim tempPointsToMap As Double = 0
   Dim tempTransform As ESRI.ArcGIS.Display.IDisplayTransformation = Nothing
   If displayTransform Is Nothing Then
      tempPointsToMap = dPointSize * m_dDeviceRatio
   Else
      tempTransform = CType(displayTransform, IDisplayTransformation)
      tempPointsToMap = tempTransform.FromPoints(dPointSize)
   End If
   Return tempPointsToMap
End Function

Drawing efficiently

Code the ISymbol methods efficiently, as they may be called frequently. There are a number of issues to consider to increase your symbol's drawing efficiency.
 
  • Calculating and storing the shape of the symbol
    LogoMarkerSymbol calculates the shape and size of the symbol in two different coordinate spaces: device units for ISymbol.Draw and map coordinates for ISymbol.QueryBoundary and  IMarkerMask.QueryMarkerMask. Consider the amount of processing each set of calculations will require and which will limit the speed of these functions. Storing and calculating the shape of the symbol in both map and device coordinates may enable you to create a more efficient symbol; however, using a single method can make your code simpler and more maintainable. Consider also the routines you use to manipulate the shape of your symbol; these may be called frequently. Therefore, providing a direct mathematical approach may be quicker than the query interfaces (QIs) and object creation you may need to use to convert using the geometrical transformations inside ArcObjects.
  • Caching the shape of the symbol
    If more than one item is drawn with the same symbol, the drawing sequence starts with a call to SetupDC. Then Draw is called once for each item, and finally, ResetDC is called. The diagram below shows the sequence of calls for a SimpleRenderer and a ClassBreaksRenderer.
    It may be most efficient to determine the size and shape of your symbol once in the SetupDC method, then use this repeatedly in the Draw method by changing its location, depending on how you draw your symbol.
  • Efficient object creation
    Consider how your code will scale when it is used for hundreds of features or elements. For example, QueryBoundary is called frequently by ArcMap when drawing FeatureLayer and when drawing elements. QueryBoundary is also called when displaying the TOC, saving the document, and displaying property pages that show the symbol. Ensure that your QueryBoundary routine is efficient enough not to impede these processes, which may interrupt your workflow. You may see a decrease in your draw times if you instantiate all the objects you need when the symbol is instantiated, then reset the values each time. For example, the QueryBoundsFromGeom function creates new Point objects to build the boundary of the symbol.

[C#]
ptColl.AddPoint(Utility.CreatePoint(point.X + dVal, point.Y - dVal), ref missing, ref missing);
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y - dVal), ref missing, ref missing);
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y + dVal), ref missing, ref missing);

IPoint p = ptColl.get_Point(0);
segColl.AddSegment((ISegment)Utility.CreateCircArc(point, ptColl.get_Point(2), ref p), ref missing, ref missing);

[VB.NET]
ptColl.AddPoint(Utility.CreatePoint(point.X + dVal, point.Y - dVal), missing, missing)
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y - dVal), missing, missing)
ptColl.AddPoint(Utility.CreatePoint(point.X - dVal, point.Y + dVal), missing, missing)

Dim p As IPoint = ptColl.Point(0)
segColl.AddSegment(CType(Utility.CreateCircArc(point, ptColl.Point(2), p), ISegment), missing, missing)
You could declare Point objects as member variables m_pt1, m_pt2, and m_pt3, instantiate them when the class is initialized, and reuse them in the QueryBoundsFromGeom function.
The following code can execute approximately 50 percent faster when you repeatedly call QueryGeometry.

[C#]
m_pt1.PutCoords(point.X + dVal, pPoint.Y - dVa);
m_pt2.PutCoords(point.X + dVal, pPoint.Y - dVal);
m_pt3.PutCoords(point.X + dVal, pPoint.Y - dVal);

ptColl.AddPoint(m_pt1, ref missing, ref missing);
ptColl.AddPoint(m_pt2, ref missing, ref missing);
ptColl.AddPoint(m_pt3, ref missing, ref missing);

IPoint p = ptColl.get_Point(0);
segColl.AddSegment((ISegment)Utility.CreateCircArc(point, ptColl.get_Point(2), ref p), ref missing, ref missing);

[VB.NET]
m_pt1.PutCoords(point.X + dVal, pPoint.Y - dVa)
m_pt2.PutCoords(point.X + dVal, pPoint.Y - dVal)
m_pt3.PutCoords(point.X + dVal, pPoint.Y - dVal)

ptColl.AddPoint(m_pt1, missing, missing)
ptColl.AddPoint(m_pt2, missing, missing)
ptColl.AddPoint(m_pt3, missing, missing)

Dim p As IPoint = ptColl.Point(0)
segColl.AddSegment(CType(Utility.CreateCircArc(point, ptColl.Point(2), p), ISegment), missing, missing)

Creating and implementing ILogoMarkerSymbol

You need to provide a way to change the colors of the separate sections of the logo design.
Create an interface called ILogoMarkerSymbol with four read-write properties: ColorLeft, ColorRight, ColorTop, and ColorBorder.

[C#]
public interface ILogoMarkerSymbol
{
   ESRI.ArcGIS.Display.IColor ColorTop {get; set;}
   ESRI.ArcGIS.Display.IColor ColorLeft {get; set;}
   ESRI.ArcGIS.Display.IColor ColorRight {get; set;}
   ESRI.ArcGIS.Display.IColor ColorBorder {get; set;}
}

[VB.NET]
Public Interface ILogoMarkerSymbol
   Property ColorTop() As ESRI.ArcGIS.Display.IColor
   Property ColorLeft() As ESRI.ArcGIS.Display.IColor
   Property ColorRight() As ESRI.ArcGIS.Display.IColor
   Property ColorBorder() As ESRI.ArcGIS.Display.IColor
End Interface
Implement ILogoMarkerSymbol in the LogoMarkerSymbol coclass. In each property, clone the incoming IColor parameters and set the appropriate member variable.

[C#]
ESRI.ArcGIS.Display.IColor ILogoMarkerSymbol.ColorBorder
{
 get
 {
    // Return ColorBorder by Value.
    IClone clone = m_colorBorder as IClone;
    return clone.Clone() as IColor;
 }
 set
 {
    // Set ColorBorder by Value.
    IClone clone = value as IClone;
    m_colorBorder = clone.Clone() as IColor;
 }
}

[VB.NET]
Private Property ColorBorder() As ESRI.ArcGIS.Display.IColor Implements ILogoMarkerSymbol.ColorBorder
Get
  ' Return ColorBorder by Value.
   Dim clonee As IClone = TryCast(m_colorBorder, IClone)
   Return TryCast(clonee.Clone(), IColor)
End Get
Set(ByVal value As ESRI.ArcGIS.Display.IColor)
  ' Set ColorBorder by Value.
  Dim clonee As IClone = TryCast(value, IClone)
  m_colorBorder = TryCast(clonee.Clone(), IColor)
End Set
End Property

Implementing IMarkerSymbol

Implementing IMarkerSymbol allows ArcGIS to recognize that your class can be used to symbolize points. This interface is commonly used by ArcGIS applications, for example, when setting color and size using the Element Properties dialog box.
 
By implementing IMarkerSymbol, you ensure that a symbol can interact with the ArcMap UI, for example, the Element Properties dialog box
 
Code the Color property to refer to the predominant color at the top of the logo by calling the ILogoMarkerSymbol.ColorTop property.

[C#]
ESRI.ArcGIS.Display.IColor ESRI.ArcGIS.Display.IMarkerSymbol.Color
{
  get
  {
     // Return Color property by Value.
     IClone clone = (IClone)m_colorTop;
     return clone.Clone() as IColor;
  }
  set
  {
     // Set Color property by Value.
     IClone clone = value as IClone;
     m_colorTop = clone.Clone() as IColor;
  }
}

[VB.NET]
Private Property Color() As ESRI.ArcGIS.Display.IColor Implements ESRI.ArcGIS.Display.IMarkerSymbol.Color
  Get
     ' Return Color property by Value.
     Dim clonee As IClone = CType(m_colorTop, IClone)
     Return TryCast(clonee.Clone(), IColor)
  End Get
  Set(ByVal value As ESRI.ArcGIS.Display.IColor)
     ' Set Color property by Value.
     Dim clonee As IClone = TryCast(value, IClone)
     m_colorTop = TryCast(clonee.Clone(), IColor)
 End Set
End Property
In the Angle property, you can add a check for angles greater than 360 degrees.

[C#]
double ESRI.ArcGIS.Display.IMarkerSymbol.Angle
{
get
{
   //The angle is set as degrees and is also used internally as degrees in this class.
   return m_dAngle;
}
set
{
   // In this symbol, you can correct for an angle > 360 degrees.
   if (value > 360)
   value = value - (Convert.ToInt32(value / 360) * 360);
   //The angle is set as degrees and is also used internally as degrees in this class.
   m_dAngle = value;
}
}

[VB.NET]
Private Property Angle() As Double Implements ESRI.ArcGIS.Display.IMarkerSymbol.Angle
Get
  'The angle is set as degrees and is also used internally as degrees in this class.
  Return m_dAngle
End Get
Set(ByVal value As Double)
  ' In this symbol, you can correct for an angle > 360 degrees.
  If value > 360 Then
     value = value - (Convert.ToInt32(value / 360) * 360)
  End If
  'The angle is set as degrees, and is also used internally as degrees in this class.
  m_dAngle = value
End Set
End Property

Implementing ISymbolRotation

If you want your symbol to adjust itself to a rotated map display, implement ISymbolRotation. Although it is not essential to implement this interface, it requires little extra coding, as you should have already added symbol rotation code to allow for the IMarkerSymbol.Angle property.
 
When you rotate the symbol for drawing, simply subtract the map rotation angle from IMarkerSymbol.Angle. You can get the map rotation value from DisplayTransformation passed in SetupDC:

[C#]
dAngle = 360.0 - (m_dAngle + m_dMapRotation);

[VB.NET]
dAngle = 360.0 - (m_dAngle + m_dMapRotation)
ISymbolRotation allows a symbol to work with the Data Frame tools in ArcMap.
 
 

Implementing IMapLevel

IMapLevel is commonly used by the ArcMap Advanced Drawing Options to draw joined and merged symbols, most commonly those used to draw cased roads. It is simple to implement, as you only need to store an integer value in the read-write  MapLevelproperty.

[C#]
int ESRI.ArcGIS.Display.IMapLevel.MapLevel
{
 get
 {
    return m_lMapLevel;
 }
 set
 {
    m_lMapLevel = value;
 }
}

[VB.NET]
Private Property MapLevel() As Integer Implements ESRI.ArcGIS.Display.IMapLevel.MapLevel
   Get
      Return m_lMapLevel
   End Get
   Set(ByVal value As Integer)
      m_lMapLevel = Value
   End Set
End Property
This value will be used when your symbol is used in MultiLayerMarkerSymbol, when the Advanced Drawing Options indicate symbols must be drawn joined and merged.
 

Implementing IMarkerMask

IMarkerMask is used to draw a mask around a symbol. The QueryMarkerMask method should populate the Boundary parameter with the shape of the symbol if drawn at the specified geometry. The shape needs to be in map units, as it will be passed to the ISymbol.Draw method of  IFillSymbol by ArcMap.
 
By implementing IMarkerMask, you allow the framework to draw a mask area around your symbol.
 
Ensure the boundary is empty, then use the same technique you used in ISymbol.QueryBoundary to populate Boundary.

[C#]
Boundary.SetEmpty();
IPoint point = Geometry as IPoint;
IDisplayTransformation displayTrans = (IDisplayTransformation)Transform;
QueryBoundsFromGeom(hDC, ref displayTrans, ref Boundary, ref point);

// Unlike ISymbol.QueryBoundary, QueryMarkerMask requires a simple geometry.
ITopologicalOperator topo = Boundary as ITopologicalOperator;
if (!topo.IsKnownSimple)
{
  if (!topo.IsSimple)
     topo.Simplify();
}

[VB.NET]
Boundary.SetEmpty()
Dim point As IPoint = TryCast(Geometry, IPoint)
Dim displayTrans As IDisplayTransformation = CType(Transform, IDisplayTransformation)
QueryBoundsFromGeom(hDC, displayTrans, Boundary, point)

' Unlike ISymbol.QueryBoundary, QueryMarkerMask requires a simple geometry.
Dim topo As ITopologicalOperator = TryCast(Boundary, ITopologicalOperator)
If (Not topo.IsKnownSimple) Then
  If (Not topo.IsSimple) Then
     topo.Simplify()
  End If
End If

Implementing IPropertySupport

IPropertySupport is used to apply an object to one or more of the symbol's properties. It is a generic interface, which can be used by a client without the client knowing the exact nature of the underlying class.

[C#]
public bool Applies(object pUnk)
{
   IColor color = pUnk as IColor;
   ILogoMarkerSymbol logoMarkerSymbol = pUnk as ILogoMarkerSymbol;
   if (null != color || null != logoMarkerSymbol)
      return true;
   return false;
}

[VB.NET]
Public Function Applies(ByVal pUnk As Object) As Boolean Implements ESRI.ArcGIS.esriSystem.IPropertySupport.Applies
   Dim c As IColor = TryCast(pUnk, IColor)
   Dim logoMarkerSym As ILogoMarkerSymbol = TryCast(pUnk, ILogoMarkerSymbol)
   If Not Nothing Is c OrElse Not Nothing Is logoMarkerSym Then
      Return True
   End If
   Return False
End Function
In the  CanApply method, check if the object can be applied at the particular moment the method is called; a more complex class can involve checking the internal state of the class. In the case of LogoMarkerSymbol, the result does not depend on any state, so you can delegate the call to  Applies.
 
In the  Current property, check the incoming object reference—if it can be applied to any of the properties of the class, set the pUnk pointer to the current value of that property.

[C#]
object get_Current(object pUnk)
{
  IColor color = pUnk as IColor;
  if (null != color)
  {
     IColor currentColor = ((IMarkerSymbol)this).Color;
     return (object)currentColor;
  }

  ILogoMarkerSymbol logoMarkerSymbol = pUnk as ILogoMarkerSymbol;
  {
     IClone clone = ((IClone)this).Clone();
     return (object)clone;
  }
}

[VB.NET]
Private ReadOnly Property Current(ByVal pUnk As Object) As Object Implements ESRI.ArcGIS.esriSystem.IPropertySupport.Current
  Get
     Dim c As IColor = TryCast(pUnk, IColor)
     If Not Nothing Is c Then
        Dim currentColor As IColor = (CType(Me, IMarkerSymbol)).Color
        Return CObj(currentColor)
     End If
     Dim logoMarkerSym As ILogoMarkerSymbol = TryCast(pUnk, ILogoMarkerSymbol)
     Dim clonee As IClone = (CType(Me, IClone)).Clone()
     Return CObj(clonee)
  End Get
End Property
In the  Apply  method, set the incoming object as the appropriate member of your symbol class. In the code below, the incoming object can be an instance of the LogoMarkerSymbol class itself, in which case the values of the incoming object are assigned to the class member by using the  IClone.Assign  method.
[C#]
object Apply(object newObject)
{
  object oldObject = null;
  IColor color = newObject as IColor;
  if (null != color)
  {
     oldObject = ((IPropertySupport)this).get_Current(newObject);
     ((IMarkerSymbol)this).Color = color;
  }
  
   ILogoMarkerSymbol logoMarkerSymbol = newObject as ILogoMarkerSymbol;
  {
     oldObject = ((IPropertySupport)this).get_Current(newObject);
     IClone clone = (IClone)newObject;
     ((IClone)this).Assign(clone);
  }
  return oldObject;
}

[VB.NET]
Private Function Apply(ByVal newObject As Object) As Object Implements ESRI.ArcGIS.esriSystem.IPropertySupport.Apply
   Dim oldObject As Object = Nothing
   Dim c As IColor = TryCast(newObject, IColor)
   If Not Nothing Is c Then
      oldObject = (CType(Me, IPropertySupport)).Current(newObject)
      CType(Me, IMarkerSymbol).Color = c
   End If
   Dim logoMarkerSym As ILogoMarkerSymbol = TryCast(newObject, ILogoMarkerSymbol)
   oldObject = (CType(Me, IPropertySupport)).Current(newObject)
   Dim clonee As IClone = CType(newObject, IClone)
   CType(Me, IClone).Assign(clonee)
   Return oldObject
End Function
To be consistent with core symbols, you should at least apply an  IColor object to the IMarkerSymbol.Color property, although you can extend this to allow the setting of any of your properties.
 

Initializing members

Add a private routine to the LogoMarkerSymbol class to initialize the member variables. Call this function from the class initialize.

[C#]
private void InitializeMembers()
{
   // Set up default values as far as possible.
   m_lhDC = 0;
   …

   IColor color = null;
   IClone clone = null;
   color = ESRI.ArcGIS.ADF.Converter.ToRGBColor(Color.Red);
   m_colorTop = clone.Clone() as IColor;
   …

   // ISymbol property defaults.
   m_lROP2 = esriRasterOpCode.esriROPCopyPen;
   …
   m_bRotWithTrans = true;
}

[VB.NET]
Private Sub InitializeMembers()
  ' Set up default values as far as possible.
  m_lhDC = 0
  …
  Dim c As IColor = Nothing
  Dim clonee As IClone = Nothing

  c = ESRI.ArcGIS.ADF.Converter.ToRGBColor(Drawing.Color.Red)
  clonee = CType(c, IClone)
  m_colorTop = TryCast(clonee.Clone(), IColor)
  …
  m_lROP2 = esriRasterOpCode.esriROPCopyPen
  …
  m_bRotWithTrans = True
End Sub
Placing the initialization code in a separate function allows you to reset LogoMarkerSymbol to default values at any point, which is particularly useful when implementing persistence.
 

Implementing cloning and persistence

Cloning and persistence are essential functions for any symbol. Every time a reference to a symbol is passed to a property page, the symbol object is cloned. This allows any changes made to the symbol to be discarded and also allows the change to be added to the Undo/Redo stack in ArcMap. Every time a map document is saved, all the symbols applied to features and graphic elements are persisted. Add a standard implementation of persistence and cloning for the LogoMarkerSymbol example.
 
In the  IPersistVariant.Save method, save the persistence version number first, then each required member of the class.

[C#]
void ESRI.ArcGIS.esriSystem.IPersistVariant.Save(ESRI.ArcGIS.esriSystem.IVariantStream Stream)
{
   // Write the persistence version number first.
   Stream.Write(m_lCurrPersistVers);

   // Persist ISymbol properties.
   Stream.Write(m_lROP2);

   // Persist IMarkerSymbol properties.
   Stream.Write(m_dSize);
   Stream.Write(m_dXOffset);
   …
}

[VB.NET]
Private Sub Save(ByVal Stream As ESRI.ArcGIS.esriSystem.IVariantStream) Implements ESRI.ArcGIS.esriSystem.IPersistVariant.Save
   ' Write the persistence version number first.
   Stream.Write(m_lCurrPersistVers)

   'Persist ISymbol properties.
   Stream.Write(m_lROP2)
 
    ' Persist IMarkerSymbol properties.
   Stream.Write(m_dSize)
   Stream.Write(m_dXOffset)
   …
End Sub
In  IPersistVariant.Load, check the persistence version number first. Call the InitializeMembers function to set default values into the symbol, before reading values from the Stream, in the same order they were saved to set the member variables.

[C#]
void ESRI.ArcGIS.esriSystem.IPersistVariant.Load(ESRI.ArcGIS.esriSystem.IVariantStream Stream)
{
   // Read the persisted version number first.
   int lSavedVers = 0;
   lSavedVers = Convert.ToInt32(Stream.Read());
   if ((lSavedVers > m_lCurrPersistVers) | (lSavedVers <= 0))
   {
      throw new Exception("Failed to read from stream");
   }

   // Set members to default values.
   InitializeMembers();

   // Load the first persistance pattern.
   if (lSavedVers == 1)
   {
      m_lROP2 = (esriRasterOpCode)Stream.Read();
      m_dSize = Convert.ToDouble(Stream.Read());
      m_dXOffset = Convert.ToDouble(Stream.Read());
     …
   }
}

[VB.NET]
Private Sub Load(ByVal Stream As ESRI.ArcGIS.esriSystem.IVariantStream) Implements ESRI.ArcGIS.esriSystem.IPersistVariant.Load
   ' Read the persisted version number first.
   Dim lSavedVers As Integer = 0
   lSavedVers = Convert.ToInt32(Stream.Read())
   If (lSavedVers > m_lCurrPersistVers) Or (lSavedVers <= 0) Then
      Throw New Exception("Failed to read from stream")
   End If

   ' Set members to default values.
   InitializeMembers()

   ' Load the first persistance pattern.
   If lSavedVers = 1 Then
      m_lROP2 = CType(Stream.Read(), esriRasterOpCode)
      m_dSize = Convert.ToDouble(Stream.Read())
      m_dXOffset = Convert.ToDouble(Stream.Read())
      …
   End If
End Sub
In the  IClone.IsEqual  method, you may decide that the source and other symbols are equal if the RGB property values of IColor members are equivalent, instead of casting to IClone the color class and checking its IsEqual member in turn.
[C#]
bool ESRI.ArcGIS.esriSystem.IClone.IsEqual(ESRI.ArcGIS.esriSystem.IClone other)
{
   bool tempIClone_IsEqual = false;
   LogoMarkerSym.ILogoMarkerSymbol srcLogoSym = null;
   LogoMarkerSym.ILogoMarkerSymbol pRecLogoSym = null;
   IMarkerSymbol srcMarkerSym = null;
   IMarkerSymbol recMarkerSym = null;
   …
   if (other != null)
   {
      if (other is LogoMarkerSym.ILogoMarkerSymbol)
      {
         // Check for equality on default interface.
         srcLogoSym = other as ILogoMarkerSymbol;
         pRecLogoSym = this;
         tempIClone_IsEqual = tempIClone_IsEqual & (System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(pRecLogoSym.ColorBorder.RGB)).Equals(System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(srcLogoSym.ColorBorder.RGB))));
         tempIClone_IsEqual = tempIClone_IsEqual & (System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(pRecLogoSym.ColorLeft.RGB)).Equals(System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(srcLogoSym.ColorLeft.RGB))));
         …

[VB.NET]
Private Function IsEqual(ByVal other As ESRI.ArcGIS.esriSystem.IClone) As Boolean Implements ESRI.ArcGIS.esriSystem.IClone.IsEqual
   Dim tempIClone_IsEqual As Boolean = False
   Dim srcLogoSym As ILogoMarkerSymbol = Nothing
   Dim pRecLogoSym As ILogoMarkerSymbol = Nothing
   Dim srcMarkerSym As IMarkerSymbol = Nothing
   Dim recMarkerSym As IMarkerSymbol = NothingIf Not other Is Nothing Then
      If TypeOf other Is ILogoMarkerSymbol Then
         ' Check for equality on default interface.
         srcLogoSym = TryCast(other, ILogoMarkerSymbol)
         pRecLogoSym = Me
         tempIClone_IsEqual = tempIClone_IsEqual And (System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(pRecLogoSym.ColorBorder.RGB)).Equals(System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(srcLogoSym.ColorBorder.RGB))))
         tempIClone_IsEqual = tempIClone_IsEqual And (System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(pRecLogoSym.ColorLeft.RGB)).Equals(System.Drawing.ColorTranslator.FromOle(System.Convert.ToInt32(srcLogoSym.ColorLeft.RGB))))
         …

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值