Skip to content

ProConcepts Editing

ArcGIS Pro SDK edited this page May 4, 2016 · 41 revisions

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# and Visual Basic
Subject:       Editing
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          05/04/2016
ArcGIS Pro:    1.2
Visual Studio: 2013, 2015

In this topic

Editing in ArcGIS Pro

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.

Editing Controls

Deciding how your customization is exposed to the end user is usually the first decision you need to make. 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.

Commands

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 controls on the Edit tab are examples of edit commands.

You can use the ArcGIS Pro Button template in Visual Studio to create an edit command.

Tools

Tools allow you to interact with the display in some manner. Custom tools can be used to define areas, create a sketch, or select features for an edit operation.

Sketch Tools

Sketch tools are used to create an edit sketch in the display. The returned geometry is then used for further edit operations such as creating new or editing existing features.

You can use the ArcGIS Pro MapTool template in Visual Studio to create a sketch tool (A MapTool can sketch a feedback on the UI when its this.IsSketchTool property is set to true. For more information on MapTools, see the MapTool section in the Map Exploration ProConcepts. The editing community samples contain many examples of sketch tools.

Construction Tools

Construction tools—such as the Point, Line, Polygon, and Circle tools—are used to create features in the template environment. Construction tools are sketch tools that also 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 the tool will be associated with.

Modules

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.

Performing Edits with EditOperation##

All edits within Pro should be performed through the EditOperation class and its members.

Edit operations perform 3 key functions:

  1. Execute the operations against underlying datastores
  2. Add an operation to the ArcGIS Pro OperationManager Undo/Redo stack
  3. Invalidate any layer caches associated with the layers edited by the Edit Operation.

Edit operations can span multiple datastores.

The basic pattern to perform an edit is to instantiate the EditOperation class, set its name, call one or more members of the operation with the required parameters (e.g. Create, Clip, Cut, Modify,etc.), and execute the operation. On success, an undo-able operation, using the name of the operation as its label, is placed on the Pro undo stack. If any aspect of the edit operation fails then the entire operation (if it contained multiple operations) is failed. 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.

Another aspect of editing in Pro that may be new to you if you are coming from ArcObjects is that editing in Pro is predominantly layer based and not feature class based. When using edit operations, most of the time you will be using a layer or standalonetable - a "MapMember" - as the input to edit operations and not feature classes or GDB table objects. Pro has a sophisticated caching mechanism and using the MapMembers with edit operations ensures that the underlying layer and table caches are invalidated after any edit. You can check if a feature layer is editable, using BasicFeatureLayer.CanEditData. 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.

EditOperations are commonly used in conjunction with MapTools that sketch or are "Construction Tools" being used to create new features. The Create method on the EditOperation has overloads to work with both layers and tables (i.e. MapMembers) or the current EditingTemplate. Create also has overloads to apply a predefined set of attributes to a new feature or row.

This example illustrates a create edit operation being executed inside the callback of a Construction tool.

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();
        }

Note: this is the default code generated "out of the box" by the Construction tool template that comes with the Pro SDK. CurrentTemplate is a property of MapTool which is automatically set to the current Editing Template (based on the template selected on the Create Features dockpane).

In this example, a new feature is being created using a feature layer selected from the current map. A dictionary is being used because we want to set default values for two of the attributes as well as the feature 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
   }

Edit operations can contain many edits all combined into a single "execute" or transaction (e.g. creation, modifying geometry and attributes, deleting features, etc.). The relevant edit operation methods are invoked, in code, in the order in which they are to be executed (by the "Execute" or "ExecuteAsync" method call). If a single operation specified within the execute fails then all operations in the edit operation will be failed. On successful completion, a single operation is added to the Pro undo stack which will undo the entire set of underlying operations (caveat: read about non versioned edits) in Pro.

