-
Notifications
You must be signed in to change notification settings - Fork 120
ProConcepts Editing
The editing functionality in ArcGIS Pro is delivered through the ArcGIS.Desktop.Editing assembly. This assembly provides the framework to create and maintain your geographic data.
ArcGIS.Desktop.Editing.dll
Language: C#
Subject: Editing
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 9/28/2021
ArcGIS Pro: 2.9
Visual Studio: 2017, 2019
- Editing in ArcGIS Pro
- Editing controls
- Performing Edits in Pro
- Feature Inspector and Working with Attributes
- Feature Templates and Table Templates
- Annotation Feature Editing
- Dimension Feature Editing
- Snapping
- Edit Operations and Long and Short Transactions
- Edit Events
- Sketch Feedback and Sketch Symbology
- Editing Options
The editing assembly is the entry point for performing all edits in ArcGIS Pro. It provides coarse grained classes for making edits to layers in the map in addition to directly editing feature classes through the Geodatabase API. When developing editing customizations, you can incorporate concepts and functionality from other assemblies, notably Geometry and Geodatabase.
The application framework can be customized to add your own commands, tools and modules. The type of customization to use is largely based on how the user will interact with it. The following sections provide an overview of the types of customizations available.
Commands are usually buttons or menu items on the UI. Their functions typically work with a selected set, a whole layer, or by manipulating the editing environment in some way. Commands do not interact with the display, so if you need to draw a sketch, select features, or define an area, you need to write a Tool instead. The Save, Discard, and Delete buttons on the Edit tab are examples of commands specific to the editing environment.
You can use the ArcGIS Pro Button template in Visual Studio to create a command.
Tools allow you to interact with the display in some manner. They can be used to define areas, create a sketch, or select features for an edit operation. They can be active or inactive. When a tool is active, it is THE tool that the user is interacting with. Only one tool can be active in the application at a time.
Sketch tools are used to create an edit sketch in the display. The returned geometry is then used for further edit operations such as editing existing features.
You can use the ArcGIS Pro MapTool template in Visual Studio to create a sketch tool. A MapTool can sketch feedback on the UI when its this.IsSketchTool
property is set to true. Use the SketchType
property to specify what type of feedback you require. Common values are SketchGeometryType.Point
, SketchGeometryType.Rectangle
, SketchGeometryType.Line
or SketchGeometryType.Polygon
. Other options include SketchGeometryType.Circle
, SketchGeometryType.Ellipse
, or SketchGeometryType.Lasso
amongst others.
At 2.7+, a regular N-sided regular polygon or polyline feedback can be achieved by using SketchGeometryType.RegularPolygon
or SketchGeometryType.RegularPolyline
, along with setting the SketchNumerofSides
property. The higher the number of sides, the more the sketch feedback begins to represent a circle. If no number of sides is specified, the sketch defaults to 6 (hexagon). The maximum number of sides is 100, the minimum that can be specified is 3.
For example; use the following code to sketch octagon feedback.
public CustomEditTool() {
IsSketchTool = true;
SketchType = SketchGeometryType.RegularPolygon;
SketchNumberOfSides = 8;
...
}
For more information on MapTools, see the MapTool section in the Map Exploration ProConcepts. The editing section in the community samples contains many examples of sketch tools.
Sketch tools can also make use of the various sketch events within their workflows.
Sketch Tools can make use of a property in their constructors called UseSelection. Generally speaking, UseSelection, when set true, marks a tool as needing a selection in order to operate. If a COTS tool is activated that requires a selection, and no features are selected, it automatically goes into select mode to allow you to select a feature before beginning the sketch.
Custom tools can also toggle between sketch and select modes by using the UseSelection property. If a custom tool sets UseSelection = true it can be placed into select mode by holding down the SHIFT key. Releasing the SHIFT key puts the tool back into sketch mode and restores the previous state of the sketch (and the sketch undo / redo stack). Note your SketchType must be SketchGeometryType.Point
, SketchGeometryType.Multipoint
, SketchGeometryType.Line
or SketchGeometryType.Polygon
in order to be able to toggle into select mode. No other SketchTypes support this behavior.
public CustomEditTool() {
IsSketchTool = true;
SketchType = SketchGeometryType.Line;
...
//To allow default activation of "selection" mode
//Hold down the SHIFT key to toggle into
//selection mode. The state of the sketch is preserved.
UseSelection = true;//set to false to "turn off"
}
ActivateSelectAsync
Developers are not restricted to using the default SHIFT key behavior to toggle to select mode. If developers want to "manually" toggle their tools (into select mode) they can use ActivateSelectAsync(bool activate) with activate = true. To go back to sketch mode, the tool must call ActivateSelectAsync with activate = false. With UseSelection = true
set in the constructor, the previous state of the sketch will be restored, including the sketch undo/redo stack, when the tool toggle back to sketch mode. If UseSelection=false
(the default), the sketch is simply cleared and the tool operator begins sketching again.
Tools that choose to manually toggle between select mode and sketch mode via ActivateSelectAsync are advised to handle the SHIFT key to override its default behavior (if they have UseSelection = true
set in their constructors).
Again, only SketchTypes of SketchGeometryType.Point
, SketchGeometryType.Multipoint
, SketchGeometryType.Line
or SketchGeometryType.Polygon
support this behavior.
The behavior of a custom tool with regards to UseSelection and ActivateSelectAsync is described in the table below:
UseSelection | Select Mode | Notes |
---|---|---|
True | SHIFT key | Gets built-in behavior. Sketch preserved. |
True | Custom key | Manual. Use ActivateSelectAsync. Sketch preserved. Handle SHIFT |
False | N/A | Select mode not available |
False | Custom key | Manual. Use ActivateSelectAsync. Sketch cleared. |
An example tool based on the code from the DemoUseSelection sample shows an implementation of ActivateSelectAsync. Notice how it handles the SHIFT key to prevent the base tool behavior from interfering.
internal class DifferenceTool_v2 : MapTool {
private bool _inSelMode = false;
public CustomEditTool() {
IsSketchTool = true;
...
//Set UseSelection = false to clear the sketch if we toggle
//Handle the SHIFT key to prevent it interfering
UseSelection = true;
}
protected override void OnToolKeyDown(MapViewKeyEventArgs k) {
//toggle sketch selection mode with a custom key
if (k.Key == System.Windows.Input.Key.W) {
if (!_inSelMode) {
_inSelMode = true;
k.Handled = true;
//toggle the tool to select mode.
//The sketch is saved if UseSelection = true;
ActivateSelectAsync(true);
}
}
else if (!_inSelMode) {
//disable effect of Shift in the base class.
//Mark the key event as handled to prevent further processing
k.Handled = Module1.Current.IsShiftKey(k);
}
}
protected override Task HandleKeyDownAsync(MapViewKeyEventArgs k) {
//Called when we return "k.Handled = true;" from OnToolKeyDown
//TODO any additional key down handling logic here
return Task.FromResult(0);
}
protected override void OnToolKeyUp(MapViewKeyEventArgs k) {
if (k.Key == System.Windows.Input.Key.W) {
if (_inSelMode) {
_inSelMode = false;
k.Handled = true;//process this one
//Toggle back to sketch mode. If UseSelection = true
//the sketch will be restored
ActivateSelectAsync(false);
}
}
else if (_inSelMode) {
//disable effect of Shift in the base class.
//Mark the key event as handled to prevent further processing
k.Handled = Module1.Current.IsShiftKey(k);
}
}
protected override Task HandleKeyUpAsync(MapViewKeyEventArgs k) {
//Called when we return "k.Handled = true;" from OnToolKeyUp
//TODO any additional key up handling logic here
return Task.FromResult(0);
}
protected override Task OnToolActivateAsync(bool active) {
//clear the flag
_inSelMode = false;
return base.OnToolActivateAsync(active);
}
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry) {
var mv = MapView.Active;
return QueuedTask.Run(() => {
...
//TODO - the actual edits
});
}
}
internal class Module1 : Module {
...
public bool IsShiftKey(MapViewKeyEventArgs k) {
return (k.Key == System.Windows.Input.Key.LeftShift ||
k.Key == System.Windows.Input.Key.RightShift);
}
Construction tools-such as the Point, Line, Polygon, and Circle tools—are used to create features within the template environment. Construction tools are sketch tools that define the type of edit template geometry they are associated with.
You can use the ArcGIS Pro Construction Tool template in Visual Studio to create a construction tool. Configure the categoryRefID
tag in the config.daml file for your tool to set the type of geometry feature class the tool will be associated with. Configure the SketchType
property in the tool constructor to the correct geometry that you wish to sketch (rectangle, polygon, point, line etc).
Here is the default implementation of a construction tool provided by the Visual Studio template.
<tool id="Sample_ConstructionTool_ConstructionTool1" categoryRefID="esri_editing_construction_point"
caption="ConstructionTool 1"
className="ConstructionTool1" loadOnClick="true"
smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed16.png"
largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed32.png">
<!--note: use esri_editing_construction_polyline, esri_editing_construction_polygon for categoryRefID as needed-->
<tooltip heading="Tooltip Heading">Tooltip text<disabledText /></tooltip>
<content guid="ee73d46f-913f-42b6-b951-57e18622f183" />
</tool>
internal class ConstructionTool1 : MapTool
{
public ConstructionTool1()
{
IsSketchTool = true;
UseSnapping = true;
// Select the type of construction tool you wish to implement.
// Make sure that the tool is correctly registered with the correct component category type in the daml
SketchType = SketchGeometryType.Point;
// SketchType = SketchGeometryType.Line;
// SketchType = SketchGeometryType.Polygon;
//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 sketch 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;
// Queue feature creation
createOperation.Create(CurrentTemplate, geometry);
// Execute the operation
return createOperation.ExecuteAsync();
}
}
Table Construction Tools are a special type of construction tool used to create rows in a standalone table. By definition, a table does not have a geometry field, so a table construction tool by default does not sketch feedback on the UI. It does however follow the tool pattern of being active or inactive and the construction tool pattern of being associated with a template type (specified by the categoryRefID attribute in the config.daml file).
If you wish to build a custom tool to create a row in a standalone table, use the ArcGIS Pro Table Construction Tool template in Visual Studio. The categoryRefID tag in the config.daml file defaults to the table category, and the constructor for the tool class has the IsSketchTool property set to false and the SketchType property set to None to reflect the default behavior of not sketching on the UI.
Here is the default implementation of a table construction tool
<tool id="Sample_ConstructionTool_TableConstructionTool1" categoryRefID="esri_editing_construction_table"
caption="TableConstructionTool 1"
className="TableConstructionTool1" loadOnClick="true"
smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed16.png"
largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed32.png">
<!--note: use esri_editing_construction_polyline, esri_editing_construction_polygon for categoryRefID as needed-->
<tooltip heading="Tooltip Heading">Tooltip text<disabledText /></tooltip>
<content guid="8800805c-e704-4c72-97dc-fd54bd5cbd66" />
</tool>
class TableConstructionTool1 : MapTool
{
public TableConstructionTool1()
{
IsSketchTool = false;
// set the SketchType to None
SketchType = SketchGeometryType.None;
//Gets or sets whether the sketch is for creating a feature and should use the CurrentTemplate.
UsesCurrentTemplate = true;
}
/// <summary>
/// Called when the "Create" button is clicked. This is where we will create the edit operation and then execute it.
/// </summary>
/// <param name="geometry">The geometry created by the sketch - will be null because SketchType = SketchGeometryType.None</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)
return Task.FromResult(false);
// geometry will be null
// Create an edit operation
var createOperation = new EditOperation();
createOperation.Name = string.Format("Create {0}", CurrentTemplate.StandaloneTable.Name);
createOperation.SelectNewFeatures = false;
// determine the number of rows to add
var numRows = this.CurrentTemplateRows;
for (int idx = 0; idx < numRows; idx++)
// Queue feature creation
createOperation.Create(CurrentTemplate, null);
// Execute the operation
return createOperation.ExecuteAsync();
}
}
Within the Create Features UI, a row can be created for a table template by clicking the "Create" button. The active tool receives the OnSketchCompleteAsync callback with a null geometry parameter. Table construction tools use an additional property CurrentTemplateRows
to determine how many rows are to be created. The UI control connected to this property is visible on the ActiveTemplate pane for the table template.
Table construction tools can be modified to become sketching construction tools. For example, perhaps you wish to create a construction tool that will obtain certain attributes from features on the map and have them be populated in your table row. In this case you would change the IsSketchTool property to true, modify the SketchType property to the appropriate geometry type and add the UseSnapping property (set to true) if you require snapping.
public TableConstructionTool1()
{
IsSketchTool = true;
// set the SketchType
SketchType = SketchGeometryType.Rectangle;
// sketch will snap to features
UseSnapping = true;
//Gets or sets whether the sketch is for creating a feature and should use the CurrentTemplate.
UsesCurrentTemplate = true;
}
The table construction tool now operates like a feature construction tool (ie it can sketch feedback on the UI and when the sketch is finished, the tool receives the OnSketchCompleteAsync callback with the geometry parameter populated with the sketch), and you can use the sketch geometry to obtain information from the map to populate the table rows.
The TableConstructionTool sample provides examples of table construction tools including two that are sketching construction tools.
Every add-in creates a module that you can add custom code to. In an editing context, this can be used to set up event listeners for editing or application events when the module loads, assuming AutoLoad is true in the module configuration. See the Events section in this topic for more information on working with events.
Editing in the application, starting at 2.6, can be enabled and disabled via the UI. For users this means first checking on the "Enable and disable editing from the Edit tab" in the Session section of the Editing options on the Pro backstage.
If this checkbox is checked, the "Toggle editing on/off" button becomes visible on the Edit UI ribbon and users must enable editing within a project before doing any edits (they can also disable editing or "toggle it off" once they have enabled it or "toggled it on"). If the "Enable and disable editing" checkbox has been checked, then editing must always be enabled for any project after it is opened; the editing state of a project is not preserved on close.
Custom editing tools and buttons that want to be enabled and disabled in concert with the Pro editing tools, in response to the editing status of the application, can use either the pre-existing esri_editing_EditingPossibleCondition
condition or, for (map) tools, they can use the slightly more robust esri_editing_EditingMapCondition
condition which additionally checks that a 2D map has been activated. If a user disables editing on the UI, Edit Operations in core tools and add-ins will not execute and return false (see "EditOperation" sub-section below).
By default, unless the user has explicitly checked the "Enable and disable editing" checkbox (or an add-in has disabled editing), editing in a project will always be enabled. Add-ins can check the editing enabled status within a given project via the Project.Current.IsEditingEnabled property. To enable or disable editing via the api, add-ins can call Project.Current.SetIsEditingEnabledAsync with true or false respectively.
If Project.Current.SetIsEditingEnabledAsync is called with "true", editing is enabled and the editing UI will likewise be enabled. If Project.Current.SetIsEditingEnabledAsync is called with "false", any on-going editing is stopped and further editing is disabled. The editing UI is also disabled. Before add-ins can disable editing any pending edits must either be discarded or saved or the call to Project.Current.SetIsEditingEnabledAsync(false)
will fail. Enabling and disabling editing via the api is shown in the following example:
Enable
//Can we edit ? If not, ask the user to enable editing
if (!Project.Current.IsEditingEnabled) {
var res = MessageBox.Show(
"You must enable editing to use editing tools. Would you like to enable editing?",
"Enable Editing?", System.Windows.MessageBoxButton.YesNoCancel);
if (res == System.Windows.MessageBoxResult.No ||
res == System.Windows.MessageBoxResult.Cancel)
return;//user does not want to enable editing
Project.Current.SetIsEditingEnabledAsync(true);
}
Disable
//We are editing. Does the user want to disable it?
if (Project.Current.IsEditingEnabled) {
var res = MessageBox.Show(
"Do you want to disable editing? Editing tools will be disabled",
"Disable Editing?", System.Windows.MessageBoxButton.YesNoCancel);
if (res == System.Windows.MessageBoxResult.No ||
res == System.Windows.MessageBoxResult.Cancel)
return;//user does not want to stop
//Before disabling we must check for any edits
if (Project.Current.HasEdits) {
res = MessageBox.Show("Save edits?", "Save Edits?",
System.Windows.MessageBoxButton.YesNoCancel);
if (res == System.Windows.MessageBoxResult.Cancel)
return;//user has cancelled
else if (res == System.Windows.MessageBoxResult.No)
Project.Current.DiscardEditsAsync(); //user does not want to save
else
Project.Current.SaveEditsAsync();//save
}
//ok to disable editing
Project.Current.SetIsEditingEnabledAsync(false);
}
EditOperation
If editing has been disabled in the current project, meaning Project.Current.IsEditingEnabled
returns false, edit operations will not execute. If editOperation.Execute()
is called, it is essentially a no-op and returns false. Once disabled, editing must be (re)enabled either from the UI or via a call to Project.Current.SetIsEditingEnabledAsync(true)
to successfully execute an edit operation.
Prior to 2.6
Prior to 2.6 developers can continue to use the esri_editing_EditingPossibleCondition
DAML condition. Add-ins can enable or disable the editing UI by deactivating or activating the "esri_editing_editorDisabled" state respectively. Edit operations, however, are not affected and continue to execute. The "esri_editing_editorDisabled" state is intended to be used during an add-in module initialization and not while a project is open.
//Pre-2.6, Pro editing UI can be enabled/disabled via the
//esri_editing_editorDisabled state
if (!FrameworkApplication.State.Contains("esri_editing_editorDisabled")) {
FrameworkApplication.State.Activate("esri_editing_editorDisabled");//Disable
}
else {
FrameworkApplication.State.Deactivate("esri_editing_editorDisabled");//re-enable
}
The two preferred mechanisms are the EditOperation and Inspector classes. EditOperations provide a coarse-grained API for creating and manipulating shapes and attributes whereas Inspector is a utility class more geared toward editing attributes of selected features.
Edit operations perform 3 key functions:
- Execute the operations against underlying datastores (edit operations can span multiple datastores)
- Consolidate multiple edits into a single operation
- Invalidate any layer caches associated with the layers edited by the Edit Operation.
All edits specified for a given edit operation instance are combined into a single "execute" (e.g. creation, modifying geometry and attributes, deleting features, etc.). Combining individual edits into a single execute improves performance over executing each edit individually. Edit operations can combine edits across different datasets in the same datastore or different datastores. Pro establishes and manages the edit sessions on all underlying datastores (assuming long transaction semantics, more on this here). All edits within all active edit sessions are saved or discarded together (when a user "saves" or "discards" edits on the Pro UI or via the API). At the conclusion of a save or discard, all existing edit sessions are closed and the undo/redo operation stack is cleared.
Each edit specified on an edit operation instance must be independent of the other edits also specified. For example, a modify cannot depend on the completion of a create or a clip specified on the same edit operation nor on the completion of a move or rotate, and so on. If edits are dependent on each other, the canonical example being the creation of a feature combined with the creation of an attachment, then those operations must be chained (see chaining edit operations). The ordering of the EditOperation methods in your add-in code has no bearing on the order in which the edits are executed. Edits within an edit operation are executed in an arbitrary order though the edit operation callback is always executed last.
If the underlying datastores on which the edits are being performed support undo/redo then the EditOperation adds an entry to Pro's Undo operation stack. If a single edit specified within the execute fails then all edits in the edit operation will fail. The ExecuteAsync and Execute methods return a Boolean indicating the success of the edit. If the edit fails, returning false, the ErrorMessage property is populated with an error description, if known. The value in the "ErrorMessage" property will be applicable to the first operation that failed. At the conclusion of the execute, the edit operation also ensures that any underlying layer and table caches are invalidated.
Here are 3 examples of editing using edit operations:
The first example shows 3 separate creates along with two feature updates (all independent) combined into a single edit operation:
var editOp = new EditOperation();
editOp.Name = "Simple edit operation";
//Add three points - 1 point to each layer
editOp.Create(pointsLayer1, start_pt);
editOp.Create(pointsLayer2, GeometryEngine.Instance.Move(start_pt, distance, 0.0));
editOp.Create(pointsLayer3, GeometryEngine.Instance.Move(start_pt, distance * 2, 0.0));
//Modify two polygons - 1 polygon in two layers
editOp.Modify(polyLayer2, 1, GeometryEngine.Instance.Buffer(boundary, distance));
editOp.Modify(polyLayer3, 1, GeometryEngine.Instance.Buffer(boundary, distance * 2));
//Execute the operations
editOp.ExecuteAsync();
The second example shows Clip, Split, and Planarize edits on a single feature also combined into a single edit operation:
//Multiple operations can be performed by a single
//edit operation.
var op = new EditOperation();
op.Name = "Clip, Split, and Planarize Features";
//Combine three operations, one after the other...
op.Clip(featureLayer, oid, clipPoly);
op.Split(featureLayer, oid, cutLine);
op.Planarize(featureLayer, new List<long>() { oid});
//Execute them all together
await op.ExecuteAsync();
if (!op.IsSucceeded) {
//TODO: get the op.ErrorMessage, inform the user
}
In the third example, a dictionary is being used to set preliminary values for a new feature to include two of its attributes and geometry:
var polyLayer = MapView.Active.Map.GetLayersAsFlattenedList().First((l) => l.Name == "Polygons") as FeatureLayer;
var polygon = .... ; //New polygon to use
//Define some default attribute values
Dictionary<string,object> attributes = new Dictionary<string, object>();
attributes["SHAPE"] = polygon;//Geometry
attributes["Name"] = string.Format("Poly {0}", ++count);
attributes["Notes"] = string.Format("Notes {0}", count);
//Create the new feature
var op = new EditOperation();
op.Name = string.Format("Create {0}", polys.Name);
op.Create(polyLayer, attributes);
await op.ExecuteAsync();
if (!op.IsSucceeded) {
//TODO: get the op.ErrorMessage, inform the user
}
Refer to the ProSnippets Editing for other examples of using EditOperation.
In certain situations a given edit may be dependent on the results of a previous edit being committed yet you still want all the edits to be grouped as one undo-able entry on the undo/redo stack. For example you may wish to create a feature and add an attachment to that feature. You cannot queue the Create and AddAttachment methods in the same EditOperation because the AddAttachment method signature requires the Object ID of its related feature (meaning it must already have been created). Thus you are restricted to calling these methods in two separate EditOperations. Calling two separate edit operations will add two separate "undo" items to the undo/redo stack (requiring two undo's to undo the feature create and/or two redo's to redo it). This is not your desired solution. Instead, by chaining the second "AddAttachment" edit operation to the first, even though the two operations execute independently, only one undo operation is added to the undo/redo stack. Undo-ing or redo-ing the create feature and its attachment is accomplished with a single operation.
Therefore, a chained operation is simply an EditOperation that is linked to a previous EditOperation allowing all edits from both operations to be part of the same undo/redo item. You use a chained operation when the edit you require is dependent on the results of previous edits being committed and you want the combined group of edits to be linked together and treated as one item on the undo/redo stack. Any number of edit operations can be chained.
To chain an edit operation, create it using the EditOperation.CreateChainedOperation()
method on the edit operation instance you want to be chained to (and not via "new"). Call CreateChainedOperation once the preceding edit operation is known to have executed successfully. Configure the chained operation as needed and then call its execute method (so both edit operations are explicitly executed). When the chained operation is executed, its undo/redo is linked with, or chained to, the Undo operation already added by the preceding edit operation.
The code snippet below illustrates this example of a chained edit linking the Create and AddAttachment methods. The first edit operation creates the feature and obtains the Object ID when the operation is executed. The second operation, AddAttachment, is chained to the first operation via var op2 = op1.CreateChainedOperation();
(instead of creating the second edit operation with var op2 = new EditOperation()
). It adds the attachment using the Object ID of the newly created feature from the previous operation. Because these operations are chained, they are treated as a single operation on the undo stack (which will either apply or undo the creation and add attachment together).
//create a feature and add an attachment as one operation.
return QueuedTask.Run(() =>
{
//create an edit operation and name.
var op1 = new EditOperation();
op1.Name = string.Format("Create point in '{0}'", CurrentTemplate.Layer.Name);
//create a new feature and return the oid.
long newFeatureID = -1;
op1.Create(CurrentTemplate, geometry, oid => newFeatureID = oid);
// newFeatureID is populated when the operation executes
bool result = op1.Execute();
if (result)
{
//create a chained operation from the first
var op2 = op1.CreateChainedOperation();//we do NOT use "new"!
// add the attachment
op2.AddAttachment(CurrentTemplate.Layer, newFeatureID, @"C:\Hydrant.jpg");
op2.Execute();//The chained edit operation is executed
return new Tuple<bool, string>(op2.IsSucceeded, op2.ErrorMessage);
}
return new Tuple<bool, string>(op1.IsSucceeded, op1.ErrorMessage);
});
In the Utility Network topology, features are related to each other through geometric coincidence (features co-located at the same physical x, y, z coordinates) or via association mechanisms. An association is used when features to be "associated" are not necessarily geometrically coincident. One feature can have many associations (e.g., a utility pole and a transformer, a vault and a switch or fuse, and so on). When creating features and associations, it may be desirable to create the features and their associations in a single transaction. Similar to the "create feature and attachment" scenario, the challenge is the features must be created before they can be associated.
Therefore, to allow feature and association creation to be combined, associations between features can be defined using a placeholder for the Global IDs of the features "to be" created that will end up being associated. The special placeholder is an ArcGIS.Desktop.Editing.RowHandle
. The RowHandle can either be a real row (in the case of an association made on existing features) or can represent a row that will be made in the future (i.e., at some point within the scope of the edit operation execute). To create a RowHandle for features to be created (and associated), use an ArcGIS.Desktop.Editing.RowToken
, when constructing the association RowHandles.
The EditOperation
class provides a series of CreateEx overloads that return a RowToken immediately after they are called (before EditOperation.Execute
). The RowToken can be passed into the constructor of the RowHandle when creating the appropriate association. When the edit operation is executed, the RowTokens are replaced with the real rows and the association is created in conjunction with the feature creates. The creation of the features and association(s) are treated as a single undo-able operation on the Undo/Redo stack (same as chaining edit operations).
In this example, a utility pole and a transformer are created and associated within a single edit operation:
var editOp = new EditOperation();
editOp.Name = "Create pole + transformer bank. Attach bank to pole";
//Create the transformer and pole
RowToken transformerToken = editOp.CreateEx(transformerLayer, transformerAttributes);
RowToken poleToken = editOp.CreateEx(poleLayer, poleAttributes);
//Create association using the row tokens not features
var poleAttachment = new AssociationDescription(AssociationType.Attachment,
new RowHandle(poleToken), new RowHandle(transformerToken));
editOp.Create(poleAttachment);
//Execute the EditOperation
editOp.ExecuteAsync();
Refer to Utility Network Associations for more detail.
There are certain instances where you need to perform edits that span both GIS and non-GIS data and the edits (GIS and non-GIS) must be applied (or undone) within a single edit operation. For example, you create a new parcel feature in the Geodatabase and need to insert a row into one or more assessor tables, or you modify an asset in the GIS (facility, customer, utility related) and need to propagate the change to related business tables (non-GIS). In these scenarios, you will need to perform your edits within an EditOperation.Callback()
method, topic 9494.
When working directly with feature classes or tables in a "callback", you are responsible for providing your own editing logic (e.g. using GeometryEngine to edit or create geometry and Rows and RowCursors for row or feature attribute updates). You cannot use edit operation methods within the context of your call back routine or lambda. The edit operation callback is executed when EditOperation.ExecuteAsync
(or EditOperation.Execute
) is called (not when the call back lambda is declared). The edit operation will perform the edits specified in the call back Action delegate within its ("edit operation") transactional scope.
The following restrictions apply when implementing editing logic within EditOperation Callbacks:
- All edits must be done "by hand". No mixing in of other edit operation method calls.
- Use non-recycling row cursors to update rows
- You must call Store, CreateRow, etc. as needed to commit changes, create new rows, etc.
- You are responsible for feature and feature dataset invalidation. Layer and table caches are not updated automatically when using an edit operation callback.
The following example illustrates the general implementation pattern of EditOperation call back:
var editOp = new EditOperation();
editOp.Name = "Do callback";
var featLayer1 = ...
editOp.Callback((context) => {
//Do all your edits here – use non-recycling cursors
var qf = ...;
using (var rc = featLayer1.GetTable().Search(qf, false)) {
while (rc.MoveNext()) {
context.Invalidate(rc.Current);//Invalidate the row before
//Do edits...
rc.Current.Store(); //call store
context.Invalidate(rc.Current);//Invalidate the row after
}
}
//Edit the non_versioned_table here, etc.
//Note: can also invalidate any datasets (instead of rows)
//context.Invalidate(featLayer1.GetTable()); - simpler but less efficient
}, featLayer1.GetTable(), non_versioned_table,...); //Pass as parameters the datasets
//and tables you will be editing
editOp.Execute();
When deleting features within a callback, call invalidate before the feature delete.
while (rc.MoveNext()) {
context.Invalidate(rc.Current);//Invalidate the row ~before~ delete only
rc.Current.Delete(); //delete the row
}
The EditOperation.IEditContext
provides an Abort
method that can be called within the edit operation callback to cancel the on-going transaction. After calling EditOperation.IEditContext.Abort
your callback should exit.
Any undo-able edits will be rolled back, and the edit operation result from Execute will be false. The edit operation error message will have the value specified when the abort was made.
var editOp = new EditOperation();
editOp.Callback(Edits, dataset1, dataset2); //Nothing happens at the transaction level when this executes
await editOperation.ExecuteAsync(); //will return false if the callback is aborted
//The callback 'Edits'
private void Edits(EditOperation.IEditContext context) {
bool somethingBadHappened = false;
... Editing logic here ---
if (somethingBadHappened)
context.Abort("This is the ErrorMessage");
//TODO, any clean up
return;//exit
}
}
Edits can be attempted on all editable layers and standalone tables, i.e. MapMembers, determined by the CanEditData property on BasicFeatureLayer, StandAloneTable and IDisplayTable. This property is a combination of the editability of the data source (read/write), and the IsEditable property of those classes which represents the check box state on the List by Editing view of the table of contents.
For updating attribute values of features, the Editor extension provides a convenient utility class called Inspector class. The basic pattern for using the inspector is:
- Instantiate an inspector instance (
var inspector = new Inspector();
) - Load (the attributes) of one or more features into the inspector (
inspector.Load(...)
orinspector.LoadAsync(...)
). This will load joined data. - Use indexer-style access (via the "[" and "]" brackets) by field index or name to set attribute values.
- Call
Inspector.ApplyAsync()
or useEditOperation.Modify(inspector)
to apply the attribute updates to the set of features.
The Inspector.ApplyAsync()
method applies and saves the changes in one shot (there is no undo). Whereas EditOperation.Modify(inspector)
will apply the changes and add an undo operation to the Pro undo/redo stack on EditOperation.Execute
.
Note: All features loaded into the inspector must be from the same feature layer. Only feature values that are modified in the inspector are applied to the loaded feature(s). If tables are joined to the feature layer then the joined attributes are loaded also.
In this example, the attributes for one feature are loaded and a name attribute is modified.
var inspector = new Inspector();
await inspector.LoadAsync(roads, oid);
inspector["NAME"] = "New name";
var op = new EditOperation();
op.Name = string.Format("Modify Road {0}",oid) ;
op.Modify(inspector);
await op.ExecuteAsync();
//Or
//await inspector.ApplyAsync(); //but no undo
Feature geometry can be updated as well as attributes simply by setting the "SHAPE"
field to the new geometry value.
var poly = ....
var inspector = new Inspector();
inspector.Load(...);
inspector["NAME"] = "New name";
inspector.Shape = poly;
//Or...where "SHAPE" is the name of the shape field
//inspector["SHAPE"] = poly;
//Elsewhere
await inspector.ApplyAsync();
In this example the first features selected by a zipcode polygon are loaded. Their "Zipcode" attribute is modified:
var zipcode = 92373;
var zippoly = ...;
var inspector = new Inspector();
//Select all visible features that are within the polygon
//Get the first layer that has features selected...
var kvp = MapView.Active.SelectFeatures(zippoly, isWhollyWithin: true).FirstOrDefault(k => k.Value.Count > 0);
if (kvp.Key != null) {
// load the features from the layer
await inspector.LoadAsync(kvp.Key, kvp.Value);
//If the layer has a field called Zipcode, set its value
//and update all the selected features
if (inspector.HasAttributes && inspector.Count(a => a.FieldName == "Zipcode") > 0) {
inspector["Zipcode"] = zipcode;
var op = new EditOperation();
op.Name = string.Format("Updated {0} Zipcode to {1}",
kvp.Key.Name, zipcode);
op.Modify(inspector);
await op.ExecuteAsync();
}
}
The inspector can also be used as a convenient short-cut for querying the attributes of a single feature (if a set of features has been loaded then the attribute values for the first feature in the set of object ids are loaded only). The attributes can be accessed using either the indexer-style access with field name or index and via enumeration - foreach(var attrib in inspector)
- as the inspector implements a IEnumerator<Attribute> GetEnumerator()
method.
Subtype fields should be updated with the Inspector.ChangeSubtype(int code, bool propogate)
method. Set the propagation parameter to true to ensure domain values on other fields are given default values to match the new subtype value. The subtype field can be referenced by name, if known, or via the SubtypeAttribute property on the inspector.
In this example a set of features for a specified subtype are all loaded into an inspector. Their subtype is changed to a new subtype:
var status = await QueuedTask.Run(() =>
{
var fcdef = roads.GetFeatureClass().GetDefinition();
//equivalent to "inspector.SubtypeAttribute.FieldName"
var subtypeField = fcdef.GetSubtypeField();
int code = fcdef.GetSubtypes().First(s => s.GetName().Equals("Backroad")).GetCode();
int new_code = fcdef.GetSubtypes().First(s => s.GetName().Equals("Country road")).GetCode();
//Select features with the current subtype code "code"
var qf = new QueryFilter {WhereClause = string.Format("{0} = {1}", subtypeField, code)};
var select = roads.Select(qf);
//Load them
var inspector = new Inspector();
inspector.Load(roads, select.GetObjectIDs());
//Change subtype for all features in the set
//Note: "propogate" param is set to true.
inspector.ChangeSubtype(new_code, true);
var op = new EditOperation();
op.Name = "Change Subtype";
op.Modify(inspector);
op.Execute();
return new Tuple<bool, string>(op.IsSucceeded, op.ErrorMessage);
});
Feature templates are a central concept in the editing environment in ArcGIS Pro. Creating features using the editor relies on the use of feature templates.
Map authors create and manage feature templates with the Manage Templates pane, accessed from the Templates popup button in the lower right of the Features group on the Edit tab. The map author can modify the default attribute values and set the default construction tool used to create the new type of feature.
Every feature template is associated with a feature layer. When a layer is persisted, as a layer file or in a project, the feature templates are stored as part of the layer's definition.
Standalone tables are used to create and maintain non-spatial data in a database. Each such table can have a number of templates that facilitate the creation of records in that table. You can modify default attribute values in the table templates so that when you create a new record using those templates, corresponding default attributes are automatically filled in the new row/record. Table templates are stored in the table's definition.
The following example shows how to access feature templates and set the current template:
//Get selected layer in toc
var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
QueuedTask.Run(() =>
{
//Get the selected template in create features pane
var currTemplate = ArcGIS.Desktop.Editing.Templates.EditingTemplate.Current;
//Get all templates for a layer
var layerTemplates = featLayer.GetTemplates();
//Find a template on a layer by name
var resTemplate = featLayer.GetTemplate("Residential");
//Activate the default tool on a template and set the template as current
resTemplate.ActivateDefaultToolAsync();
});
The following example shows how to access table templates:
//Get the standalone table
var table = MapView.Active.Map.GetStandaloneTablesAsFlattenedList().FirstOrDefault();
QueuedTask.Run(() =>
{
//Get all templates for a table
var tableTemplates = table.GetTemplates();
//Find a table template by name
var ownerTemplate = table.GetTemplate("Owners");
});
Feature templates are automatically generated for editable layers when they are added to a map. Templates are not regenerated if the renderer changes, for example, changing from single symbol to unique value. New templates can be created for a layer or copied and altered from an existing template. Creating and modifying templates is done through the CIM classes. Since ArcGIS Pro 2.2, the CreateTemplate extension method facilitates template creation with a populated Inspector object. You can also easily assign the template name, description, tags, default tool and tool filter with the same call. Table templates are also created and modified the same way through CIM classes. Pro 2.9 introduces the CreateTemplate extension method for table templates that helps you create templates with default attribute values assigned to it.
The following example creates a new template for a layer from an existing template using the CIM:
//Get parcels layer
var featLayer = MapView.Active.Map.FindLayers("Parcels").First();
QueuedTask.Run(() =>
{
//Find a template on a layer by name
var resTemplate = featLayer.GetTemplate("Residential");
//Get CIM layer definition
var layerDef = featLayer.GetDefinition() as CIMFeatureLayer;
//Get all templates on this layer
var layerTemplates = layerDef.FeatureTemplates.ToList();
//Copy template to new temporary one
var resTempDef = resTemplate.GetDefinition() as CIMFeatureTemplate;
//Could also create a new one here
//var newTemplate = new CIMFeatureTemplate();
//Set template values
resTempDef.Name = "Residential copy";
resTempDef.Description = "This is the description for the copied template";
resTempDef.WriteTags(new[] { "Testertag" });
resTempDef.DefaultValues = new Dictionary<string, object>();
resTempDef.DefaultValues.Add("YEARBUILT", "1999");
//Add the new template to the layer template list
layerTemplates.Add(resTempDef);
//Set the layer definition templates from the list
layerDef.FeatureTemplates = layerTemplates.ToArray();
//Finally set the layer definition
featLayer.SetDefinition(layerDef);
});
The following example creates a new template for a table from an existing template using the CIM:
//Get owners table
var authoritiesTable = MapView.Active.Map.FindStandaloneTables("Authorized persons").First();
QueuedTask.Run(() =>
{
//Find a table template by name
var ownerTemplate = authoritiesTable.GetTemplate("Owners");
//Get CIM table definition
var tableDef = authoritiesTable.GetDefinition();
//Get all templates on this table
var tableTemplates = tableDef.RowTemplates.ToList();
//Copy template to new temporary one
var tableTempDef = ownerTemplate.GetDefinition();
//Set template values
tableTempDef.Name = "Owners template copy";
tableTempDef.Description = "New table template description";
tableTempDef.WriteTags(new[] { "Tag1" });
tableTempDef.DefaultValues = new Dictionary<string, object>();
tableTempDef.DefaultValues.Add("Authorization Level", "1");
//Add the new template to the table template list
tableTemplates.Add(tableTempDef);
//Set the table definition templates from the list
tableDef.RowTemplates = tableTemplates.ToArray();
//Finally set the table definition
authoritiesTable.SetDefinition(tableDef);
});
This example creates a new feature template using the extension method:
// Must be executed on the MCT - wrap in a QueuedTask.Run
// Load the schema
insp = new Inspector();
insp.LoadSchema(layer);
// Set up the fields
insp["FieldOne"] = value1;
insp["FieldTwo"] = value2;
// Set up tags
var tags = new[] { "tag1", "tag2" };
// Set up default tool - use daml-id rather than guid
string defaultTool = "esri_editing_SketchCircleLineTool";
// Create a new CIM template - new extension method
var newTemplate = layer.CreateTemplate("My new template", "sample description", insp, defaultTool, tags);
// You can create new table template using the new extension method the same way
//var newTableTemplate = table.CreateTemplate("New table template", "description", insp, tags: tags);
Templates can be removed from a layer or table by removing them from the list of templates on the corresponding layer or table definition. The definition is then set back on the layer/table.
The following example removes templates from a layer that matches a pattern:
QueuedTask.Run(() =>
{
//Get parcels layer
var featLayer = MapView.Active.Map.FindLayers("Parcels").First();
//Get CIM layer definition
var layerDef = featLayer.GetDefinition() as CIMFeatureLayer;
//Get all templates on this layer
var layerTemplates = layerDef.FeatureTemplates.ToList();
//Remove templates matching a pattern
layerTemplates.RemoveAll(t => t.Description.Contains("Commercial"));
//Set the templates and layer definition back on the layer
layerDef.FeatureTemplates = layerTemplates.ToArray();
featLayer.SetDefinition(layerDef);
});
Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.ActiveTemplateChangedEvent
to be notified when the active template is activated or deactivated.
The ActiveTemplateChangedEventArgs
event argument contains the incomming template and view (the template being activated and which view it is in), and the outgoing template and view (the template being deactivated and the view it is in). Note that the incoming and outgoing templates may be null when changing views that do not support templates such as the catalog, model builder and layout views.
The following example activates a specific tool on a template whenever that template is activated:
//Template Activation Event
ArcGIS.Desktop.Editing.Events.ActiveTemplateChangedEvent.Subscribe(OnActiveTemplateChanged);
private async void OnActiveTemplateChanged(ActiveTemplateChangedEventArgs obj)
{
//return if incoming template is null
if (obj.IncomingTemplate == null)
return;
//Activate two-point line tool for Freeway template in the Layers map
if (obj.IncomingTemplate.Name == "Freeway" && obj.IncomingMapView.Map.Name == "Layers")
await obj.IncomingTemplate.ActivateToolAsync("esri_editing_SketchTwoPointLineTool");
}
Annotation features differ from other geodatabase features in a few small but fundamental ways. It is important to keep these in mind when developing custom annotation editing tools. See the Annotation Editing Concepts document for specifics about editing annotation.
Dimension features are also a little different from standard geodatabase features. As with annotation editing it is important to keep these differences in mind when developing custom dimension editing tools. See the Dimension Editing Concepts document for specifics about editing dimensions.
On the UI, you can toggle snapping, set the application snap modes (point, edge, vertex, and so on) and other environment options such as snapping tolerance through the snapping drop-down menu and settings dialog from the Snapping group on the Edit tab. Additionally, you can control what layers to snap to through the List by Snapping view in the Contents pane. The snapping API reflects these UI options.
The following example enables snapping on the UI and sets some snapping environment options:
//Using ArcGIS.Desktop.Mapping
//enable snapping
Snapping.IsEnabled = true;
//Enable a snap mode, others are not changed.
Snapping.SetSnapMode(SnapMode.Point,true);
//Set multiple snap modes exclusively. All others will be disabled.
Snapping.SetSnapModes(SnapMode.Edge, SnapMode.Point);
//Set snapping options via get/set options
var snapOptions = Snapping.GetOptions(myMap);
snapOptions.SnapToSketchEnabled = true;
snapOptions.XYTolerance = 100;
snapOptions.ZToleranceEnabled = true;
snapOptions.ZTolerance = 0.6;
Snapping.SetOptions(myMap,snapOptions);
The following example shows setting some general snapping options and snapping availability for all polyline layers in the current map:
internal static async Task ConfigureSnappingAsync() {
//General Snapping
Snapping.IsEnabled = true;
Snapping.SetSnapMode(SnapMode.Edge, true);
Snapping.SetSnapMode(SnapMode.End, true);
Snapping.SetSnapMode(SnapMode.Intersection, true);
//Snapping on any line Feature Layers that are not currently snappable
var flayers = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().Where(
l => l.ShapeType == esriGeometryType.esriGeometryPolyline && !l.IsSnappable).ToList();
if (flayers.Count() > 0) {
await QueuedTask.Run(() => {
foreach (var fl in flayers) {
// use an extension method
// (must be called inside QueuedTask)
fl.SetSnappable(true);
// or use GetDefinition, SetDefinition to access the CIM directly
// (must be called inside QueuedTask)
//var layerDef = fl.GetDefinition() as CIMGeoFeatureLayerBase;
//layerDef.Snappable = true;
//fl.SetDefinition(layerDef);
}
});
}
}
Finer snapping control can be achieved using layer snap modes. For example, to only snap to the vertices of a layer of interest and not the vertices of others, the application vertex Snap Mode and the layer of interest vertex Snap Mode should be set True while the other layer’s vertex Snap Modes would be set False. Note that toggling an individual layer’s snap mode will only have an effect if the corresponding application Snap Mode is True.
Snap modes can be set for each layer via the Snapping.SetLayerSnapModes
methods while the current state of a layer’s snap modes is obtained via the Snapping.GetLayerSnapModes
method.
There is no UI in ArcGIS Pro for viewing layer snap modes and these settings do not persist.
The following examples illustrates the layer snap modes.
// configure by layer
// find the state of the snapModes for the layer
var lsm = ArcGIS.Desktop.Mapping.Snapping.GetLayerSnapModes(layer);
bool vertexOn = lsm.Vertex;
bool edgeOn = lsm.Edge;
bool endOn = lsm.End;
// or use
vertexOn = lsm.GetSnapMode(SnapMode.Vertex);
edgeOn = lsm.GetSnapMode(SnapMode.Edge);
endOn = lsm.GetSnapMode(SnapMode.End);
// update a few snapModes
// turn Vertex off
lsm.SetSnapMode(SnapMode.Vertex, false);
// intersections on
lsm.SetSnapMode(SnapMode.Intersection, true);
// and set back to the layer
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, lsm);
// assign a single snap mode at once
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, SnapMode.Vertex, false);
// turn ALL snapModes on
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, true);
// turn ALL snapModes off
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, false);
// configure for a set of layers
// set Vertex, edge, end on for a set of layers, other snapModes false
var vee = new LayerSnapModes(false);
vee.Vertex = true;
vee.Edge = true;
vee.End = true;
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layerList, vee);
// turn intersection snapping on without changing any of the other snapModes
var dictLSM = ArcGIS.Desktop.Mapping.Snapping.GetLayerSnapModes(layerList);
foreach (var layer in dictLSM.Keys)
{
var lsm = dictLSM[layer];
lsm.Intersection = true;
}
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(dictLSM);
// set all snapModes off for a list of layers
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layerList, false);
Here is a combined example illustrating how to snap to only the vertices of a particular layer. All other layer snapmodes are turned off.
// snapping must be on
ArcGIS.Desktop.Mapping.Snapping.IsEnabled = true;
// turn all application snapModes off
ArcGIS.Desktop.Mapping.Snapping.SetSnapModes();
// set application snapMode vertex on
ArcGIS.Desktop.Mapping.Snapping.SetSnapMode(SnapMode.Vertex, true);
// ensure layer snapping is on
await QueuedTask.Run(() =>
{
fLayer.SetSnappable(true);
});
// set vertex snapping only
var vertexOnly = new LayerSnapModes(false);
vertexOnly.Vertex = true;
// set vertex only for the specific layer, clearing all others
var dict = new Dictionary<Layer, LayerSnapModes>();
dict.Add(fLayer, vertexOnly);
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(dict, true); // true = reset other layers
To use the snapping environment in a custom MapTool, set the tool's UseSnapping property to true (usually in the constructor). The snapping environment can be changed at any time during the sketch as required.
internal class MyCustomTool1 : MapTool {
public MyCustomTool1() {
IsSketchTool = true;
UseSnapping = true; //Use snapping
SketchType = SketchGeometryType.Line;
You can determine what features the sketch has snapped to in your custom tools through the map tool property SnappingResults
. This returns a readonly list of SnapResult
for each vertex in the current sketch. For those vertices that have snapped to a feature or the grid, SnapResult contains the layer, feature OID, SnapType (vertex, edge, end etc) and the snap location. For those vertices that have not snapped in the sketch, SnapResult contains null for the layer, -1 for the feature OID and the SnapType type is None.
If the sketch has snapped to the editing grid, SnapResult contains null for the layer, the SnapType is Grid, the OID denotes the grid feature: 0 for a grid line, 1 for a grid intersection and 2 for grid inference.
protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
//assume a custom point map tool
//get the feature the point sketch snapped to if any
var snapResultPoint = SnappingResults[0];
if (snapResultPoint.Layer != null)
{
var snappedLayer = snapResultPoint.Layer;
var snappedOID = snapResultPoint.OID;
var snapType = snapResultPoint.SnapType;
if (snaptype == SnapType.Grid)
{
// we snapped to the grid
var gridLine = (snappedOID == 0);
var gridIntersection = (snappedOID == 1);
var gridInference = (snappedOID == 2);
}
else if (snapType != SnapType.None)
{
// we snapped to a feature (snappedLayer, snappedOID)
}
}
By way of recap, long transactions support an underlying edit session whereas short transactions do not*. An edit session supports save, discard, undo, and redo. Both long and short transactions do support canceling edits in-progress (though to varying degrees depending on the underlying workspace type). By default, any given edit operation allows datasets with short and long transaction semantics to be mixed within the same execute of an edit operation. The transaction semantics of an EditOperation can be inspected at runtime via its EditOperation.EditOperationType
property, topic 9568. It has three possible values:
- Null. This is the default. Mixing short and long semantics is ok.
- Long. No mixing. All datasets in the edit operation must support long transaction semantics
- Short. No mixing. All datasets in the edit operation must support short transaction semantics
Refer to the EditOperationType enum.
To restrict an edit operation to just datasets that are long or short, then ~set~ the EditOperation.EditOperationType
to EditOperationType.Long
or EditOperationType.Short
respectively. If the EditOperation.EditOperationType
is set then adding a dataset to the edit operation with the wrong transation semantic will cause the edit operation execute to fail.
Mixing datasets with long and short transaction semantics (the default behavior) can lead to some inconsistencies with the Pro UI experience for Undo/Redo and Save/Discard. Undo/Redo and Save/Discard only apply to workspaces with edit sessions (long transactions). So if edits were mixed, only the long transactions (contained in an edit session) can be saved, discarded, undone, or redone. If a user clicks "Undo" or "Save" on the Pro UI, for example, any "Direct" edits (short transactions) will be unaffected. To undo a direct edit another edit must be performed to reverse it. Similar for a redo. Direct edits do not have an edit session and are immediately committed on completion of the edit operation execute.
If you are mixing versioned and non-versioned data from the same datastore (e.g. some datasets are registered as versioned in the datastore whereas some datasets are not) then all edits against the versioned data (in that datastore) must be saved or discarded before executing any operations against non-versioned data (from that same datastore) unless the non-versioned datasets are edited first. Said another way, when mixing versioned and non-versioned data from the same datastore, the non-versioned data must always be edited first. Attempting to execute edit operations against non-versioned data when there are already pending edits for versioned data in the same datastore will throw an exception.
Note: In ArcGIS Pro 2.5 non-versioned feature services support an edit session with undo and redo capabilities. However, the EditOperationType
still requires Short
(rather than Long
).
Typically, in most editing scenarios, the application developer already knows upfront the schemas and characteristics of his/her TOC content (that the given application is editing). However, if the EditOperationType does need to be discovered at runtime, it can be determined by analyzing the underlying ArcGIS.Core.Data.GeodatabaseType
, topic 6821, and ArcGIS.Core.Data.RegistrationType
, topic 6822 of the layer or standalone table's dataset (when dealing with branch versioned data, differentiating between the default and a named version is also required). Consult the DatasetCompatibility sample for an example implementation.
Use the following tables to reference the participating EditOperationType for a given dataset and its characteristics with respect to Cancel Edit, Undo/Redo, Save/Discard:
EditOperationType by dataset at ArcGIS Pro 2.4 and earlier
Dataset | GeodatabaseType | RegistrationType | Version | EditOperationType |
---|---|---|---|---|
Shape file | FileSystem | N/A | N/A | LONG |
File GDB | LocalDatabase | N/A | N/A | LONG |
Enterprise (Versioned) | RemoteDatabase | Versioned | N/A | LONG |
Enterprise (Direct) | RemoteDatabase | NonVersioned | N/A | SHORT |
Feature Service (Hosted) | Service | NonVersioned | N/A | SHORT |
Feature Service (Standard) | Service | NonVersioned | N/A | SHORT |
Feature Service (Branch) | Service | Versioned | Default | SHORT |
Feature Service (Branch) | Service | Versioned | Named | LONG |
EditOperationType by dataset at ArcGIS Pro 2.5+
Dataset | GeodatabaseType | RegistrationType | Version | EditOperationType |
---|---|---|---|---|
Shape file | FileSystem | N/A | N/A | LONG |
File GDB | LocalDatabase | N/A | N/A | LONG |
Mobile (.geodatabase) | LocalDatabase | N/A | N/A | LONG |
Memory GDB | Memory | N/A | N/A | LONG |
Enterprise (Versioned) | RemoteDatabase | Versioned | N/A | LONG |
Enterprise (Direct) | RemoteDatabase | NonVersioned | N/A | SHORT |
Feature Service (Hosted)* | Service | NonVersioned | N/A | LONG |
Feature Service (Standard)* | Service | NonVersioned | N/A | LONG |
Feature Service (Branch) | Service | Versioned | Default | SHORT |
Feature Service (Branch) | Service | Versioned | Named | LONG |
*In ArcGIS Pro 2.5 non-versioned feature services do support an edit session with undo and redo capabilities. However, the EditOperationType
requires SHORT
(rather than LONG
). This is a known issue.
Characteristics by dataset by participating EditOperationType at ArGIS Pro 2.4 and earlier
Dataset | Cancel Edit | Undo/Redo | Save/Discard | EditOperationType |
---|---|---|---|---|
Shape file | Yes | Yes | Yes | LONG |
File GDB | Yes | Yes | Yes | LONG |
Enterprise (Versioned) | Yes | Yes | Yes | LONG |
Enterprise (Direct) | Yes | No | No | SHORT |
Feature Service (Hosted) | Yes* | No | No | SHORT |
Feature Service (Standard) | Yes* | No | No | SHORT |
Feature Service (Branch, Default) | Yes | No | No | SHORT |
Feature Service (Branch, Named) | Yes | Yes | Yes | LONG |
*Prior to 2.3, there is no Cancel Edit for feature creates. At 2.3, cancelling a create on a hosted or standard feature service generates another edit event (essentially, the "cancel" performs a delete).
Characteristics by dataset by participating EditOperationType at ArGIS Pro 2.5+
Dataset | Cancel Edit | Undo/Redo | Save/Discard | EditOperationType |
---|---|---|---|---|
Shape file | Yes | Yes | Yes | LONG |
File GDB | Yes | Yes | Yes | LONG |
Mobile (.geodatabase) | Yes | Yes | Yes | LONG |
Memory GDB | Yes | Yes | Yes | LONG |
Enterprise (Versioned) | Yes | Yes | Yes | LONG |
Enterprise (Direct) | Yes | No | No | SHORT |
Feature Service (Hosted)* | Yes | Yes | Yes | LONG* |
Feature Service (Standard)* | Yes | Yes | Yes | LONG* |
Feature Service (Branch, Default) | Yes | No | No | SHORT |
Feature Service (Branch, Named) | Yes | Yes | Yes | LONG |
*In ArcGIS Pro 2.5 non-versioned feature services do support an edit session with undo and redo capabilities. However, the EditOperationType
still requires SHORT
(rather than LONG
). This is a known issue.
Notes
Memory GDB refers to a memory geodatabase created with a MemoryConectionProperties via the Geodatabase class or the keyword "memory" via GP tools.
For SQLite, if the extension is “.sqlite” or “.gpkg” (geopackage), then the SQLite instance will exhibit pure database behavior, i.e., it doesn’t support geodatabase functionality. SQLite connections are created via the SQLiteConnectionPath as the connector to a Database constructor. Because the Database class is not derived from Geodatabase
, it does not support a GetGeodatabaseType()
method.
PluginDatastore
is a new type introduced at Pro 2.3. Plugin data stores are read-only. They do not have a corresponding GeodatabaseType or EditOperationType.
Unlike in ArcObjects where an edit session is explicitly started on the underlying datastore, in Pro, the Editor manages all active edit sessions for you. Edit sessions are maintained for all datasets with LONG EditOperationType semantics (see above). The first edit performed (on a given dataset) will start an edit session (on that datastore). The edit session will be maintained until the edits are saved or discarded. Once an edit session is closed, the OperationManager's Undo/Redo stack is cleared.
For datasets with SHORT EditOperationType semantics, each edit operation is treated as an individual session. If the first edit operation is performed on non-versioned or non-undoable data, the edit proceeds and is committed on success. The edit will not be placed on the undo stack and calling Save or Discard is, essentially, a no-op.
For those datasets participating in an edit session, edits can be saved or discarded using methods available on the Project instance object. These methods are awaitable and do not block the UI:
- Save edits:
await Project.Current.SaveEditsAsync();
SaveEditsAsync - Discard Edits:
await Project.Current.DiscardEditsAsync();
DiscardEditsAsync - Has Edits:
Project.Current.HasEdits
HasEdits
Edits can be undone or redone via the OperationManager
class. To access the operation manager containing your edits, use the Map.OperationManager
property on the map (or scene) that contains the data you are editing. For example:
//Undo all edits on the current map
var operationMgr = MapView.Active.Map.OperationManager;
while (operationMgr.CanUndo)
await operationMgr.UndoAsync();
//Redo the last edit
if (operationMgr.CanRedo)
await operationMgr.RedoAsync();
MapViews with the same underlying map or scene always share the operation manager. Access MapView.Active.Map.OperationManager
to get the operation manager instance associated with the currently active view. This is important if your edit code is executing as part of a MapTool or other component that always works on data in whichever map or scene is active.
Project.Current.HasEdits specifies if the Project has unsaved edits. If the project has more than one datasource, HasEdits will be true if there are edits to data in any of the datasources (participating in an edit session). The datastores that have pending edits can be retrieved from the current project via Project.Current.EditedDatastores and, additionally, any datastore can be queried individually to determine if it has pending edits via its HasEdits property. Datastores with SHORT semantics will always return false (for HasEdits) because their edits are never pending and are saved immediately after each edit has completed. For file geodatabase datastores, within the first edit operation, the HasEdits method will return false until the edit operation is complete.
The editing assembly provides events to listen for changes to objects at row level and for the progress and execution of an edit operation. The specific events in the ArcGIS.Desktop.Editing.Events
namespace are as follows:
- EditStartedEvent—Raised when an EditOperation is starting execution.
- EditCompletingEvent—Raised when an EditOperation is completing execution.
- EditCompletedEvent—Raised when an EditOperation execution is completed successfully.
- RowCreatedEvent—Raised when a row is created (Table.CreateRow()/FeatureClass.CreateRow())
- RowChangedEvent—Raised when a row is modified (Row.Store()/Feature.Store())
- RowDeletedEvent—Raised when a row is deleted (Row.Delete()/Feature.Delete())
Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.EditStartedEvent
to be notified when any EditOperation is starting to execute. The EditStartedEventArgs
event argument passes the currently executing EditOperation
. This event can not be canceled.
Additional EditOperation's should not be created within this event, nor should the passed EditOperation be executed.
Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.EditCompletingEvent
to be notified when any EditOperation is completing execution. The EditCompletingEventArgs
event argument passes the currently executing EditOperation
for which additional edits can be queued for execution. This event, and by association the EditOperation, can be canceled via the EditCompletingEventArgs.CancelEdit
method.
Additional EditOperation's should not be created within this event, nor should the passed EditOperation be executed.
Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.EditCompletedEvent
to be notified when:
- Any create, modify, and delete has completed (e.g. after an
editOperation.Execute
) - Any Undo or Redo has completed.
- Any Save or Discard has completed (and, hence, closing any current edit sessions)
- Any Reconcile or Post has completed.
- Any Change Version has completed.
In the case where features are created, modified, or deleted (whether directly or indirectly via an undo or redo), the EditCompletedEventArgs
event argument contains lists of all creates, modified, and deletes by object id per MapMember (layer or standalone table). Note: by default the creates, modifies, and deletes are null. There is no cancelation possible via the EditCompletedEvent.
EditCompletedEvent is a global event. It fires after any edit has completed on any dataset in any map within the current project. Note: EditCompletedEvent only fires once after the conclusion of all edits included within the edit operation execution or undo/redo.
private SubscriptionToken _token;
//elsewhere, register for the event
_token = EditCompletedEvent.Subscribe(OnEditComplete);
//unregister calling unsubscribe
if (_token != null) {
EditCompletedEvent.Subscribe(_token);
_token = null;
}
//Event handler
protected Task OnEditComplete(EditCompletedEventArgs args) {
//Creates, Modifies, Deletes are all 'IReadOnlyDictionary<MapMember, IReadOnlyCollection<long>>'
//Check for null
if (args.Creates != null) {
foreach (var kvp in args.Creates) {
MapMember layerOrTable = kvp.Key;
IReadOnlyCollection<long> oids = kvp.Value;
// TODO, handle creates
}
}
... etc ...
return Task.FromResult(0);
}
//Elsewhere, perform an edit
editOp.Create(this.CurrentTemplate, geometry);
// Execute the operation
editOp.ExecuteAsync(); //EditCompletedEvent fires when
//ExecuteAsync has finished
In ArcGIS Pro, the row events are public, but the subscription is on a per-dataset basis. To listen to RowCreatedEvent
, RowChangedEvent
, and RowDeletedEvent
, you subscribe to a public event by calling the static Subscribe on the event and passing in the Table or FeatureClass for which you want to receive row level events. Use the row events to:
- Make changes to the row being edited (changes become part of the ongoing transaction)
- Validate row attributes that have been changed
- Cancel row transactions* (e.g. if they fail validation)
- Add custom logging/audit trail functionality
*Prior to 2.3, feature creates on Hosted and Standard Feature Services cannot be cancelled.
*Short transaction edits already committed within the ongoing transaction will not be undone.
The row events are published during the execution of the edit operation (whereas the EditCompletedEvent
is published after the entire edit operation has completed). For every individual create or edit, for every dataset for which you have registered, a corresponding RowEvent is fired. For example, if you create 3 features in an edit operation and you are registered for RowCreate events for the corresponding dataset(s), then you will receive 3 RowCreatedEvent callbacks - one for each create. Similarly for modifies or deletes. Each individual modify and delete results in a RowEvent being fired per edit per feature.
Note that RowEvent callbacks are always called on the QueuedTask
so there is no need to wrap your code within a QueuedTask.Run
lambda.
The following snippet subscribes to the RowCreatedEvent, RowChangedEvent for the 'Addresses' layer and the RowCreatedEvent for the 'Buildings' layer
var map = MapView.Active.Map;
var addrLayer = map.GetLayersAsFlattenedList().FirstOrDefault(l => l.Name == "Addresses") as FeatureLayer;
if (addrLayer == null)
return;
var bldgLayer = map.GetLayersAsFlattenedList().FirstOrDefault(l => l.Name == "Buildings") as FeatureLayer;
if (bldgLayer == null)
return;
QueuedTask.Run(() =>
{
ArcGIS.Desktop.Editing.Events.RowCreatedEvent.Subscribe(RowCreated, addrLayer.GetTable());
ArcGIS.Desktop.Editing.Events.RowChangedEvent.Subscribe(RowChanged, addrLayer.GetTable());
ArcGIS.Desktop.Editing.Events.RowCreatedEvent.Subscribe(RowCreated, bldgLayer.GetTable());
});
A RowChangedEventArgs is passed in as an argument to your RowEvent handler. There are two important properties on the RowChangedEventArgs object; the Row property that is the row being created, modified, or deleted, and the Operation property which is the EditOperation object that is currently being executed. You can query the Row using Row.HasValueChanged
to determine which values have changed.
Because the events are published during the execution of the edit operation, you can cancel the ongoing transaction by calling the RowChangedEventArgs CancelEdit method. Cancelling within a row event will cancel the entire transaction (and edit operation Execute will return false) rolling back all undo-able edits to that point.
In the following example, any changes to the "POLICE_DISTRICT" field in "crimes_fc" are validated. If a "POLICE_DISTRICT" value fails validation, the edit operation is cancelled.
RowChangedEvent.Subscribe((rc) => {
//Validate any change to “police district”
if (rc.Row.HasValueChanged(rc.Row.FindField("POLICE_DISTRICT"))) {
if (FailsValidation(rc.Row["POLICE_DISTRICT"])) {
//Cancel edits with invalid “police district” values
rc.CancelEdit($"Police district {rc.Row["POLICE_DISTRICT"]} is invalid");
}
}
}, crimes_fc);
In addition to validating field values in modified or created features, you may wish to make additional edits by creating new features or modifying other rows in your RowEvent callbacks. Do not use a new edit operation object.
The following snippets show how to make further modifications to an edited row and add a record to an audit table after subscribing to the RowChangedEvent.
The first uses the ArcGIS.Core.Data
API to make additional edits and is the required pattern if you are working with ArcGIS prior to version 2.4.
private Guid _currentRowChangedGuid = Guid.Empty;
private void RowChanged(RowChangedEventArgs args)
{
try
{
// prevent re-entrance
if (_currentRowChangedGuid == args.Guid)
return;
var row = args.Row;
row["Description"] = description;
// calling store will also result in a RowChanged event if any
// attribute columns have changed.
// I need to keep track of args.Guid to prevent re-entrance and a
// recursive situation (see check above)
_currentRowChangedGuid = args.Guid;
row.Store();
// now add a record to the history table
// use the geodatabase API
if (history != null)
{
using (var table = row.GetTable())
using (var hist_table = history.GetTable())
{
using (var rowBuff = hist_table.CreateRowBuffer())
{
rowBuff["TABLENAME"] = table.GetName();
rowBuff["DESCRIPTION"] = description;
rowBuff["WHO"] = Environment.UserName;
rowBuff["WHAT"] = args.EditType.ToString();
hist_table.CreateRow(rowBuff);
}
}
}
// clear the cached row guid
_currentRowChangedGuid = Guid.Empty;
}
}
At 2.4 or later, you can make use of the running EditOperation
passed via RowChangedEventArgs.Operation
. Any additional edits are included in the current operation and appear as one entry in the undo/redo stack.
private Guid _currentRowChangedGuid = Guid.Empty;
private void RowChanged(RowChangedEventArgs args)
{
try
{
// prevent re-entrance
if (_currentRowChangedGuid == args.Guid)
return;
var row = args.Row;
row["Description"] = description;
// calling store will also result in a RowChanged event if any
// attribute columns have changed.
// I need to keep track of args.Guid to prevent re-entrance and a
// recursive situation (see check above)
_currentRowChangedGuid = args.Guid;
row.Store();
// now add a record to the history table
// 'history' is in my map and has been retrieved separately
// use the running EditOperation to add the record
if (history != null)
{
// get the current EditOperation
var parentEditOp = obj.Operation;
// build attributes
using (var table = row.GetTable())
{
var atts = new Dictionary<string, object>();
atts.Add("TABLENAME", table.GetName());
atts.Add("DESCRIPTION", description);
atts.Add("WHO", Environment.UserName);
atts.Add("WHAT", args.EditType.ToString());
parentEditOp.Create(history, atts);
}
}
// clear the cached row guid
_currentRowChangedGuid = Guid.Empty;
}
}
Note if the history table does not exist in your current map, then you will need to use a combination of the ArcGIS.Core.Data
API and the running EditOperation to accomplish the logging.
private Guid _currentRowChangedGuid = Guid.Empty;
private void RowChanged(RowChangedEventArgs args)
{
try
{
// prevent re-entrance
if (_currentRowChangedGuid == args.Guid)
return;
var row = args.Row;
row["Description"] = description;
// calling store will also result in a RowChanged event if any
// attribute columns have changed.
// I need to keep track of args.Guid to prevent re-entrance and a
// recursive situation (see check above)
_currentRowChangedGuid = args.Guid;
row.Store();
// now add a record to the history table
// use the geodatabase API to retrieve the 'historyTable'
// use the running EditOperation to add the record
using (var geodatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(@"D:\Data\MyHistoryGDB.gdb"))))
{
using (var historyTable = geodatabase.OpenDataset<Table>("History"))
{
// get the current EditOperation
var parentEditOp = obj.Operation;
// build attributes
using (var table = row.GetTable())
{
var atts = new Dictionary<string, object>();
atts.Add("TABLENAME", table.GetName());
atts.Add("DESCRIPTION", description);
atts.Add("WHO", Environment.UserName);
atts.Add("WHAT", args.EditType.ToString());
parentEditOp.Create(historyTable, atts);
}
}
}
// clear the cached row guid
_currentRowChangedGuid = Guid.Empty;
}
}
Here's another example of how to use the current operation within the row events. In this scenario we wish to add an attachment to a newly created feature. Previously we discussed this example as requiring you to chain edit operations together in order to have the two edits participate in a single undo/redo action; because the AddAttachment method requires the feature to already be created. The RowChangedEventArgs.Operation property provides us with an alternative implementation if we use the RowCreated event.
private void OnRowCreatedEvent(RowChangedEventArgs obj)
{
// get the row and the current operation
var row = args.Row;
var parentEditOp = obj.Operation;
// retrieve the row objectid
var sOid = row["objectid"].ToString();
if (long.TryParse(sOid, out long oid))
// add the attachment
parentEditOp.AddAttachment(layer, oid, @"C:\Temp\Painting.jpg");
}
There are a couple of important things to keep in mind when using the row events.
Firstly, any modifications performed within the RowEvent handlers can also cause cascaded events to be generated. This can occur if you edit rows or features other than the row participating in the current row event (passed to you via the RowChangedEventArg.Row
property). A RowChangedEvent
will be raised immediately whenever Row.Store() or Feature.Store() is executed (the same is also true for RowCreatedEvent
and RowDeletedEvent
in response to a row create or delete). Make sure you have an exit condition to avoid infinite recursion. For example, if you are changing a feature attribute within a RowCreatedEvent or RowChangedEvent, on a different row then consider tracking that row/feature's object id to ignore the corresponding RowChangedEvent your attribute change will generate.
Additionally, do not dispose the row obtained from the RowChangedEventArgs. The same row can be used for calling more than one event handler.
Finally with the new RowChangedEventArgs.Operation property, note that you cannot chain or execute the running operation within the events. Attempting to do so will cause exceptions to be thrown.
As of 2.7, the SDK supports several events for notifications of edit sketch changes, including:
Note: These events and event argument classes live in the ArcGIS.Desktop.Mapping.Events namespace (within the ArcGIS.Desktop.Editing.dll). The following edit tools fire these sketch events:
- All construction tools (except for annotation and dimensions)
- Align Features
- Reshape and Edit Vertices.
- 3rd party (custom) tools*
Note that these events always fire on the MCT so running subscription code within a QueuedTask.Run delegate is not necessary. These events are provided to allow custom logic, that is not associated with a (sketch|edit) tool, to receive notifications of changes to the sketch geometry (custom sketch and construction tools should continue to use the built in method overloads to handle sketch changes rather than subscribing to these events - i.e. override OnSketchModifiedAsync, OnSketchCancelledAsync, and OnSketchCompleteAsync in the usual way).
*Custom tools that want to publish sketch events should opt-in by setting their FireSketchEvents
property to true. The default is false (no sketch events published). Custom tools can change their FireSketchEvents
property at any time during their lifecycle, not just when the tool is instantiated. The FireSketchEvents
setting has no effect on the sketch method overrides. Custom tools always receive callbacks on the sketch method overrides when they are the active tool.
SketchModifiedEvent
The most commonly used event is the SketchModifiedEvent. This event fires whenever the geometry of the sketch is changed such as when adding, removing or moving a vertex. You may want to listen to this event when performing validation logic (on the sketch geometry). SketchModifiedEventArgs provides subscribers with the MapView, the previous and current sketch, and the SketchOperationType which can be used to determine the nature of the sketch modification. The Boolean IsUndo property indicates if this event occurred as a result of an undo action. Note that in a sketch undo operation, IsUndo = true, and SketchOperationType will report the type of sketch operation that was undone.
ArcGIS.Desktop.Mapping.Events.SketchModifiedEvent.Subscribe((args) => {
if (args.IsUndo)//not interested in undos
return;
if (args?.CurrentSketch?.IsEmpty ?? true)//not interested in null or empty sketch
return;
//interested in polylines only
//Examine the current (last) vertex in the line sketch
if (args.CurrentSketch is Polyline polyLine) {
var lastSketchPoint = polyLine.Points.Last();
//TODO use lastSketchPoint
...
}
});
SketchCancelledEvent
The SketchCancelledEvent fires whenever the sketch is cancelled. The accompanying SketchCancelledEventArgs contains the map view the sketch cancellation originated on.
ArcGIS.Desktop.Mapping.Events.SketchCancelledEvent.Subscribe((args) => {
var mapView = args.MapView;
//TODO, handle sketch cancelled
...
});
BeforeSketchCompletedEvent
The BeforeSketchCompletedEvent fires when a sketch is finished but before the active tool receives the completed sketch via its OnSketchCompleteAsync overload and before the SketchCompletedEvent fires. Subscribers to BeforeSketchCompletedEvent, therefore, have an opportunity to modify the sketch prior to the active tool consuming it (e.g. within an edit or feature creation). A typical workflow might be for logic external to the tool ensuring that the Z or M values on the sketch are correctly set prior to it being passed to (the active tool and subsequent) edit operations. The completed sketch is passed in to subscribers via the BeforeSketchCompletedEventArgs.Sketch property and can be changed by setting the altered geometry back via BeforeSketchCompletedEventArgs.SetSketchGeometry(updated_sketch). Attempts to pass a different sketch geometry type to BeforeSketchCompletedEventArgs.SetSketchGeometry than the current SketchType
(eg swap a polygon for a polyline or vice versa) will be ignored*.
If there are multiple subscribers to the event, each subscriber in turn, gets the latest modified sketch geometry consisting of all edits made to the sketch (via SetSketchGeometry calls) thus far. Once the last subscriber is complete, the active tool receives the sketch, comprising any changes made by BeforeSketchCompletedEvent subscribers, via OnSketchCompleteAsync as usual.
ArcGIS.Desktop.Mapping.Events.BeforeSketchCompletedEvent.Subscribe((args) => {
//Sketch can be null depending on preceding BeforeSketchCompletedEvent subscribers
if (args.Sketch?.IsEmpty ?? true)
return;
//assign Zs from default surface
var result = args.MapView.Map.GetZsFromSurfaceAsync(args.Sketch).Result;
args.SetSketchGeometry(result.Geometry);
return Task.CompletedTask;
});
*Null can be passed in as the geometry to SetSketchGeometry. This will "null" the sketch for all downstream listeners and active tool.
SketchCompletedEvent
The SketchCompletedEvent fires after the sketch has been completed ("finished") and after the active tool's OnSketchCompleteAsync callback is complete. The accompanying SketchCompletedEventArgs contains the map view the sketch completion originated on as well as the finished sketch in the SketchCompletedEventArgs.Sketch property. This is the same sketch geometry as was received by the active tool.
ArcGIS.Desktop.Mapping.Events.SketchCompletedEvent.Subscribe((args) => {
var mapView = args.MapView;
//Sketch can be null depending on BeforeSketchCompletedEvent listeners
if (args.Sketch?.IsEmpty ?? true)
return;
var sketch_geom = args.Sketch;
//TODO, use the completed sketch
...
});
The Geometry control provides a UI for viewing vertices.
Use it in GeometryMode.Geometry
to display the vertices of a geometry. It can be a geometry that is created on-the-fly or a geometry of an existing feature. If the geometry is Z-aware or M-aware the control will display the Z and/or M values. In Geometry mode the control acts the same as the Geometry tab on the Attributes pane. Bind the geometry to be shown to the Geometry property of the control.
<UserControl x:Class="Example.GeometryViewDockpane"
... xmlns:editControls="clr-namespace:ArcGIS.Desktop.Editing.Controls;assembly=ArcGIS.Desktop.Editing"
...>
<Grid>
<editControls:GeometryControl GeometryMode="Geometry" Geometry="{Binding Geometry}" />
</Grid>
The Geometry property in the associated (dockpane) view model is defined as:
private Geometry _geometry;
public Geometry Geometry
{
get => _geometry;
set => SetProperty(ref _geometry, value);
}
In GeometryMode.Sketch
, the control responds to sketching on the map view allowing you to view and modify the vertices of the current sketch. If the SketchLayer property is set; the properties of the layer's feature class determine whether the GeometryControl displays Z and/or M values as you sketch. If no SketchLayer is set, the GeometryControl only displays X,Y coordinates. When the sketch is finished or cancelled, the control is cleared.
<UserControl x:Class="Example.GeometryViewDockpane"
... xmlns:editControls="clr-namespace:ArcGIS.Desktop.Editing.Controls;assembly=ArcGIS.Desktop.Editing"
...>
<Grid>
<editControls:GeometryControl GeometryMode="Sketch" SketchLayer="{Binding SketchLayer}"/>
</Grid>
The SketchLayer property in the associated (dockpane) view model is defined as:
private BasicFeatureLayer _sketchLayer;
public BasicFeatureLayer SketchLayer
{
get => _sketchLayer;
set => SetProperty(ref _sketchLayer, value);
}
A sample using the Geometry control can be found at GeometryControl.
The MapTool sketch feedback consists of vertices and segments. A vertex can be fixed or it can be the active or current vertex. The default symbology for a fixed vertex is a hollow green square; whereas the default symbology for the current vertex is a hollow red square. Similarly segments are either fixed or the active / current segment. There is no difference in the symbology between fixed or current segments; all sketch segments have the default symbology of a black dash-dot line; commonly referred to as the ant-trail. All vertices and segments whether they are fixed or current comprise the sketch feedback.
Within a custom MapTool, you can customize the sketch symbol using the SketchSymbol
property. Typically you would set this in the OnToolActivateAsync method. Alternatively you could use the OnSketchModifiedAsync method if you wanted to make the modifications more dynamic (for example changing the sketch symbol's color based on the number of vertices in the sketch).
Here's a code snippet for setting the SketchSymbol property in OnToolActivateAsync
private CIMLineSymbol _lineSymbol = null;
protected override async Task OnToolActivateAsync(bool hasMapViewChanged)
{
if (_lineSymbol == null)
{
_lineSymbol = await Module1.CreateLineSymbolAsync();
}
this.SketchSymbol = _lineSymbol.MakeSymbolReference();
}
And the result. Note that you can still see the green and red vertex squares along with the dash-dot ant-trail line of the sketch feedback overlaying the sketch symbol.
Starting with Pro 2.9, you can also change the symbology of the vertices and segments of the sketch feedback in your custom MapTool. There are 4 different types of vertices - unselected vertex, selected vertex, unselected current vertex, and selected current vertex. For the purposes of sketching all vertices are unselected. Selected vertices are only visible within the Edit Vertices tool. As previously mentioned the default vertex symbology whilst sketching is the green hollow square (unselected vertex) and the red hollow square (unselected current vertex). The default symbology for vertices within the Edit Vertices tool is a green filled square (selected vertex) and a red filled square (selected current vertex). The images below illustrate this; the first shows the unselected vertex symbology whilst sketching; the lower images show the selected vertex symbology within Edit Vertices.
You can modify the vertex and segment symbology of the sketch feedback using the following new methods:
- GetSketchVertexSymbolOptions - Gets the symbol for a vertex while sketching.
- SetSketchVertexSymbolOptions- Sets the symbol for a vertex while sketching.
- GetSketchSegmentSymbolOptions- Gets the default segment sketching symbol information.
- SetSketchSegmentSymbolOptions- Sets the segment sketching symbol information.
- ResetSketchVertexSymbolOptions- Sets the current sketch vertex symbols back to the options defined in the application settings.
- ResetSketchSegmentSymbolOptions- Sets the current sketch segment symbol back to the options defined in the application settings.
Symbology set via these methods override the default symbology defined in the application settings. Here's a code snippet showing how to customize the segment symbol of the sketch feedback. In this example, the ant-trail becomes a blue dash-dot line of width 2.5. Note it is not possible to change the segment symbol type. It continues to be constrained to be a dash-dot line.
protected override Task OnToolActivateAsync(bool active)
{
return QueuedTask.Run(() =>
{
var options = GetSketchSegmentSymbolOptions();
var blue = new CIMRGBColor();
blue.R = 0;
blue.G = 0;
blue.B = 255;
options.PrimaryColor = blue;
options.Width = 2.5;
SetSketchSegmentSymbolOptions(options);
});
}
A sample tool modifying sketch feedback can be found at CustomToolSketchSymbology.
Of course, you can customize both the SketchSegment and the sketch feedback options at the same time. Here's a combined example along with the output. You can see the red SketchSegment overlaid with the blue sketch feedback segment options. The vertices are the cyan circle from the SketchSegment overlaid with the default red and green hollow squares of the sketch feedback vertex options.
private CIMLineSymbol _lineSymbol = null;
protected override async Task OnToolActivateAsync(bool hasMapViewChanged)
{
if (_lineSymbol == null)
{
_lineSymbol = await Module1.CreateLineSymbolAsync();
}
this.SketchSymbol = _lineSymbol.MakeSymbolReference();
await QueuedTask.Run(() =>
{
var options = GetSketchSegmentSymbolOptions();
var blue = new CIMRGBColor();
blue.R = 0;
blue.G = 0;
blue.B = 255;
options.PrimaryColor = blue;
options.Width = 2.5;
SetSketchSegmentSymbolOptions(options);
});
}
See the Sketch Symbology Options section for details on how to modify the sketch feedback vertex and segment application options for all tools rather than just for a custom tool. Note that any symbol modifications made in a custom tool will take precedence over those in the application options.
Editing options corresponding to the editing options on the Pro options UI are available in the api via the static ApplicationOptions.EditingOptions
property - refer to ProConcepts Content - ApplicationOptions for more information. The available options include:
//Gets and sets the application editing options.
public class EditingOptions
{
//Gets and sets whether the dialog to confirm deletes is displayed.
public bool ShowDeleteDialog { get; set; }
//Gets and sets the sketch behavior on a touch pad (click + drag vs click-drag-click)
public bool DragSketch { get; set; }
//Gets and sets whether dynamic constraints are displayed in the map
public bool ShowDynamicConstraints { get; set; }
//Gets and sets whether Deflection is the default direction constraint. Set to
//false for Absolute to be the default direction constraint.
public bool IsDeflectionDefaultDirectionConstraint { get; set; }
//Gets and sets whether Direction is the default constraint for input mode. Set
//to false for Distance to be the default constraint for input mode.
public bool IsDirectionDefaultInputConstraint { get; set; }
//Gets and sets whether the editing toolbar is visible.
public bool ShowEditingToolbar { get; set; }
//Gets and sets the editing toolbar position.
public ToolbarPosition ToolbarPosition { get; set; }
//Gets and sets the editing toolbar size.
public ToolbarSize ToolbarSize { get; set; }
//Gets and sets whether the Escape key is enabled as a shortcut to cancel the
//active tool in Stereo maps.
public bool EnableStereoEscape { get; set; }
//Gets and sets whether the editing toolbar is magnified.
public bool MagnifyToolbar { get; set; }
//Gets and sets whether edits are to be automatically saved.
public bool AutomaticallySaveEdits { get; set; }
//Gets and sets whether edits are to be saved by time or operation when
//ArcGIS.Desktop.Core.EditingOptions.AutomaticallySaveEdits is true.
public bool AutoSaveByTime { get; set; }
//Gets and sets the time interval (in minutes) after which edits will be saved
//if ArcGIS.Desktop.Core.EditingOptions.AutomaticallySaveEdits is true and
//ArcGIS.Desktop.Core.EditingOptions.AutoSaveByTime is true.
public int SaveEditsInterval { get; set; }
//Gets and sets the number of operations after which edits will be saved if
//ArcGIS.Desktop.Core.EditingOptions.AutomaticallySaveEdits
//is true and ArcGIS.Desktop.Core.EditingOptions.AutoSaveByTime is false.
public int SaveEditsOperations { get; set; }
//Gets and sets whether edits are saved when saving project.
public bool SaveEditsOnProjectSave { get; set; }
//Gets and sets whether the dialog to confirm save edits is displayed.
public bool ShowSaveEditsDialog { get; set; }
//Gets and sets whether the dialog to confirm discard edits is displayed.
public bool ShowDiscardEditsDialog { get; set; }
//Gets and sets whether editing can be enabled and disabled from the Edit tab.
public bool EnableEditingFromEditTab { get; set; }
//Gets and sets whether the active editing tool is deactivated when saving or
//discarding edits.
public bool DeactivateToolOnSaveOrDiscard { get; set; }
//Gets and sets whether newly added layers are editable by default.
public bool NewLayersEditable { get; set; }
//Gets and sets whether double-click is enabled as a shortcut for Finish when
//sketching.
public bool FinishSketchOnDoubleClick { get; set; }
//Gets and sets whether vertex editing is allowed while sketching.
public bool AllowVertexEditingWhileSketching { get; set; }
//Gets and sets whether a warning is displayed when subtypes are changed.
public bool WarnOnSubtypeChange { get; set; }
//Gets and sets whether default values are initialized on a subtype change.
public bool InitializeDefaultValuesOnSubtypeChange { get; set; }
//Gets and sets the uncommitted attribute edits setting.
public UncommitedEditMode UncommitedAttributeEdits { get; set; }
//Gets and sets whether attribute validation is enforced.
public bool EnforceAttributeValidation { get; set; }
//Gets and sets whether topology is stretched proportionately when moving a
//topology element.
public bool StretchTopology { get; set; }
//Gets and sets the uncommitted geometry edits setting.
public UncommitedEditMode UncommitedGeometryEdits { get; set; }
//Gets and sets if Move tool is to be activated after all paste operations.
public bool ActivateMoveAfterPaste { get; set; }
//Gets and sets whether feature symbology is displayed in the sketch.
public bool ShowFeatureSketchSymbology { get; set; }
//Gets and sets whether geometry is stretched proportionately when moving a vertex.
public bool StretchGeometry { get; set; }
//Determines if the segment sketch symbol can be set.
public bool CanSetSegmentSymbolOptions(SegmentSymbolOptions segmentSymbol);
//Determines if the vertex sketch symbol can be set.
public bool CanSetVertexSymbolOptions(VertexSymbolType symbolType,
VertexSymbolOptions vertexSymbol);
//Gets the default segment sketching symbol information. This method must be called
//on the MCT. Use QueuedTask.Run.
public SegmentSymbolOptions GetDefaultSegmentSymbolOptions();
//Gets the default symbol information for a vertex while sketching. This method
//must be called on the MCT. Use QueuedTask.Run.
public VertexSymbolOptions GetDefaultVertexSymbolOptions(VertexSymbolType symbolType);
//Gets the segment sketching symbol information. This method must be called on
//the MCT. Use QueuedTask.Run.
public SegmentSymbolOptions GetSegmentSymbolOptions();
//Gets the symbol for a vertex while sketching. This method must be called on the
//MCT. Use QueuedTask.Run.
public VertexSymbolOptions GetVertexSymbolOptions(VertexSymbolType symbolType);
//Sets the segment sketch symbol. This method must be called on the MCT.
//Use QueuedTask.Run.
public void SetSegmentSymbolOptions(SegmentSymbolOptions segmentSymbol);
//Sets the symbol for a vertex while sketching. This method must be called on the
//MCT. Use QueuedTask.Run.
public void SetVertexSymbolOptions(VertexSymbolType symbolType,
VertexSymbolOptions vertexSymbol);
}
Example:
//toggle/switch option values
var options = ApplicationOptions.EditingOptions;
options.EnforceAttributeValidation = !options.EnforceAttributeValidation;
options.WarnOnSubtypeChange = !options.WarnOnSubtypeChange;
options.InitializeDefaultValuesOnSubtypeChange = !options.InitializeDefaultValuesOnSubtypeChange;
options.UncommitedAttributeEdits = (options.UncommitedAttributeEdits == UncommitedEditMode.AlwaysPrompt) ?
UncommitedEditMode.Apply : UncommitedEditMode.AlwaysPrompt;
options.StretchGeometry = !options.StretchGeometry;
options.StretchTopology = !options.StretchTopology;
options.UncommitedGeometryEdits = (options.UncommitedGeometryEdits == UncommitedEditMode.AlwaysPrompt) ?
UncommitedEditMode.Apply : UncommitedEditMode.AlwaysPrompt;
options.ActivateMoveAfterPaste = !options.ActivateMoveAfterPaste;
options.ShowFeatureSketchSymbology = !options.ShowFeatureSketchSymbology;
options.FinishSketchOnDoubleClick = !options.FinishSketchOnDoubleClick;
options.AllowVertexEditingWhileSketching = !options.AllowVertexEditingWhileSketching;
options.ShowDeleteDialog = !options.ShowDeleteDialog;
options.EnableStereoEscape = !options.EnableStereoEscape;
options.DragSketch = !options.DragSketch;
options.ShowDynamicConstraints = !options.ShowDynamicConstraints;
options.IsDeflectionDefaultDirectionConstraint = !options.IsDeflectionDefaultDirectionConstraint;
options.IsDirectionDefaultInputConstraint = !options.IsDirectionDefaultInputConstraint;
options.ShowEditingToolbar = !options.ShowEditingToolbar;
options.ToolbarPosition = (options.ToolbarPosition == ToolbarPosition.Bottom) ?
ToolbarPosition.Right : ToolbarPosition.Bottom;
options.ToolbarSize = (options.ToolbarSize == ToolbarSize.Medium) ? ToolbarSize.Small : ToolbarSize.Medium;
options.MagnifyToolbar = !options.MagnifyToolbar;
options.EnableEditingFromEditTab = !options.EnableEditingFromEditTab;
options.AutomaticallySaveEdits = !options.AutomaticallySaveEdits;
options.AutoSaveByTime = !options.AutoSaveByTime;
options.SaveEditsInterval = (options.AutomaticallySaveEdits) ? 20 : 10;
options.SaveEditsOperations = (options.AutomaticallySaveEdits) ? 60 : 30;
options.SaveEditsOnProjectSave = !options.SaveEditsOnProjectSave;
options.ShowSaveEditsDialog = !options.ShowSaveEditsDialog;
options.ShowDiscardEditsDialog = !options.ShowDiscardEditsDialog;
options.DeactivateToolOnSaveOrDiscard = !options.DeactivateToolOnSaveOrDiscard;
options.NewLayersEditable = !options.NewLayersEditable;
EditingOptions contains a number of option settings related to the sketch symbology. These require use of the Pro Main CIM Thread, MCT, and so are accessed and set via Get and Set methods (which must be wrapped in a QueuedTask.Run(...)
) rather than Get and Set properties. Symbology settings are also a little different from other settings in that they are constrained by what can represent a valid symbology setting. Not all possible point and line combinations are valid for use on the sketch. Add-ins, therefore, can use the CanSetVertexSymbolOptions(vertexOptions)
and CanSetSegmentSymbolOptions(segmentOptions)
to determine if their symbology options are valid/can be used with sketch vertex and sketch segment symbology respectively. Additionally, to assist add-ins in returning to the default settings for vertex and segment sketch symbology, a GetDefaultVertexSymbolOptions(vertexType)
and GetDefaultSegmentSymbolOptions()
methods are provided. Examples follow:
Retrieve the existing sketch symbology settings:
var options = ApplicationOptions.EditingOptions;
//Must use QueuedTask
QueuedTask.Run(() => {
//There are 4 vertex symbol settings - selected, unselected and the
//current vertex selected and unselected.
var reg_select = options.GetVertexSymbolOptions(VertexSymbolType.RegularSelected);
var reg_unsel = options.GetVertexSymbolOptions(VertexSymbolType.RegularUnselected);
var curr_sel = options.GetVertexSymbolOptions(VertexSymbolType.CurrentSelected);
var curr_unsel = options.GetVertexSymbolOptions(VertexSymbolType.CurrentUnselected);
//to convert the options to a symbol use
//GetPointSymbol
var reg_sel_pt_symbol = reg_select.GetPointSymbol();
//ditto for reg_unsel, curr_sel, curr_unsel
//Segment symbol options
var seg_options = options.GetSegmentSymbolOptions();
});
Set sketch symbol options:
var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() => {
//change the regular unselected vertex symbol:
//default is a green, hollow, square, 5pts. Change to
//Blue outline diamond, 10 pts
var vertexSymbol = new VertexSymbolOptions(VertexSymbolType.RegularUnselected);
vertexSymbol.OutlineColor = ColorFactory.Instance.BlueRGB;
vertexSymbol.MarkerType = VertexMarkerType.Diamond;
vertexSymbol.Size = 10;
//check are these changes valid?
if (options.CanSetVertexSymbolOptions(
VertexSymbolType.RegularUnselected, vertexSymbol)) {
//apply them - will throw for invalid choices
options.SetVertexSymbolOptions(VertexSymbolType.RegularUnselected, vertexSymbol);
}
//change the segment symbol primary color to green and
//width to 1 pt
var segSymbol = options.GetSegmentSymbolOptions();
segSymbol.PrimaryColor = ColorFactory.Instance.GreenRGB;
segSymbol.Width = 1;
//check are these changes valid?
if (options.CanSetSegmentSymbolOptions(segSymbol)) {
//apply them - will throw for invalid choices
options.SetSegmentSymbolOptions(segSymbol);
}
Set sketch options back to defaults:
var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() => {
//set vertex reg unselected back to defaults...
//ditto for reg selected and current selected, unselected
var def_reg_unsel =
options.GetDefaultVertexSymbolOptions(VertexSymbolType.RegularUnselected);
//apply default
options.SetVertexSymbolOptions(
VertexSymbolType.RegularUnselected, def_reg_unsel);
//set segment sketch back to default
var def_seg = options.GetDefaultSegmentSymbolOptions();
options.SetSegmentSymbolOptions(def_seg);
});
Versioning option settings control the conflict and conflict reconciliation behavior for an edit session involving versioned data. They are available via the static ApplicationOptions.VersioningOptions
property. The VersioningOptions include:
//Gets and sets the application versioning options.
public class VersioningOptions
{
public VersioningOptions();
//Gets and sets the value defining how conflicts are flagged.
public ConflictDetectionType DefineConflicts { get; set; }
//Gets and sets the value defining how conflicts are resolved.
public ConflictResolutionType ConflictResolution { get; set; }
//Gets and sets the value indicating if a dialog is displayed during
//the reconcile process.
public bool ShowReconcileDialog { get; set; }
//Gets and sets the value indicating if a dialog is displayed to review
//conflicts.
public bool ShowConflictsDialog { get; set; }
}
For example, this snippet toggles/changes the current versioning option values:
var vOptions = ApplicationOptions.VersioningOptions;
vOptions.DefineConflicts = (vOptions.DefineConflicts == ConflictDetectionType.ByRow) ?
ConflictDetectionType.ByColumn : ConflictDetectionType.ByRow;
vOptions.ConflictResolution = (vOptions.ConflictResolution == ConflictResolutionType.FavorEditVersion) ?
ConflictResolutionType.FavorTargetVersion : ConflictResolutionType.FavorEditVersion;
vOptions.ShowConflictsDialog = !vOptions.ShowConflictsDialog;
vOptions.ShowReconcileDialog = !vOptions.ShowReconcileDialog;
Home | API Reference | Requirements | Download | Samples
- Overview of the ArcGIS Pro SDK
- What's New for Developers at 3.4
- Installing ArcGIS Pro SDK for .NET
- Release notes
- Resources
- Pro SDK Videos
- ProSnippets
- ArcGIS Pro API
- ProGuide: ArcGIS Pro Extensions NuGet
Migration
- ProSnippets: Framework
- ProSnippets: DAML
- ProConcepts: Framework
- ProConcepts: Asynchronous Programming in ArcGIS Pro
- ProConcepts: Advanced topics
- ProGuide: Custom settings
- ProGuide: Command line switches for ArcGISPro.exe
- ProGuide: Reusing ArcGIS Pro Commands
- ProGuide: Licensing
- ProGuide: Digital signatures
- ProGuide: Command Search
- ProGuide: Keyboard shortcuts
Add-ins
- ProGuide: Installation and Upgrade
- ProGuide: Your first add-in
- ProGuide: ArcGIS AllSource Project Template
- ProConcepts: Localization
- ProGuide: Content and Image Resources
- ProGuide: Embedding Toolboxes
- ProGuide: Diagnosing ArcGIS Pro Add-ins
- ProGuide: Regression Testing
Configurations
Customization
- ProGuide: The Ribbon, Tabs and Groups
- ProGuide: Buttons
- ProGuide: Label Controls
- ProGuide: Checkboxes
- ProGuide: Edit Boxes
- ProGuide: Combo Boxes
- ProGuide: Context Menus
- ProGuide: Palettes and Split Buttons
- ProGuide: Galleries
- ProGuide: Dockpanes
- ProGuide: Code Your Own States and Conditions
Styling
- ProSnippets: Content
- ProSnippets: Browse Dialog Filters
- ProConcepts: Project Content and Items
- ProConcepts: Custom Items
- ProGuide: Custom Items
- ProGuide: Custom browse dialog filters
- ArcGIS Pro TypeID Reference
- ProSnippets: Editing
- ProConcepts: Editing
- ProConcepts: COGO
- ProConcepts: Annotation Editing
- ProConcepts: Dimension Editing
- ProGuide: Editing Tool
- ProGuide: Sketch Tool With Halo
- ProGuide: Construction Tools with Options
- ProGuide: Annotation Construction Tools
- ProGuide: Annotation Editing Tools
- ProGuide: Knowledge Graph Construction Tools
- ProGuide: Templates
3D Analyst Data
Plugin Datasources
Topology
Linear Referencing
Object Model Diagram
- ProSnippets: Geometry
- ProSnippets: Geometry Engine
- ProConcepts: Geometry
- ProConcepts: Multipatches
- ProGuide: Building Multipatches
Relational Operations
- ProSnippets: Knowledge Graph
- ProConcepts: Knowledge Graph
- ProGuide: Knowledge Graph Construction Tools
Reports
- ProSnippets: Map Authoring
- ProSnippets: Annotation
- ProSnippets: Charts
- ProSnippets: Labeling
- ProSnippets: Renderers
- ProSnippets: Symbology
- ProSnippets: Text Symbols
- ProConcepts: Map Authoring
- ProConcepts: Annotation
- ProConcepts: Dimensions
- ProGuide: Tray buttons
- ProGuide: Custom Dictionary Style
- ProGuide: Geocoding
3D Analyst
CIM
Graphics
Scene
Stream
Voxel
- ProSnippets: Map Exploration
- ProSnippets: Custom Pane with Contents
- ProConcepts: Map Exploration
- ProGuide: Map Pane Impersonation
- ProGuide: TableControl
Map Tools
- ProGuide: Feature Selection
- ProGuide: Identify
- ProGuide: MapView Interaction
- ProGuide: Embeddable Controls
- ProGuide: Custom Pop-ups
- ProGuide: Dynamic Pop-up Menu
Network Diagrams
- ArcGIS Pro API Reference Guide
- ArcGIS Pro SDK (pro.arcgis.com)
- arcgis-pro-sdk-community-samples
- ArcGISPro Registry Keys
- ArcGIS Pro DAML ID Reference
- ArcGIS Pro Icon Reference
- ArcGIS Pro TypeID Reference
- ProConcepts: Distributing Add-Ins Online
- ProConcepts: Migrating to ArcGIS Pro
- FAQ
- Archived ArcGIS Pro API Reference Guides
- Dev Summit Tech Sessions