ProGuide Annotation Construction Tools - Esri/arcgis-pro-sdk GitHub Wiki
Language: C#
Subject: Editing
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 10/06/2024
ArcGIS Pro: 3.4
Visual Studio: 2022
This ProGuide demonstrates how to build tools for creating annotation features. The code used to illustrate this guide can be found at Anno Tools Sample.
The accompanying ProGuide Annotation Editing Tools demonstrates how to build tools for modifying annotation features.
Important please consult ProConcepts Editing Annotation which covers special considerations for authors of editing tools for annotation and ProConcepts Annotation for general reference information on Annotation.
Prerequisites
- Download and install the sample data required for this guide as instructed in ArcGIS Pro SDK Community Samples Releases.
- Create a new ArcGIS Pro Module Add-in, and name the project AnnoTools. If you are not familiar with the ArcGIS Pro SDK, you can follow the steps in the ProGuide Build your first add-in to get started.
Step 1
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Construction Tool item to the add-in project, and name the item AnnoSimpleConstructionTool. Modify the Config.daml file tool item as follows:
- Change the caption to "Simple Anno Tool (Template)"
- Change the tool heading to "Simple Anno Tool (Template)" and the ToolTip text to "Simple Anno Construction Tool."
Step 2
By default, construction tools created with the Visual Studio template are assigned to the esri_editing_construction_point
category meaning point features will be created. You can see this in the Config.daml file by looking at the categoryRefID attribute.
<tool id="AnnoTools_AnnoSimpleConstructionTool" caption="Simple Anno Tool (Template)"
categoryRefID="esri_editing_construction_point"
className="AnnoSimpleConstructionTool"
loadOnClick="true"
smallImage="Images\GenericButtonRed16.png"
largeImage="Images\GenericButtonRed32.png">
<tooltip heading="Simple Anno Tool (Template)">Simple Anno Construction Tool
<disabledText /></tooltip>
</tool>
Modify this attribute to esri_editing_construction_annotation
to signify that this tool will create annotation features.
Step 3
Open the AnnoSimpleConstructionTool.cs file and review the code.
public AnnoSimpleConstructionTool()
{
IsSketchTool = true;
UseSnapping = true;
// set the sketch type to point
SketchType = SketchGeometryType.Point;
//Gets or sets whether the sketch is for creating a feature and should use the CurrentTemplate.
UsesCurrentTemplate = true;
//Gets or sets whether the tool supports firing sketch events when the map sketch changes.
//Default value is false.
FireSketchEvents = true;
}
/// <summary>
/// Called when the sketch finishes. This is where we will create the edit operation and
/// then execute it.</summary>
/// <param name="geometry">The geometry created by the sketch.</param>
/// <returns>A Task returning a Boolean indicating if the sketch complete event was
/// successfully handled.</returns>
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
if (CurrentTemplate == null || geometry == null)
return Task.FromResult(false);
// Create an edit operation
var createOperation = new EditOperation();
createOperation.Name = string.Format("Create {0}", CurrentTemplate.Layer.Name);
createOperation.SelectNewFeatures = true;
// pass the point geometry to the Create method
createOperation.Create(CurrentTemplate, geometry);
// Execute the operation
return createOperation.ExecuteAsync();
}
This is the standard code generated by the Visual Studio template for any construction tool regardless of feature class type. The constructor sets up the construction tool to have a SketchType of Point. The OnSketchCompleteAsync method creates a feature with the attribute values defined by the CurrentTemplate and the geometry returned from the sketch, wrapped in an EditOperation. Annotation construction tools differ from construction tools for other feature class types in only one way; the geometry passed to the EditOperation.Create
method is the geometry of the CIMTextGraphic, rather than the geometry of the feature.
In this construction tool, we will continue to use a SketchType of Point and pass a point geometry to the Create method, thus our annotation features will have a CIMTextGraphic shape of type point. (Remember that the annotation feature itself has a shape of type polygon.)
Step 4
Build the sample. Debug the add-in and start ArcGIS Pro. Open the SampleAnno.aprx project. Validate the UI by activating an annotation template.
Select the new construction tool and create some annotation features by digitizing points. Verify that the annotation text is orientated in a horizontal manner. Also note that the feature is created with the default text string of 'Text' and other attributes of the template including SymbolID.
Consider opening the template properties (right click on the template in the Create Features dock pane and choose Properties) and modifying some of the text attributes under the Annotation tab such as horizontal alignment, font size, font color or text string. Modifying individual text attributes allows you to customize the template symbol independently of modifying the symbol at the feature class definition level. There is only 1 symbol defined for the annotation feature class in this project so do not change the SymbolID of the template. Create some more features and verify that the symbol of your new annotation features match the text attributes of the template.
When finished, close ArcGIS Pro and return to your Visual Studio solution.
Step 5
In Visual Studio, add another ArcGIS Pro Add-ins | ArcGIS Pro Construction Tool item to the add-in project and call it AnnoAdvancedConstructionTool. We will modify the default implementation of this tool to create annotation features using a two point line and custom annotation text attributes.
Update the Config.daml file so that it looks like below (changing the categoryRefID and tooltip attributes).
<tool id="AnnoTools_AnnoAdvancedConstructionTool" caption="Advanced Anno Tool"
categoryRefID="esri_editing_construction_annotation"
className="AnnoAdvancedConstructionTool"
loadOnClick="true"
smallImage="Images\GenericButtonRed16.png"
largeImage="Images\GenericButtonRed32.png">
<tooltip heading="Advanced Anno Tool">Advanced Anno Construction Tool
<disabledText /></tooltip>
</tool>
Step 6
Open the AnnoAdvancedConstructionTool.cs file. In the constructor, change the SketchType of the construction tool to be of type Line.
public AnnoAdvancedConstructionTool()
{
IsSketchTool = true;
UseSnapping = true;
// set the sketch type to line
SketchType = SketchGeometryType.Line;
UsesCurrentTemplate = true;
FireSketchEvents = true;
}
Next, override the OnSketchModifiedAsync method of the tool to force the sketch to finish when two points have been digitized. This is achieved by retrieving the current sketch geometry using the MapTool GetCurrentSketchAsync method, casting the geometry to a Polyline and interrogating the number of points. If there are 2 points digitized, call the MapTools FinishSketchAsync method.
Add the following code to the AnnoAdvancedConstructionTool class.
protected override async Task<bool> OnSketchModifiedAsync()
{
// restrict the sketch to a 2 point line
bool finish = await QueuedTask.Run(async () =>
{
// get the current sketch
var geometry = await base.GetCurrentSketchAsync();
// cast to a polyline
var geom = geometry as ArcGIS.Core.Geometry.Polyline;
// check the point count
return geom?.PointCount >= 2;
});
// call FinishSketchAsync if we have 2 points
if (finish)
finish = await base.FinishSketchAsync();
return finish;
}
Build the sample. Debug the add-in and start ArcGIS Pro. Open the SampleAnno.aprx project. Activate an annotation template and ensure that the additional construction tool exists. Activate the Advanced Construction tool and verify that the sketch completes after digitizing two points.
Step 7
The final step is to modify the OnSketchCompleteAsync method. Rather than using the default implementation, use the EditOperation.Create method which uses an Inspector object. In this example we will change 4 text attribute values; TextString, Color, VerticalAlignment and Underline in addition to setting the AnnotationClassID, SymbolID and Shape.
In order to supply AnnotationClassID and SymbolID values, the valid labels and symbols for the annotation feature class need to be determined. The label and symbol collections can be retrieved by accessing the CIM definition of the annotation feature class. These routines need to run on the MCT, so wrap in a QueuedTask.Run. The following code illustrates this
bool result = await QueuedTask.Run(() =>
{
// get the anno layer
AnnotationLayer annoLayer = CurrentTemplate.Layer as AnnotationLayer;
if (annoLayer == null)
return false;
// get the anno feature class
var fc = annoLayer.GetFeatureClass() as
ArcGIS.Core.Data.Mapping.AnnotationFeatureClass;
if (fc == null)
return false;
// get the featureclass CIM definition which contains the labels, symbols
var cimDefinition = fc.GetDefinition() as
ArcGIS.Core.Data.Mapping.AnnotationFeatureClassDefinition;
var labels = cimDefinition.GetLabelClassCollection();
var symbols = cimDefinition.GetSymbolCollection();
// make sure there are labels, symbols
if ((labels.Count == 0) || (symbols.Count == 0))
return false;
});
Find the label class required. Typically this should match a particular subtype name or some other characteristic. Each label class has an associated symbol. This example attempts to find the label class that matches the current template name. Note this is not a very robust solution as user generated templates or templates which have been renamed will cause this to default to the first label class in the collection which may not be the expected result.
bool result = await QueuedTask.Run(() =>
{
....
// find the label class required
// typically you would use a subtype name or some other characteristic
// use the first label class
var label = labels[0];
if (labels.Count > 1)
{
// find a label class based on template name
foreach (var LabelClass in labels)
{
if (LabelClass.Name == CurrentTemplate.Name)
{
label = LabelClass;
break;
}
}
}
// each label has a textSymbol
// the symbolName *should* be the symbolID to be used
var symbolName = label.TextSymbol.SymbolName;
int symbolID = -1;
if (!int.TryParse(symbolName, out symbolID))
{
// int.TryParse fails - attempt to find the symbolName in the symbol collection
foreach (var symbol in symbols)
{
if (symbol.Name == symbolName)
{
symbolID = symbol.ID;
break;
}
}
}
// no symbol?
if (symbolID == -1)
return false;
});
Using the inspector object obtained from the CurrentTemplate, assign the AnnotationClassID and SymbolID fields with the appropriate values. This code should also be within the QueuedTask.Run.
// use the template's inspector object
var inspector = CurrentTemplate.Inspector;
// AnnotationClassID, SymbolID and Shape are the bare minimum for an annotation
// feature
// use the inspector[fieldName] to set the annotationClassid
// this is allowed since annotationClassID is a guaranteed field in the
// annotation schema
inspector["AnnotationClassID"] = label.ID;
// set the symbolID too
inspector["SymbolID"] = symbolID;
In ArcGIS Pro, the only fields guaranteed to exist in an annotation schema are AnnotationClassID, SymbolID, Element, FeatureID or FeatureGlobalID (if using a GlobalID relationship), ZOrder and Status along with the system ObjectID and Shape fields. All other fields which store text formatting attributes (such as TextString, FontName, VerticalAlignment, HorizontalAlignment etc) are optional. They are not guaranteed to exist (in the physical schema).
For this reason, the AnnotationProperties class was introduced at ArcGIS Pro 2.2 which provides access to the text attributes of an annotation feature. Obtain the AnnotationProperties class using the Inspector.GetAnnotationProperties method. Set the individual text attributes and CIMTextGraphic shape as appropriate and then use the Inspector.SetAnnotationProperties method to assign the values back on to the Inspector object.
// get the annotation properties
var annoProperties = inspector.GetAnnotationProperties();
// use the annotation properties to set the other attributes
annoProperties.TextString = "My annotation feature";
annoProperties.Color = ColorFactory.Instance.GreenRGB;
annoProperties.VerticalAlignment = ArcGIS.Core.CIM.VerticalAlignment.Top;
annoProperties.Underline = true;
// set the geometry to be the sketched line
// when creating annotation features the shape to be passed
// in the create operation is the CIMTextGraphic shape
annoProperties.Shape = geometry;
// set the annotation properties back on the inspector
inspector.SetAnnotationProperties(annoProperties);
Finally create the EditOperation class, and execute the feature creation.
// Create an edit operation
var createOperation = new EditOperation();
createOperation.Name = string.Format("Create {0}", CurrentTemplate.Layer.Name);
createOperation.SelectNewFeatures = true;
// create and execute using the inspector
createOperation.Create(CurrentTemplate.Layer, inspector);
return createOperation.Execute();
So the complete OnSketchCompleteAsync method looks like below.
/// <summary>
/// Called when the sketch finishes. This is where we will create the edit operation and
/// then execute it.</summary>
/// <param name="geometry">The geometry created by the sketch.</param>
/// <returns>A Task returning a Boolean indicating if the sketch complete event was
/// successfully handled.</returns>
protected override async Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
if (CurrentTemplate == null || geometry == null)
return false;
bool result = await QueuedTask.Run(() =>
{
// get the anno layer
AnnotationLayer annoLayer = CurrentTemplate.Layer as AnnotationLayer;
if (annoLayer == null)
return false;
// get the anno feature class
var fc = annoLayer.GetFeatureClass()
as ArcGIS.Core.Data.Mapping.AnnotationFeatureClass;
if (fc == null)
return false;
// get the featureclass CIM definition which contains the labels, symbols
var cimDefinition = fc.GetDefinition() as
ArcGIS.Core.Data.Mapping.AnnotationFeatureClassDefinition;
var labels = cimDefinition.GetLabelClassCollection();
var symbols = cimDefinition.GetSymbolCollection();
// make sure there are labels, symbols
if ((labels.Count == 0) || (symbols.Count == 0))
return false;
// find the label class required
// typically you would use a subtype name or some other characteristic
// use the first label class
var label = labels[0];
if (labels.Count > 1)
{
// find a label class based on template name
foreach (var LabelClass in labels)
{
if (LabelClass.Name == CurrentTemplate.Name)
{
label = LabelClass;
break;
}
}
}
// each label has a textSymbol
// the symbolName *should* be the symbolID to be used
var symbolName = label.TextSymbol.SymbolName;
int symbolID = -1;
if (!int.TryParse(symbolName, out symbolID))
{
// int.TryParse fails - attempt to find the symbolName in the symbol collection
foreach (var symbol in symbols)
{
if (symbol.Name == symbolName)
{
symbolID = symbol.ID;
break;
}
}
}
// no symbol?
if (symbolID == -1)
return false;
// use the template's inspector object
var inspector = CurrentTemplate.Inspector;
// AnnotationClassID, SymbolID and Shape are the bare minimum for an
// annotation feature
// use the inspector[fieldName] to set the annotationClassid
// this is allowed since annotationClassID is a guaranteed field in the
// annotation schema
inspector["AnnotationClassID"] = label.ID;
// set the symbolID too
inspector["SymbolID"] = symbolID;
// get the annotation properties
var annoProperties = inspector.GetAnnotationProperties();
// use the annotation properties to set the other attributes
annoProperties.TextString = "My annotation feature";
annoProperties.Color = ColorFactory.Instance.GreenRGB;
annoProperties.VerticalAlignment = ArcGIS.Core.CIM.VerticalAlignment.Top;
annoProperties.Underline = true;
// set the geometry to be the sketched line
// when creating annotation features the shape to be passed
// in the create operation is the CIMTextGraphic shape
annoProperties.Shape = geometry;
// set the annotation properties back on the inspector
inspector.SetAnnotationProperties(annoProperties);
// Create an edit operation
var createOperation = new EditOperation();
createOperation.Name = string.Format("Create {0}", CurrentTemplate.Layer.Name);
createOperation.SelectNewFeatures = true;
// create and execute using the inspector
createOperation.Create(CurrentTemplate.Layer, inspector);
return createOperation.Execute();
});
return result;
}
Build the sample. Debug the add-in and start ArcGIS Pro. Open the SampleAnno.aprx project. Use the second custom construction tool to create some new annotation features. Note the text, color, vertical alignment and underlining of the created annotation text. Note also that the orientation of your digitized line changes the angle of the annotation text of the created feature.
Close ArcGIS Pro and return to Visual Studio.
Continue to the ProGuide Annotation Editing Tools for a walkthrough and examples of building editing tools for annotation features.