The following example shows a Clip, Cut, and Planarize operation on a single feature combined into a single edit operation:

   //Multiple operations can be performed by a single
   //edit operation.
   var op = new EditOperation();
   op.Name = "Clip, Cut, and Planarize Features";

   //Combine three operations, one after the other...
   op.Clip(featureLayer, oid, clipPoly);
   op.Cut(featureLayer, oid, cutLine);
   op.Planarize(featureLayer, new List<long>() { oid});

   //Execute them all together (in the order they were declared)
   await op.ExecuteAsync();

   if (!op.IsSucceeded) {
     //TODO: get the op.ErrorMessage, inform the user
   }

If one of the underlying operations fails then an error message is returned for the first operation that failed only. Combining individual operations in a single execute improves performance as all operations are combined into a single undo-able operation.

Edit operations can also combine operations across different datasets (i.e. layers and/or tables) whether sourced on data in the same datastore or different datastores. Under the covers, Pro establishes an edit session that spans all the datastores and all edits will be saved or discarded together at the conclusion of the edit session (the edit session ends when the user chooses to save or discard the edits or the Project is closed and the user is forced to save or discard the edits).

In this example, an edit operation is defined that edits two different feature layers. Which datastore their underlying datasets reference is irrelevant to the operation.

  var centerline = MapView.Active.Map.GetLayersAsFlattenedList().First((l) => l.Name == "streets") as FeatureLayer;
  var zip = MapView.Active.Map.GetLayersAsFlattenedList().First((l) => l.Name == "zipcodes") as FeatureLayer;

  //Get object id of centerline and zipcode to be updated (eg from selection,...)
  var oid_c = ...
  var oid_z = ...

  //Geometry for reshape of zip code poly
  var poly = ...

  Dictionary<string,object> attributes = new Dictionary<string, object>();
  attributes["NAME"] = "Main St.";
  attributes["COMMENT"] = "Updated road name";

   var op = new EditOperation();
   op.Name = "Update Streets and Zipcode";
   op.Modify(centerline, oid_c, attributes);
   op.Reshape(zip, oid_z, poly);

   await op.ExecuteAsync();

   if (!op.IsSucceeded) {
     //TODO: get the op.ErrorMessage, inform the user
   }

If either of the modify fails or the reshape fails the entire operation is failed. On success, an undo-able operation "Update Streets and Zipcode" is placed on the undo/redo stack.

Refer to the ProSnippets Editing for other examples of using EditOperation.

Layer Editability

Edits can be attempted on all editable layers and standalone tables, 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.

Save and Discard Edits

Edits can be saved or discarded using asynchronous methods available on the Project instance object. These methods are awaitable and do not block the UI:

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.

###Versioned and Non-Versioned Edits###

Versioned and File Geodatabase Edits

When editing a feature layer using versioned data or data from a file geodatabase then those edits (i.e. "operations") are undo-able and an entry will be placed on Pro's Undo/Redo stack for each edit. The first edit executed against a versioned or file geodatabase dataset (via code or the UI) creates an edit session within Pro. The edit session can contain edits across different datasets and different datastores. All of the edits within the edit session are either applied or discarded depending on whether you choose to save or discard the edit session. However versioned and non-versioned edits cannot be mixed in the same session. All pending versioned and file geodatabase edits must be saved or discarded before executing any operations against non-versioned data. Attempting to execute edit operations against non-versioned data when there are pending edits for versioned or file geodatabase data will throw an exception "Edit operation failed. This data is currently locked by another use and cannot be saved".

  EditingException.png

Non-Versioned Edits####

Within enterprise geodatabases it is possible maintain non-versioned datasets. When editing MapMembers using non-versioned data any edits are applied immediately to the non-versioned data. They cannot be undone. An edit session is not started and individual edits are automatically saved (or failed) when they are executed (in reality, an edit session is started for the non-versioned edit but it is completed with an automatic save or rollback on failure of each individual non-versioned edit). Attempting to execute a non-versioned edit when there are pending versioned or file geodatabase edits will throw an exception (see above).

Non-versioned edits should not be mixed in the same edit operation as versioned and file geodatabase edits. However it is, technically, possible if the non-versioned edits are executed first before there are any pending edits for versioned and file geodatabase data.

