ProConcepts Editing

Uma Harano edited this page Jun 26, 2018 · 23 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 <arcgisprosdk@esri.com>
Organization:  Esri, http://www.esri.com
Date:          6/7/2018
ArcGIS Pro:    2.2
Visual Studio: 2015, 2017

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

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

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. They 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 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 section in the community samples contains many examples of sketch tools.

Construction Tools

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 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.

Enable and Disable Editing UI

Editing controls key off the esri_editing_EditingPossibleCondition DAML condition to determine their enabled/disabled status. Editing is typically disabled on the UI if the active map or scene does not contain any editable data. If you have custom editing tools that you likewise want to be enabled and disabled in concert with the Pro editing tools then add the esri_editing_EditingPossibleCondition condition to their DAML declaration in the Add-in Config.daml.

<button id="Module1_CustomEditingButton" caption="Show Editing Status" ...
                condition="esri_editing_EditingPossibleCondition">

3rd party add-ins can activate the underlying esri_editing_editorDisabled DAML state id if they also need to set the esri_editing_EditingPossibleCondition condition false (for example, an add-in needs to enforce some custom workflow requirements or user editing permissions that require editing be disabled on the UI). To re-enable editing status on the UI that they have previously disabled, add-ins deactivate esri_editing_editorDisabled.

  //Toggle Pro editing enabled/disabled state on the UI via esri_editing_editorDisabled
  if (!FrameworkApplication.State.Contains("esri_editing_editorDisabled")) {
     FrameworkApplication.State.Activate("esri_editing_editorDisabled");//Disable
  }
  else {
    FrameworkApplication.State.Deactivate("esri_editing_editorDisabled");//re-enable
  }

Note: esri_editing_editorDisabled is not an override for the editor state set by the Pro Application. For example, if the Pro application had disabled editing because there was no editable data present, deactivating esri_editing_editorDisabled will not make a difference. It is for use in custom editing workflow scenarios only.

Performing Edits in Pro

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

Edit operations perform 3 key functions:

  1. Execute the operations against underlying datastores (edit operations can span multiple datastores)
  2. Consolidate multiple edits into a single operation
  3. 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. Creates are always executed first, with updates second, deletes third, then the various topological and transformation operators like clip, reshape, scale, move, rotate, etc. 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 be failed. The value in the "ErrorMessage" property will be applicable to the first operation that 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. 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, Cut, 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, 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
   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.

Chaining Edit Operations

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

Edit Operations and Utility Network Associations

In Utility Network Topology, features are related to each other through geometric coincidence (features co-located at the same physical x, y) or via association mechanisms (of which Pro supports three associations). An association is used when features to be "associated" are not necessarily geometrically coincident. One feature can have many associations (eg 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, topic 19603. 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 a ArcGIS.Desktop.Editing.RowToken, topic 19317, when constructing the association RowHandles.

Edit Operation provides a series of CreateEx overloads that return a RowToken immediately 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 transToken = editOp.CreateEx(transformerLayer, transAttrib);
   RowToken poleToken = editOp.CreateEx(poleLayer, poleAttrib);
   
   //Create association using the row tokens not features
   var poleAttachment = new StructuralAttachmentAssociationDescription(
        new RowHandle(poleToken), new RowHandle(transToken));

   editOp.Create(poleAttachment);

   //Execute the EditOperation
   editOp.ExecuteAsync();

Refer to Utility Network Associations for more detail.

Edit Operation Callback

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). There may also be cases where you need to edit datasets that have not been added to the current project (e.g. updating a proprietary log or audit table). In these scenarios, you will need to perform your edits within an EditOperation.Callback() method, topic 9494. Note: At Pro version 2.1, there is an additional scenario for EditOperation.Callback; namely editing of annotation text and text properties.

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 is not executed when the call back is declared. When EditOperation.ExecuteAsync (or EditOperation.Execute) is called, 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
  }

Edit Operation Callback Abort

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

Layer Editability

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.

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 the 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" 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.

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

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 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.

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. New at ArcGIS Pro 2.2 is an extension method allowing template creation without needing the CIM. The CreateTemplate 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.

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

This example creates a new template using the new 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);

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

Annotation Feature Editing

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.

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 = Snapping.GetOptions(myMap);
snapOptions.SnapToSketchEnabled = true;
snapOptions.XYTolerance = 100;
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) {
         //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 Operations and Long and Short Transactions

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.

EditOperationType

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).

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

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

Characteristics by dataset by participating EditOperationType

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) Partial* No No SHORT
Feature Service (Standard) Partial* No No SHORT
Feature Service (Branch, Default) Yes No No SHORT
Feature Service (Branch, Named) Yes Yes Yes LONG

*There is no Cancel Edit for feature creates

Edit Sessions

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.

Save and Discard Edits

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:

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.

Checking for Unsaved Edits

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.

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

EditCompletedEvent

Add-ins can subscribe to the ArcGIS.Desktop.Editing.Events.EditCompletedEvent to be notifed when:

  1. Any create, modify, and delete has completed (e.g. after an editOperation.Execute)
  2. Any Undo or Redo has completed.
  3. Any Save or Discard has completed (and, hence, closing any current edit sessions)
  4. Any Reconcile or Post has completed.
  5. 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

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 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 transation)
  • Validate row attributes that have been changed
  • Cancel row transactions* (e.g. if they fail validation)
  • Custom logging/audit trail

*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.

A RowChangedEventsArgs, topic 9815, with a Row property that is the row being created, modified, or deleted, is passed in as an argument to your RowEvent handler. You can query the Row using Row.HasValueChanged to determine which values have changed. 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);

If you need to edit additional tables within the RowEvent you must use the ArcGIS.Core.Data API to edit the tables directly. Do not use a new edit operation to create or modify features or rows in your RowEvent callbacks. RowEvent callbacks are always called on the QueuedTask so there is no need to wrap your code within a QueuedTask.Run lambda.

Keep in mind that any modifications performed within the RowEvent handler also cause cascaded events to be generated. 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, consider tracking that feature's object id to ignore the corresponding RowChangedEvent your attribute change will generate. 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). Also, do not dispose the row obtained from the RowChangedEventArgs. The same row can be used for calling more than one event handler.

Home

Developing with ArcGIS Pro


Framework

    Add-ins

    Configurations

    Customization

    Styling


Content


CoreHost


DataReviewer


Editing


Geodatabase


Geometry

    Relational Operations


Geoprocessing


Layouts


Map Authoring


Map Exploration

    Map Tools


Raster


Sharing


Tasks


Utility Network


Workflow Manager


Reference

    2.0 Migration

Clone this wiki locally
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.
Press h to open a hovercard with more details.