In general, the correct pattern for performing non-versioned edits is as follows:

   //First: Ensure there are no pending edits
   if (Project.Current.HasEdits) {
      //Because there are pending edits, you have three choices:
      //
      //1. Prompt the user and ask them to save or discard, or
      //2. Save or discard them automatically, or
      //3. Bail

      //TODO implement logic relevant to your choice
      //For example, we choose "#3" and bail

      MessageBox.Show("Please save or discard your pending edits first", "Edit Operation Cancelled");
      return;
   }

   //Now, assuming other edits are not executing in the background,
   //it is safe to execute non-versioned edits
   var nonVersionedLayer = ...
   var attribs = ....
   var polyLine = ...

   var op = new EditOperation();
   op.Modify(nonVersionedLayer, oid, attribs);
   op.Reshape(nonVersionedLayer, oid, polyLine);
   await op.ExecuteAsync();//Execute the edits. They are not undo-able!

  //Note: Project.Current.HasEdits will be false, there is nothing to save

###Feature Inspector and Working with Attributes###

For updating attribute values of features, the Editor extension provides a convenient utility class called Inspector class. The basic pattern for using inspector is:

  1. Instantiate an inspector instance (var inspector = new Inspector();)
  2. Load (the attributes) of one or more features into the inspector (inspector.Load(...) or inspector.LoadAsync(...)). This will load joined data.
  3. Use indexer-style access (via the "[" and "]" brackets) by field index or name to set attribute values.
  4. Call Inspector.ApplyAsync() or use EditOperation 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" must be 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...
//TODO: Enumerate on all the layers in the selection...
//
var kvp = MapView.Active.SelectFeatures(zippoly, isWhollyWithin: true).FirstOrDefault(k => k.Value.Count > 0);
if (kvp.Key != null) {
   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.

Editing Subtypes

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>(op2.IsSucceeded, op2.ErrorMessage);
    });

Chaining EditOperations

In some circumstances, you may need to execute sequential edit operations and still have only one entry on the undo stack (by default, each call to edit operation execute or execute "async" results in an undoable operation on the undo/redo stack).

The most common scenario for chaining is creating a feature and wanting to add an attachment to that feature as a single operation. Adding an attachment requires a mapmember and the oid of a feature to which the attachment should be added (and a path to the attachment). This requires two edit operations. The first creates the feature and gets its object id. The second adds the attachment using the oid of the newly created feature. Normally, this would put two separate entries on the undo stack - one for the create and one for the add attachment. Instead, if we chain the operations we can treat them as a single operation (which will either apply or undo the create and add attachment together).

In the example, the CreateChainedOperation method is combining the Create and AddAttachment edit operations.

      //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);
        op1.Execute();

        //create a chained operation and add the attachment.
        var op2 = op1.CreateChainedOperation();

        op2.AddAttachment(CurrentTemplate.Layer, newFeatureID, @"C:\Hydrant.jpg");
        op2.Execute();
        return new Tuple<bool, string>(op2.IsSucceeded, op2.ErrorMessage);
      });

Snapping

On the UI, you can toggle snapping, set the snap modes (point, edge, vertex, and so on) and tolerance through the snapping drop-down menu and options dialog box 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 = await Snapping.GetOptionsAsync(myMap);
snapOptions.SnapToSketchEnabled = true;
snapOptions.XYTolerance = 100;
await Snapping.SetOptionsAsync(myMap,snapOptions);

The following example shows setting some general snapping options and set 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) {
         //GetDefinition and SetDefinition must be called inside QueuedTask
         await QueuedTask.Run(() => {
            foreach (var fl in flayers) {
               var layerDef = fl.GetDefinition() as CIMGeoFeatureLayerBase;
               layerDef.Snappable = true;
               fl.SetDefinition(layerDef);
            }
         });
     }
  }

Note: 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;

Edit Completed Event

Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.EditCompletedEvent to be notifed when any create, modify, and delete has completed. The event is published after the EditOperation.Execute (or ExecuteAsync) has completed. The EditCompletedEvent cannot cancel any of the edit operations that triggered it. The EditCompletedEventArgs event argument contains a list of all creates, modified, and deletes by object id per MapMember (layer or standalone table).

In this example, the code registers for the EditCompletedEvent in the constructor of the (MapTool) class. The callback OnEditComplete will be fired every time an EditOperation completes.

namespace TestEditEvents {

    internal class TestEventsTool : MapTool {

        private SubscriptionToken _token;
        public TestEventsTool() {
          _token = EditCompletedEvent.Subscribe(OnEditComplete);
          ...
        }

        protected Task onEditComplete(EditCompletedEventArgs args) {
   
          //These are all 'IReadOnlyDictionary<MapMember, IReadOnlyCollection<long>>'
          var creates = args.Creates;
          var modifies = args.Modifies;
          var deletes = args.Deletes;

          foreach (var item in creates) {
             MapMember layerOrTable = item.Key;
             IReadOnlyCollection<long> oids = item.Value;

             // do something 
           }

            return Task.FromResult(0);
         }

        
         //Perform an edit
         protected override Task<bool> OnSketchCompleteAsync(Geometry geometry) {

            // Create an edit operation
            var op = new EditOperation();
            op.Name = string.Format("Create {0}", CurrentTemplate.Layer.Name);
            op.SelectNewFeatures = true;

            // Queue feature creation
            op.Create(CurrentTemplate, geometry);

            // Execute the operation
            op createOperation.ExecuteAsync();//EditCompletedEvent fires when
                                              //ExecuteAsync has finished
        }
    }
}

There are also a set of row-level events: RowCreatedEvent, RowChangedEvent, and RowDeletedEvent that capture creates, modifies, and deletes per layer/table as they occur. Please refer to the Edit Events topic in the Advanced section for more information on row-level events.

Feature Templates

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 button in the Manage Edits 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.

Accessing Feature Templates

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();
});

Creating and Modifying Feature Templates

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.

The following example creates a new template for a layer from an existing template:

//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);
});

Removing Feature Templates

Templates can be removed from a layer by removing them from the list of templates on the layer definition. The layer definition is then set back on the layer.

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);
});

Advanced Editing

EditOperation Callback

The edit operation defines the transactional scope for multiple edits so that the edits can be performed as a single atomic operation whereby failure in performing any edit within the specified group of edits will cause all other edits in the group to fail to be performed as well.

There are two distinct patterns when performing edits with the edit operation:

  • Working with layers within maps. This is the most common method for performing edits and uses methods on the EditOperation class (e.g., Create, Delete, Modify, and so on). Refer to Performing Edits.

  • Working directly with feature classes and tables in the datastore—Using the Callback method and working with RowCursors. This is the subject of this topic.

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). To edit GIS and non-GIS data within an operation you use the EditOperation.Callback() method.

The edits to be performed in the edit operation to the GIS and non-GIS data must be specified as an Action<T> (where T is IEditContext) to the Callback method on the EditOperation class. The "called back" Action or delegate should be implemented using row cursors to perform the edits.

It should be noted that the edit operation is not executed when the Callback is set up. This should be thought of as an act of telling EditOperation what needs to be executed when the edit operation is executed. When EditOperation.ExecuteAsync (or EditOperation.Execute) is called, the edit operation will perform the edits specified in the Action delegate when the edit operation is executed in transactional scope.

The following example illustrates the execution of the transaction:

    ArcGIS.Core.Data.Dataset dataset1 = null; 
    ArcGIS.Core.Data.Dataset dataset2 = null;

        //dataset1 and dataset2 are set before MakeEdits is called

    public async Task MakeEdits()
    { 
      EditOperation editOperation = new EditOperation();
      editOperation.Callback(Edits, dataset1, dataset2); //Nothing happens at the transaction level when this executes
      await editOperation.ExecuteAsync();   //Transaction starts, Edits are executed, Transaction ends
    }

    private void Edits(EditOperation.IEditContext context)
    {
      //Edit1
      //Edit2
      //…..
    }

Types of EditOperations

There are two types of edit operations that can be performed:

  • Long edit operations. Long edit operations have the following characteristics:

    • They are performed on versioned and file geodatabase data
    • They support Undo and Redo.
    • They start an edit session if one is not already started
    • All edits are either saved or discarded (in the current edit session). Save or discard closes the existing edit session.

Typically, a long edit operation is used when it is not known in advance whether or not an edit operation will be saved. Since only file geodatabase datasets and versioned enterprise geodatabase datasets support an ability to undo or redo edits performed on these datasets, it is logical that only file geodatabase and enterprise datasets can be edited using long edit operations.

  • Short edit operations. Short edit operations have the following characteristics:

    • They are performed on non-versioned data
    • They are either saved (on success) or undone (on failure) immediately.
    • They do not support Undo or Redo.
    • They do not participate in an edit session.

These are the edit operations that are not added to the undo/redo stack and cannot be discarded once the operation is complete. The edit operation boundary and edit session boundary are the same in the case of a short edit operation (which is the scope of the EditOperation Callback). A new edit session is started when the non-versioned edit executes and is closed automatically when the "short" edit operation completes. Since non-versioned enterprise geodatabase datasets do not support undo/redo, these datasets should only be edited in a short edit operation. If an edit session is already in-progress when a short edit operation is executed then the short transaction fails. In future releases, support for editing file geodatabase or versioned enterprise geodatabase datasets in a short edit operation may be provided.

The EditOperation class has an EditOperation.EditOperationType property, which is of enum type EditOperationType having possible values of both Long and Short. In the application, the editor works on layers and stand-alone tables in the project. These members inherently know their data source and version state. For most edits, the editor determines the type of transaction to use based on the data sources in the edit operation. If the EditOperation.EditOperationType is not set, there will be an attempt to infer the EditOperationType based on the type of dataset (file geodatabase or enterprise geodatabase) and the VersionRegistrationType (in the case of enterprise geodatabase datastores) passed into the Callback.

The EditOperationType inference is based on the following rules:

  • If the dataset is either a file geodatabase dataset or a versioned enterprise geodatabase dataset, the EditOperationType will be inferred as Long.
  • If the dataset is a non-versioned enterprise geodatabase dataset, the EditOperationType will be inferred as Short. Keep in mind that if there are existing edits for that datastore in the project, the EditOperation execution will fail. In future releases, intelligence may be added to infer that edit operations whose callbacks have non-versioned datasets passed into them should be executed in Long (albeit non-undoable) EditOperations when there are existing edits in the project.
  • If multiple datasets from the same datastore are passed into the callback, all of the datasets should be of the same VersionRegistrationType. Otherwise, the edit operation execution will fail. This is true whether or not the EditOperationType is inferred.
  • If multiple datasets from different datastores are passed in, for each set of datasets belonging to a datastore, an internal edit operation and session will be started based on the rules previously discussed. However, for all practical purposes, all the edits will be considered as being part of a single edit operation whose type would be inferred as follows:
    • If all the datasets from multiple datastores were non-versioned, the edit operation would be inferred as a Short edit operation.
    • If there was at least one dataset from a file geodatabase or a versioned dataset from an enterprise geodatabase and other datasets were non-versioned, the edit operation would be inferred as a Long edit operation.

Though the EditOperationType can be inferred, the property is provided as a way to guard against unexpected behavior. For example, if you expected a certain edit to happen only on non-versioned tables, setting the EditOperationType to Short provides a guard against a circumstance where the table being edited was inadvertently registered as versioned. Conversely, setting the EditOperationType to Long guards against an edge condition where if the first edit operation were performed on non-versioned data, because there would be no session established, the edit would succeed and the EditOperationType would be inferred to Short (not Long). Setting EditOperationType to Long guards against this edge case.

Error Scenarios

An error will be thrown upon the following scenarios:

  • Irrespective of existing edits for a datastore, an EditOperation that is marked as Short and a file geodatabase or versioned enterprise geodatabase dataset is passed into the Callback on the EditOperation.
  • Irrespective of existing edits for a datastore, an EditOperation that is marked as Long and a non-versioned enterprise geodatabase dataset is passed into the callback on the EditOperation.
  • Given that, for a datastore, there are edits that have not been saved or discarded, an EditOperation that is marked as Short.

Aborting EditOperation Callbacks

Edit Operations can be aborted only if the Callback method is used to edit datasets from [datastores] (/ArcGIS/arcgis-pro-sdk/wiki/ProConcepts-Geodatabase#datastore).

To abort an edit operation, use the [Abort] (http://pro.arcgis.com/en/pro-app/sdk/api-reference/#topic8711.html) method on the EditOperation object. This causes the transaction to abort (i.e., all changes are rolled back, and the edit operation result will be false with the error message having the value specified when the abort was made).

    ArcGIS.Core.Data.Dataset dataset1 = null; 
    ArcGIS.Core.Data.Dataset dataset2 = null;

    //dataset1 and dataset2 are set before MakeEdits is called
        
    public async Task MakeEdits()
    {
      EditOperation editOperation = new EditOperation();
      editOperation.Callback(Edits, dataset1, dataset2); //Nothing happens at the transaction level when this executes
      await editOperation.ExecuteAsync();   //Transaction starts, Edits are executed, Transaction ends
    }
 
    private void Edits(EditOperation.IEditContext context)
    {
      bool somethingBadHappened = false;
      
      //Edit1
      
      if ( somethingBadHappened )
        context.Abort("This is the ErrorMessage");
      
      //Edit2
      //…..
    }

In the above code, the result will be false, and the ErrorMessage would be "This is the ErrorMessage".

Refreshing the Map

Refreshing the map (or the attributes table) is only needed when the Callback method is used to edit datasets from [datastores] (/ArcGIS/arcgis-pro-sdk/wiki/ProConcepts-Geodatabase#datastore).

To refresh the map to reflect changes made in the data, call the [IEditContext.Invalidate] (http://pro.arcgis.com/en/pro-app/sdk/api-reference/#topic8714.html) method on the IEditContext parameter on the Action delegate passed into the callback.

public MakeEdits()
{
   EditOperation editOperation = new EditOperation();
   editOperation.Callback(Edits(), dataset1, dataset2……);   //Nothing happens at the transaction level when this executes
   await editOperation.ExecuteAsync()   //  Transaction starts, Edits are executed, Transaction ends
}

private Action<EditOperation.IEditContext> Edits()
{
    return delegate(EditOperation.IEditContext context)
    {
      RowBuffer buffer = table.CreateRowBuffer();
      //Populate RowBuffer
      Row row = table.CreateRow(buffer);
      context.Invalidate(row); //Notifies the editor to redraw the feature/row
      …..
      RowCursor cursor = table.Search(queryfilter, false);
      if(cursor.MoveNext())
      {
        Row another = cursor.Current;
        context.Invalidate(another); //Invalidate has to be called before the row is modified and store is called
        //Edit row attributes
        another.Store();
        context.Invalidate(another);//Also has to be called after the store
      }
      if(cursor.MoveNext())
      {
        Row yetAnother = cursor.Current;
        context.Invalidate(another); //Invalidate has to be called before delete
        another.Delete();
      }
    };
 }

Note: If it is known that the edits being made are not going to affect the rendering of any layers, you could avoid calling it at all; or if you knew it would only affect color and not location, you could call it at the end of store.

There are also overloads for Dataset and Datastore to perform invalidate when there are lots of edits.

Exceptions####

  • Although it's recommended that file geodatabase and versioned enterprise geodatabase datasets should be edited in long edit operations, in future releases, the ability to perform edits on file geodatabase and versioned enterprise geodatabase datasets in short edit operations may be added.
  • Although it's recommended that, in a long edit operation, only file geodatabase and versioned enterprise geodatabase datasets be edited, and all the datasets being edited in the edit operation have to pass in as arguments to the callback, you could omit passing in a non-versioned dataset to the callback while actually editing that dataset in the edit operation. This will not fail, but undo or discard will not rollback the changes made to the non-versioned dataset.
  • Currently, there are no events being raised for Relationship class edits. This also includes the foreign key of the destination being changed or deleted.

Edit Session

An edit session is a collection of sequential edit operations, where each edit operation is a collection of edits—creates, deletes and changes to datasets in a datastore—and this collection of edits is performed atomically.

In ArcGIS Pro, the edit session starts when the first edit operation is executed. The life of the edit session differs depending on the EditOperationType of the edit operation that's executed first.

If there are no edits in the project, in the case of a long edit operation, when the operation is executed, the edit session starts on the underlying datastore containing that data, and the code specified in the operation is executed. All additional long edit operations are included in the edit session and once Save Edits (ArcGIS.Desktop.Core.Project.Current.SaveEditsAsync()) or Discard Edits (ArcGIS.Desktop.Core.Project.Current.DiscardEditsAsync()) is called on the project, the undo/redo stack is cleared and the edit session is closed.

The ability to save or discard an edit session is accessible through the ArcGIS.Desktop.Core.Project.Current singleton (Example) object, which is of type ArcGIS.Desktop.Core.Project.

For undoable edit sessions, the edit session stays active until either Project.Current.SaveEditsAsync or Project.Current.DiscardEditsAsync is called. Until then, long edit operations can be undone or redone. Once the session is saved or discarded, the undo stack is cleared of these operations.

With short edit operations, 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. Even though you can call Project.Current.SaveEditsAsync or Project.Current.DiscardEditsAsyn, they are effectively no-ops. Thus, calling undo or redo will also be no-ops because the edit session will be completed at the end of each non-undoable edit operation. This is technically a short transaction, but since the edit is committed immediately, the editor can start a new transaction, long or short, with the next edit.

Edit Events

The editing assembly provides events to listen for changes to objects at row level and for the execution of an edit operation. The specific events in the ArcGIS.Desktop.Editing.Events namespace are as follows:

  • 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())

The following example subscribes to all types of events and reads the EventArgs:

protected void SetupEvents()
{
  //Subscribe to editevents
  var editComplete = EditCompletedEvent.Subscribe(onEditComplete);

  //Subscribe to row events
  QueuedTask.Run(() =>
  {
    var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
    var layerTable = featLayer.GetTable();

    //Subscribe to row events for a layer
    var rowCreateToken = RowCreatedEvent.Subscribe(onRowEvent, layerTable);
    var rowChangeToken = RowChangedEvent.Subscribe(onRowEvent, layerTable);
    var rowDeleteToken = RowDeletedEvent.Subscribe(onRowEvent, layerTable);
  });

  
}

protected Task onEditComplete(EditCompletedEventArgs args)
{
  //Show the count of features changed
  Console.WriteLine("Creates: " + args.Creates.Values.Sum(list => list.Count).ToString());
  Console.WriteLine("Modifies: " + args.Modifies.Values.Sum(list => list.Count).ToString());
  Console.WriteLine("Deletes: " + args.Deletes.Values.Sum(list => list.Count).ToString());
  return Task.FromResult(0);
}

protected void onRowEvent(RowChangedEventArgs args)
{
  //Show the type of edit
  Console.WriteLine("RowEvent " + args.EditType.ToString());
}

In this example, you select two features from a feature layer and delete them:

  var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
  var oid1 = ...
  var oid2 = ...

  var op = new EditOperation();
  op.Name = string.Format("Deleted {0},{1}",oid1,oid2);
  op.Delete(featLayer, new List<long> {oid1, oid2});
  await op.ExecuteAsync();

The op.ExecuteAsync() will result in one EditCompletedEvent and two RowDeletedEvents publshed.

The row events are published during the execution of the edit operation, whereas the EditCompletedEvent is published after the entire edit operation has completed. You can cancel the edit operation during the row events but not in the EditCompletedEvent. In each event type, the EventArgs object provides more information about the edit itself. In your customization, you can choose which event to subscribe to according to your needs.

You should subscribe to events in a logical context. For example, you could set up the events in a module without any controls so that the events would fire globally for any project and any edit. Alternatively, you could set them up for a particular custom tool or toggle them through a button. Events should be unsubscribed when no longer required (e.g., on tool deactivate or module uninitialize) depending on how they were established.

Row Events

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. If there was an edit performed of a specific type on a specific table or feature class, an event would be raised, which would cause only the event handler that was passed into the Subscribe call with that table or feature class that was edited.

For example, table1 and table2 are two tables on a geodatabase and Subscribe was called on the RowCreatedEvent with Method1 as the event handler with the ArcGIS.Core.Data.Table object corresponding to table1 as the table argument. When a row is created in table1, only then is Method1 executed. Creating rows in table2 will not invoke Method1 (unless a second Subscribe call is made with Method1 with the ArcGIS.Core.Data.Table object corresponding to table1 as the table argument).

  public void Subscribe()
  {
    await QueuedTask.Run(() =>
        {
          using (var countiesFeatureClass = geodatabase.OpenDataset<FeatureClass>("Counties"))
          {
            var changedToken = RowChangedEvent.Subscribe(CountiesChangedEventHandler, countiesFeatureClass);
          }
        });
  }

    private void CountiesChangedEventHandler(RowChangedEventArgs args)
    {
      var row = args.Row;

      //Keep in mind the modifications being performed also cause cascaded events to be generated
      //So, make sure you have an "exit" condition, which makes sure there isn't an infinite loop of cascaded
      //events causing either a Stack Overflow or the application to hang indefinitely. 
      //An example of an exit condition is maintaining a list of objectIds processed by this handler, adding to the 
      //list if the object id is seen the first time and escaping the handler if the objectid is seen again
      //Another example is shown below
      if (!Convert.ToString(row["STATE_NAME"]).Contains("_FIPS"))
      {
        row["STATE_NAME"] = String.Format("{0}_FIPS", row["STATE_NAME"]);
        row.Store();
      }
    }

Similar to EditOperation.Callback, CountiesChangedEventHandler serves as the delegate/callback to be called when the event is raised. When RowChangedEvent.Subscribe is executed, the callback specified is registered with the editor. It is only when the event is raised that the callback code will be executed.

An important point to be noted is that RowChangedEvent is raised immediately when Row.Store()/Feature.Store() is executed. This means that, while the edit operation is executing, when the execution reaches the point when Row.Store is called, the execution control will be transferred immediately to the event handlers registered with the editor for RowChangedEvent before the next statement in the edit operation is executed. Effectively, even the event handler code is being executed in the transactional scope, and all the changes made to the datastore in the handler will also be rolled back if the edit operation is aborted. The same is true for RowCreatedEvent and RowDeletedEvent.

You can also abort an edit operation from within the event handler. The parameter on Subscribe is of type EditEventArgs, which has a CancelEdit("errormessage") method. This is equivalent to calling Abort on the EditOperation.

The following are a few things to keep in mind:

  • Do not dispose of the row obtained from RowChangedEventArgs/RowCreatedEventArgs/RowDeletedEventArgs. The same row could be used for calling more than one event handler.
  • The subscription must be performed on the MCT within QueuedTask.Run(). In the previous example, the Subscribe call is wrapped in QueuedTask.Run, but the actual code of the event handler is not. This is because the event handler will always be invoked on the MCT.
  • It is advisable to unsubscribe at an appropriate point in the code using the subscription token obtained when Subscribe is called.

Developing with ArcGIS Pro

    Migration


Framework

    Add-ins

    Configurations

    Customization

    Styling


Arcade


Content


CoreHost


DataReviewer


Editing


Geodatabase

    3D Analyst Data

    Plugin Datasources

    Topology

    Linear Referencing

    Object Model Diagram


Geometry

    Relational Operations


Geoprocessing


Knowledge Graph


Layouts

    Reports


Map Authoring

    3D Analyst

    CIM

    Graphics

    Scene

    Stream

    Voxel


Map Exploration

    Map Tools


Networks

    Network Diagrams


Parcel Fabric


Raster


Sharing


Tasks


Workflow Manager Classic


Workflow Manager


Reference

Clone this wiki locally