Skip to content

ProSnippets Editing

UmaHarano edited this page Nov 6, 2023 · 19 revisions
Language:              C#  
Subject:               Editing  
Contributor:           ArcGIS Pro SDK Team <arcgisprosdk@esri.com>  
Organization:          esri, http://www.esri.com  
Date:                  10/16/2023  
ArcGIS Pro:            3.2  
Visual Studio:         2022  
.NET Target Framework: .Net 6  

Edit Operation Methods

Edit Operation - check for actions before Execute

// Some times when using EditOperation.Modify you can unknowingly be attempting to set
//  an attribute to value 
//  setting 
// In this scenario the Modify action will detect that nothing is required
// and do nothing. Because no actions have occurred, the
// Consequently the Execute operation will fail. 
if (!opEdit.IsEmpty)
  opEdit.Execute();

Edit Operation Create Features

var createFeatures = new EditOperation();
createFeatures.Name = "Create Features";
//Create a feature with a polygon
var token = createFeatures.Create(featureLayer, polygon);
if (createFeatures.IsSucceeded)
{
  // token.ObjectID wll be populated with the objectID of the created feature after Execute has been successful
}
//Do a create features and set attributes
var attributes = new Dictionary<string, object>();
attributes.Add("SHAPE", polygon);
attributes.Add("NAME", "Corner Market");
attributes.Add("SIZE", 1200.5);
attributes.Add("DESCRIPTION", "Corner Market");

createFeatures.Create(featureLayer, attributes);

//Create features using the current template
//Must be within a MapTool
createFeatures.Create(this.CurrentTemplate, polygon);

//Execute to execute the operation
//Must be called within QueuedTask.Run

if (!createFeatures.IsEmpty)
{
  createFeatures.Execute(); //Execute will return true if the operation was successful and false if not.
}

//or use async flavor
//await createFeatures.ExecuteAsync();

Create a feature using the current template

var myTemplate = ArcGIS.Desktop.Editing.Templates.EditingTemplate.Current;

//Create edit operation and execute
var op = new ArcGIS.Desktop.Editing.EditOperation();
op.Name = "Create my feature";
op.Create(myTemplate, geometry);
if (!op.IsEmpty)
{
  var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Create feature from a modified inspector

var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();
insp.Load(layer, 86);

ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
{
  // modify attributes if necessary
  // insp["Field1"] = newValue;

  //Create new feature from an existing inspector (copying the feature)
  var createOp = new ArcGIS.Desktop.Editing.EditOperation();
  createOp.Name = "Create from insp";
  createOp.Create(insp.MapMember, insp.ToDictionary(a => a.FieldName, a => a.CurrentValue));
  if (!createOp.IsEmpty)
  {
    var result = createOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Create features from a CSV file

//Run on MCT
ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
{
  //Create the edit operation
  var createOperation = new ArcGIS.Desktop.Editing.EditOperation();
  createOperation.Name = "Generate points";
  createOperation.SelectNewFeatures = false;

  // determine the shape field name - it may not be 'Shape' 
  string shapeField = layer.GetFeatureClass().GetDefinition().GetShapeField();

  //Loop through csv data
  foreach (var item in csvData)
  {

    //Create the point geometry
    ArcGIS.Core.Geometry.MapPoint newMapPoint =
        ArcGIS.Core.Geometry.MapPointBuilderEx.CreateMapPoint(item.X, item.Y);

    // include the attributes via a dictionary
    var atts = new Dictionary<string, object>();
    atts.Add("StopOrder", item.StopOrder);
    atts.Add("FacilityID", item.FacilityID);
    atts.Add(shapeField, newMapPoint);

    // queue feature creation
    createOperation.Create(layer, atts);
  }

  // execute the edit (feature creation) operation
  if (createOperation.IsEmpty)
  {
    return createOperation.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
  else
    return false;
});

Edit Operation Create row in a table using a table template

var tableTemplate = standaloneTable.GetTemplates().FirstOrDefault();
var createRow = new EditOperation();
createRow.Name = "Create a row in a table";
//Creating a new row in a standalone table using the table template of your choice
createRow.Create(tableTemplate);
if (!createRow.IsEmpty)
{
  var result = createRow.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Edit Operation Clip Features

var clipFeatures = new EditOperation();
clipFeatures.Name = "Clip Features";
clipFeatures.Clip(featureLayer, oid, clipPoly, ClipMode.PreserveArea);
//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!clipFeatures.IsEmpty)
{
  var result = clipFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await clipFeatures.ExecuteAsync();

Edit Operation Cut Features

var select = MapView.Active.SelectFeatures(clipPoly);

var cutFeatures = new EditOperation();
cutFeatures.Name = "Cut Features";
cutFeatures.Split(featureLayer, oid, cutLine);

//Cut all the selected features in the active view
//Select using a polygon (for example)
//at 2.x - var kvps = MapView.Active.SelectFeatures(polygon).Select(
//      k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//cutFeatures.Split(kvps, cutLine);
var sset = MapView.Active.SelectFeatures(polygon);
cutFeatures.Split(sset, cutLine);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!cutFeatures.IsEmpty)
{
  var result = cutFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await cutFeatures.ExecuteAsync();

Edit Operation Delete Features

var deleteFeatures = new EditOperation();
deleteFeatures.Name = "Delete Features";
var table = MapView.Active.Map.StandaloneTables[0];
//Delete a row in a standalone table
deleteFeatures.Delete(table, oid);

//Delete all the selected features in the active view
//Select using a polygon (for example)
//at 2.x - var selection = MapView.Active.SelectFeatures(polygon).Select(
//      k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//deleteFeatures.Delete(selection);
var selection = MapView.Active.SelectFeatures(polygon);
deleteFeatures.Delete(selection);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!deleteFeatures.IsEmpty)
{
  var result = deleteFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await deleteFeatures.ExecuteAsync();

Edit Operation Duplicate Features

{
  var duplicateFeatures = new EditOperation();
  duplicateFeatures.Name = "Duplicate Features";

  //Duplicate with an X and Y offset of 500 map units
  //At 2.x duplicateFeatures.Duplicate(featureLayer, oid, 500.0, 500.0, 0.0);

  //Execute to execute the operation
  //Must be called within QueuedTask.Run
  var insp2 = new Inspector();
  insp2.Load(featureLayer, oid);
  var geom = insp2["SHAPE"] as Geometry;

  var rtoken = duplicateFeatures.Create(insp2.MapMember, insp2.ToDictionary(a => a.FieldName, a => a.CurrentValue));
  if (!duplicateFeatures.IsEmpty)
  {
    if (duplicateFeatures.Execute())//Execute and ExecuteAsync will return true if the operation was successful and false if not
    {
      var modifyOp = duplicateFeatures.CreateChainedOperation();
      modifyOp.Modify(featureLayer, (long)rtoken.ObjectID, GeometryEngine.Instance.Move(geom, 500.0, 500.0));
      if (!modifyOp.IsEmpty)
      {
        var result = modifyOp.Execute();
      }
    }
  }
}

Edit Operation Explode Features

var explodeFeatures = new EditOperation();
explodeFeatures.Name = "Explode Features";

//Take a multipart and convert it into one feature per part
//Provide a list of ids to convert multiple
explodeFeatures.Explode(featureLayer, new List<long>() { oid }, true);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!explodeFeatures.IsEmpty)
{
  var result = explodeFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await explodeFeatures.ExecuteAsync();

Edit Operation Merge Features

var mergeFeatures = new EditOperation();
mergeFeatures.Name = "Merge Features";

//Merge three features into a new feature using defaults
//defined in the current template
//At 2.x -
//mergeFeatures.Merge(this.CurrentTemplate as EditingFeatureTemplate, featureLayer, new List<long>() { 10, 96, 12 });
mergeFeatures.Merge(this.CurrentTemplate as EditingRowTemplate, featureLayer, new List<long>() { 10, 96, 12 });

//Merge three features into a new feature in the destination layer
mergeFeatures.Merge(destinationLayer, featureLayer, new List<long>() { 10, 96, 12 });

//Use an inspector to set the new attributes of the merged feature
var inspector = new Inspector();
inspector.Load(featureLayer, oid);//base attributes on an existing feature
//change attributes for the new feature
inspector["NAME"] = "New name";
inspector["DESCRIPTION"] = "New description";

//Merge features into a new feature in the same layer using the
//defaults set in the inspector
mergeFeatures.Merge(featureLayer, new List<long>() { 10, 96, 12 }, inspector);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!mergeFeatures.IsEmpty)
{
  var result = mergeFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await mergeFeatures.ExecuteAsync();

Edit Operation Modify single feature

var modifyFeature = new EditOperation();
modifyFeature.Name = "Modify a feature";

//use an inspector
var modifyInspector = new Inspector();
modifyInspector.Load(featureLayer, oid);//base attributes on an existing feature

//change attributes for the new feature
modifyInspector["SHAPE"] = polygon;//Update the geometry
modifyInspector["NAME"] = "Updated name";//Update attribute(s)

modifyFeature.Modify(modifyInspector);

//update geometry and attributes using overload
var featureAttributes = new Dictionary<string, object>();
featureAttributes["NAME"] = "Updated name";//Update attribute(s)
modifyFeature.Modify(featureLayer, oid, polygon, featureAttributes);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!modifyFeature.IsEmpty)
{
  var result = modifyFeature.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await modifyFeatures.ExecuteAsync();

Edit Operation Modify multiple features

//Search by attribute
var queryFilter = new QueryFilter();
queryFilter.WhereClause = "OBJECTID < 1000000";
//Create list of oids to update
var oidSet = new List<long>();
using (var rc = featureLayer.Search(queryFilter))
{
  while (rc.MoveNext())
  {
    using (var record = rc.Current)
    {
      oidSet.Add(record.GetObjectID());
    }
  }
}

//create and execute the edit operation
var modifyFeatures = new EditOperation();
modifyFeatures.Name = "Modify features";
modifyFeatures.ShowProgressor = true;

var muultipleFeaturesInsp = new Inspector();
muultipleFeaturesInsp.Load(featureLayer, oidSet);
muultipleFeaturesInsp["MOMC"] = 24;
modifyFeatures.Modify(muultipleFeaturesInsp);
if (!modifyFeatures.IsEmpty)
{
  var result = modifyFeatures.ExecuteAsync(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Search for layer features and update a field

ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
{
  //find layer
  var disLayer = ArcGIS.Desktop.Mapping.MapView.Active.Map.FindLayers("Distribution mains").FirstOrDefault() as BasicFeatureLayer;

  //Search by attribute
  var filter = new ArcGIS.Core.Data.QueryFilter();
  filter.WhereClause = "CONTRACTOR = 'KCGM'";

  var oids = new List<long>();
  using (var rc = disLayer.Search(filter))
  {
    //Create list of oids to update
    while (rc.MoveNext())
    {
      using (var record = rc.Current)
      {
        oidSet.Add(record.GetObjectID());
      }
    }
  }

  //Create edit operation 
  var modifyOp = new ArcGIS.Desktop.Editing.EditOperation();
  modifyOp.Name = "Update date";

  // load features into inspector and update field
  var dateInsp = new ArcGIS.Desktop.Editing.Attributes.Inspector();
  dateInsp.Load(disLayer, oids);
  dateInsp["InspDate"] = "9/21/2013";

  // modify and execute
  modifyOp.Modify(insp);
  if (!modifyOp.IsEmpty)
  {
    var result = modifyOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Move features

//Get all of the selected ObjectIDs from the layer.
var firstLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
var selectionfromMap = firstLayer.GetSelection();

// set up a dictionary to store the layer and the object IDs of the selected features
var selectionDictionary = new Dictionary<MapMember, List<long>>();
selectionDictionary.Add(firstLayer as MapMember, selectionfromMap.GetObjectIDs().ToList());

var moveFeature = new EditOperation();
moveFeature.Name = "Move features";
//at 2.x - moveFeature.Move(selectionDictionary, 10, 10);  //specify your units along axis to move the geometry
moveFeature.Move(SelectionSet.FromDictionary(selectionDictionary), 10, 10);  //specify your units along axis to move the geometry
if (!moveFeature.IsEmpty)
{
  var result = moveFeature.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Move feature to a specific coordinate

//Get all of the selected ObjectIDs from the layer.
var abLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
var mySelection = abLayer.GetSelection();
var selOid = mySelection.GetObjectIDs().FirstOrDefault();

var moveToPoint = new MapPointBuilderEx(1.0, 2.0, 3.0, 4.0, MapView.Active.Map.SpatialReference); //can pass in coordinates.

var modifyFeatureCoord = new EditOperation();
modifyFeatureCoord.Name = "Move features";
modifyFeatureCoord.Modify(abLayer, selOid, moveToPoint.ToGeometry());  //Modify the feature to the new geometry 
if (!modifyFeatureCoord.IsEmpty)
{
  var result = modifyFeatureCoord.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Edit Operation Planarize Features

// note - EditOperation.Planarize requires a standard license. 
//  An exception will be thrown if Pro is running under a basic license. 

var planarizeFeatures = new EditOperation();
planarizeFeatures.Name = "Planarize Features";

//Planarize one or more features
planarizeFeatures.Planarize(featureLayer, new List<long>() { oid });

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!planarizeFeatures.IsEmpty)
{
  var result = planarizeFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await planarizeFeatures.ExecuteAsync();

Edit Operation ParallelOffset

//Create parrallel features from the selected features

//find the roads layer
var roadsLayer = MapView.Active.Map.FindLayers("Roads").FirstOrDefault();

//instatiate paralleloffset builder and set parameters
var parOffsetBuilder = new ParallelOffset.Builder()
{
  Selection = MapView.Active.Map.GetSelection(),
  Template = roadsLayer.GetTemplate("Freeway"),
  Distance = 200,
  Side = ParallelOffset.SideType.Both,
  Corner = ParallelOffset.CornerType.Mitered,
  Iterations = 1,
  AlignConnected = false,
  CopyToSeparateFeatures = false,
  RemoveSelfIntersectingLoops = true
};

//create editoperation and execute
var parallelOp = new EditOperation();
parallelOp.Create(parOffsetBuilder);
if (!parallelOp.IsEmpty)
{
  var result = parallelOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Edit Operation Reshape Features

var reshapeFeatures = new EditOperation();
reshapeFeatures.Name = "Reshape Features";

reshapeFeatures.Reshape(featureLayer, oid, modifyLine);

//Reshape a set of features that intersect some geometry....

//at 2.x - var selFeatures = MapView.Active.GetFeatures(modifyLine).Select(
//    k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//reshapeFeatures.Reshape(selFeatures, modifyLine);

reshapeFeatures.Reshape(MapView.Active.GetFeatures(modifyLine), modifyLine);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!reshapeFeatures.IsEmpty)
{
  var result = reshapeFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await reshapeFeatures.ExecuteAsync();

Edit Operation Rotate Features

var rotateFeatures = new EditOperation();
rotateFeatures.Name = "Rotate Features";

//Rotate works on a selected set of features
//Get all features that intersect a polygon

//at 2.x - var rotateSelection = MapView.Active.GetFeatures(polygon).Select(
//    k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//rotateFeatures.Rotate(rotateSelection, origin, Math.PI / 2);

//Rotate selected features 90 deg about "origin"
rotateFeatures.Rotate(MapView.Active.GetFeatures(polygon), origin, Math.PI / 2);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!rotateFeatures.IsEmpty)
{
  var result = rotateFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await rotateFeatures.ExecuteAsync();

Edit Operation Scale Features

var scaleFeatures = new EditOperation();
scaleFeatures.Name = "Scale Features";

//Rotate works on a selected set of features

//var scaleSelection = MapView.Active.GetFeatures(polygon).Select(
//    k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//scaleFeatures.Scale(scaleSelection, origin, 2.0, 2.0, 0.0);

//Scale the selected features by 2.0 in the X and Y direction
scaleFeatures.Scale(MapView.Active.GetFeatures(polygon), origin, 2.0, 2.0, 0.0);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!scaleFeatures.IsEmpty)
{
  var result = scaleFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await scaleFeatures.ExecuteAsync();

Edit Operation Split Features

var splitFeatures = new EditOperation();
splitFeatures.Name = "Split Features";

var splitPoints = new List<MapPoint>() { mp1, mp2, mp3 };

//Split the feature at 3 points
splitFeatures.Split(featureLayer, oid, splitPoints);

// split using percentage
var splitByPercentage = new SplitByPercentage() { Percentage = 33, SplitFromStartPoint = true };
splitFeatures.Split(featureLayer, oid, splitByPercentage);

// split using equal parts
var splitByEqualParts = new SplitByEqualParts() { NumParts = 3 };
splitFeatures.Split(featureLayer, oid, splitByEqualParts);

// split using single distance
var splitByDistance = new SplitByDistance() { Distance = 27.3, SplitFromStartPoint = false };
splitFeatures.Split(featureLayer, oid, splitByDistance);

// split using varying distance
var distances = new List<double>() { 12.5, 38.2, 89.99 };
var splitByVaryingDistance = new SplitByVaryingDistance() { Distances = distances, SplitFromStartPoint = true, ProportionRemainder = true };
splitFeatures.Split(featureLayer, oid, splitByVaryingDistance);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!splitFeatures.IsEmpty)
{
  var result = splitFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await splitAtPointsFeatures.ExecuteAsync();

Edit Operation Transform Features

var transformFeatures = new EditOperation();
transformFeatures.Name = "Transform Features";

//Transform a selected set of features
//At 2.x - var transformSelection = MapView.Active.GetFeatures(polygon).Select(
//    k => new KeyValuePair<MapMember, List<long>>(k.Key as MapMember, k.Value));
//transformFeatures.Transform(transformSelection, linkLayer);
////Transform just a layer
//transformFeatures.Transform(featureLayer, linkLayer);
////Perform an affine transformation
//transformFeatures.TransformAffine(featureLayer, linkLayer);

var affine_transform = new TransformByLinkLayer()
{
  LinkLayer = linkLayer,
  TransformType = TransformMethodType.Affine //TransformMethodType.Similarity
};
//Transform a selected set of features
transformFeatures.Transform(MapView.Active.GetFeatures(polygon), affine_transform);
//Perform an affine transformation
transformFeatures.Transform(featureLayer, affine_transform);

//Execute to execute the operation
//Must be called within QueuedTask.Run
if (!transformFeatures.IsEmpty)
{
  var result = transformFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await transformFeatures.ExecuteAsync();

Edit Operation Rubbersheet Features

//Perform rubbersheet by geometries
var rubbersheetMethod = new RubbersheetByGeometries()
{
  RubbersheetType = RubbersheetMethodType.Linear, //The RubbersheetType can be Linear of NearestNeighbor
  LinkLines = linkLines, //IEnumerable list of link lines (polylines)
  AnchorPoints = anchorPoints, //IEnumerable list of anchor points (map points)
  LimitedAdjustmentAreas = limitedAdjustmentAreas //IEnumerable list of limited adjustment areas (polygons)
};

var rubbersheetOp = new EditOperation();
//Performs linear rubbersheet transformation on the features belonging to "layer" that fall within the limited adjustment areas
rubbersheetOp.Rubbersheet(layer, rubbersheetMethod);
//Execute the operation
if (!rubbersheetOp.IsEmpty)
{
  var result = rubbersheetOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//Alternatively, you can also perform rubbersheet by layer
var rubbersheetMethod2 = new RubbersheetByLayers()
{
  RubbersheetType = RubbersheetMethodType.NearestNeighbor, //The RubbersheetType can be Linear of NearestNeighbor
  LinkLayer = linkLayer,
  AnchorPointLayer = anchorPointsLayer,
  LimitedAdjustmentAreaLayer = limitedAdjustmentAreaLayer
};

//Performs nearest neighbor rubbersheet transformation on the features belonging to "layer" that fall within the limited adjustment areas
rubbersheetOp.Rubbersheet(layer, rubbersheetMethod2);
if (!rubbersheetOp.IsEmpty)
{
  //Execute the operation
  var result = rubbersheetOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Edit Operation Perform a Clip, Cut, and Planarize

//Multiple operations can be performed by a single
//edit operation.
var clipCutPlanarizeFeatures = new EditOperation();
clipCutPlanarizeFeatures.Name = "Clip, Cut, and Planarize Features";
clipCutPlanarizeFeatures.Clip(featureLayer, oid, clipPoly);
clipCutPlanarizeFeatures.Split(featureLayer, oid, cutLine);
clipCutPlanarizeFeatures.Planarize(featureLayer, oid);

if (!clipCutPlanarizeFeatures.IsEmpty)
{
  //Note: An edit operation is a single transaction. 
  //Execute the operations (in the order they were declared)
  clipCutPlanarizeFeatures.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

//or use async flavor
//await clipCutPlanarizeFeatures.ExecuteAsync();

Edit Operation Chain Edit Operations

//Chaining operations is a special case. Use "Chained Operations" when you require multiple transactions 
//to be undo-able with a single "Undo".

//The most common use case for operation chaining is creating a feature with an attachement. 
//Adding an attachment requires the object id (of a new feature) has already been created. 
var editOperation1 = new EditOperation();
editOperation1.Name = string.Format("Create point in '{0}'", CurrentTemplate.Layer.Name);

long newFeatureID = -1;
//The Create operation has to execute so we can get an object_id
var token2 = editOperation1.Create(this.CurrentTemplate, polygon);

//Must be within a QueuedTask
editOperation1.Execute(); //Note: Execute and ExecuteAsync will return true if the operation was successful and false if not
if (editOperation1.IsSucceeded)
{
  newFeatureID = (long)token2.ObjectID;
  //Now, because we have the object id, we can add the attachment.  As we are chaining it, adding the attachment 
  //can be undone as part of the "Undo Create" operation. In other words, only one undo operation will show on the 
  //Pro UI and not two.
  var editOperation2 = editOperation1.CreateChainedOperation();
  //Add the attachement using the new feature id
  editOperation2.AddAttachment(this.CurrentTemplate.Layer, newFeatureID, @"C:\data\images\Hydrant.jpg");
  //Execute the chained edit operation. editOperation1 and editOperation2 show up as a single Undo operation
  //on the UI even though we had two transactions
  editOperation2.Execute();
}

Edit Operation add attachment via RowToken

//ArcGIS Pro 2.5 extends the EditOperation.AddAttachment method to take a RowToken as a paramter.
//This allows you to create a feature, using EditOperation.CreateEx, and add an attachment in one transaction.

var editOpAttach = new EditOperation();
editOperation1.Name = string.Format("Create point in '{0}'", CurrentTemplate.Layer.Name);

var attachRowToken = editOpAttach.Create(this.CurrentTemplate, polygon);
editOpAttach.AddAttachment(attachRowToken, @"c:\temp\image.jpg");

//Must be within a QueuedTask
if (!editOpAttach.IsEmpty)
{
  var result = editOpAttach.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Order edits sequentially

// perform an edit and then a split as one operation.
QueuedTask.Run(() =>
{
  var queryFilter = new QueryFilter();
  queryFilter.WhereClause = "OBJECTID = " + oid.ToString();

  // create an edit operation and name.
  var op = new EditOperation();
  op.Name = "modify followed by split";
  // set the ExecuteMOde
  op.ExecuteMode = ExecuteModeType.Sequential;

  using (var rowCursor = fc.Search(queryFilter, false))
  {
    while (rowCursor.MoveNext())
    {
      using (var feature = rowCursor.Current as Feature)
      {
        op.Modify(feature, "NAME", newName);
      }
    }
  }

  op.Split(layer, oid, splitLine);
  if (!op.IsEmpty)
  {
    bool result = op.Execute();
  }
  // else
  //  The operation doesn't make any changes to the database so if executed it will fail
});

SetOnUndone, SetOnRedone, SetOnComitted

// SetOnUndone, SetOnRedone and SetOnComittedManage can be used to manage 
// external actions(such as writing to a log table) that are associated with 
// each edit operation.

//get selected feature and update attribute
var selectedFeatures = MapView.Active.Map.GetSelection();
var testInspector = new Inspector();
testInspector.Load(selectedFeatures.ToDictionary().Keys.First(), selectedFeatures.ToDictionary().Values.First());
testInspector["Name"] = "test";

//create and execute the edit operation
var updateTestField = new EditOperation();
updateTestField.Name = "Update test field";
updateTestField.Modify(insp);

//actions for SetOn...
updateTestField.SetOnUndone(() =>
{
  //Sets an action that will be called when this operation is undone.
  Debug.WriteLine("Operation is undone");
});

updateTestField.SetOnRedone(() =>
{
  //Sets an action that will be called when this editoperation is redone.
  Debug.WriteLine("Operation is redone");
});

updateTestField.SetOnComitted((bool b) => //called on edit session save(true)/discard(false).
{
  // Sets an action that will be called when this editoperation is committed.
  Debug.WriteLine("Operation is committed");
});

if (!updateTestField.IsEmpty)
{
  var result = updateTestField.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
}

Enable Editing

Enable Editing

// if not editing
if (!Project.Current.IsEditingEnabled)
{
  var res = MessageBox.Show("You must enable editing to use editing tools. Would you like to enable editing?",
                                                        "Enable Editing?", System.Windows.MessageBoxButton.YesNoCancel);
  if (res == System.Windows.MessageBoxResult.No ||
                res == System.Windows.MessageBoxResult.Cancel)
  {
    return;
  }
  Project.Current.SetIsEditingEnabledAsync(true);
}

Disable Editing

// if editing
if (Project.Current.IsEditingEnabled)
{
  var res = MessageBox.Show("Do you want to disable editing? Editing tools will be disabled",
                                                         "Disable Editing?", System.Windows.MessageBoxButton.YesNoCancel);
  if (res == System.Windows.MessageBoxResult.No ||
                res == System.Windows.MessageBoxResult.Cancel)
  {
    return;
  }

  //we must check for edits
  if (Project.Current.HasEdits)
  {
    res = MessageBox.Show("Save edits?", "Save Edits?", System.Windows.MessageBoxButton.YesNoCancel);
    if (res == System.Windows.MessageBoxResult.Cancel)
      return;
    else if (res == System.Windows.MessageBoxResult.No)
      Project.Current.DiscardEditsAsync();
    else
    {
      Project.Current.SaveEditsAsync();
    }
  }
  Project.Current.SetIsEditingEnabledAsync(false);
}

Row Events

Subscribe to Row Events

protected void SubscribeRowEvent()
{
  QueuedTask.Run(() =>
  {
    //Listen for row events on a layer
    var featLayer = MapView.Active.GetSelectedLayers().First() as FeatureLayer;
    var layerTable = featLayer.GetTable();

    //subscribe to row events
    var rowCreateToken = RowCreatedEvent.Subscribe(OnRowCreated, layerTable);
    var rowChangeToken = RowChangedEvent.Subscribe(OnRowChanged, layerTable);
    var rowDeleteToken = RowDeletedEvent.Subscribe(OnRowDeleted, layerTable);
  });
}

protected void OnRowCreated(RowChangedEventArgs args)
{
}

protected void OnRowChanged(RowChangedEventArgs args)
{
}

protected void OnRowDeleted(RowChangedEventArgs args)
{
}

Create a record in a separate table in the Map within Row Events

// Use the EditOperation in the RowChangedEventArgs to append actions to be executed. 
//  Your actions will become part of the operation and combined into one item on the undo stack

private void HookRowCreatedEvent()
{
  // subscribe to the RowCreatedEvent
  Table table = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault().GetTable();
  RowCreatedEvent.Subscribe(MyRowCreatedEvent, table);
}

private void MyRowCreatedEvent(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  // get the edit operation
  var parentEditOp = args.Operation;

  // set up some attributes
  var attribs = new Dictionary<string, object> { };
  attribs.Add("Layer", "Parcels");
  attribs.Add("Description", "OID: " + args.Row.GetObjectID().ToString() + " " + DateTime.Now.ToShortTimeString());

  //create a record in an audit table
  var sTable = MapView.Active.Map.FindStandaloneTables("EditHistory").First();
  var table = sTable.GetTable();
  parentEditOp.Create(table, attribs);
}

Create a record in a separate table within Row Events

// Use the EditOperation in the RowChangedEventArgs to append actions to be executed. 
//  Your actions will become part of the operation and combined into one item on the undo stack

private void HookCreatedEvent()
{
  // subscribe to the RowCreatedEvent
  Table table = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault().GetTable();
  RowCreatedEvent.Subscribe(OnRowCreatedEvent, table);
}

private void OnRowCreatedEvent(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  // update a separate table not in the map when a row is created
  // You MUST use the ArcGIS.Core.Data API to edit the table. Do NOT
  // use a new edit operation in the RowEvent callbacks
  try
  {
    // get the edit operation
    var parentEditOp = args.Operation;

    // set up some attributes
    var attribs = new Dictionary<string, object> { };
    attribs.Add("Description", "OID: " + args.Row.GetObjectID().ToString() + " " + DateTime.Now.ToShortTimeString());

    // update Notes table with information about the new feature
    using (var geoDatabase = new Geodatabase(new FileGeodatabaseConnectionPath(new Uri(Project.Current.DefaultGeodatabasePath))))
    {
      using (var table = geoDatabase.OpenDataset<Table>("Notes"))
      {
        parentEditOp.Create(table, attribs);
      }
    }
  }
  catch (Exception e)
  {
    MessageBox.Show($@"Error in OnRowCreated for OID: {args.Row.GetObjectID()} : {e.ToString()}");
  }
}

Modify a record within Row Events - using Row.Store

private void HookRowChangedEvent()
{
  // subscribe to the RowChangedEvent
  Table table = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault().GetTable();
  RowChangedEvent.Subscribe(OnRowChangedEvent, table);
}

private Guid _currentRowChangedGuid = Guid.Empty;
protected void OnRowChangedEvent(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  var row = args.Row;

  // check for re-entry  (only if row.Store is called)
  if (_currentRowChangedGuid == args.Guid)
    return;

  var fldIdx = row.FindField("POLICE_DISTRICT");
  if (fldIdx != -1)
  {
    //Validate any change to �police district�
    //   cancel the edit if validation on the field fails
    if (row.HasValueChanged(fldIdx))
    {
      // cancel edit with invalid district (5)
      var value = row["POLICE_DISTRICT"].ToString();
      if (value == "5")
      {
        //Cancel edits with invalid �police district� values
        args.CancelEdit($"Police district {row["POLICE_DISTRICT"]} is invalid");
      }
    }

    // update the description field
    row["Description"] = "Row Changed";

    //  this update with cause another OnRowChanged event to occur
    //  keep track of the row guid to avoid recursion
    _currentRowChangedGuid = args.Guid;
    row.Store();
    _currentRowChangedGuid = Guid.Empty;
  }
}

Modify a record within Row Events - using EditOperation.Modify

private void HookChangedEvent()
{
  // subscribe to the RowChangedEvent
  Table table = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault().GetTable();
  RowChangedEvent.Subscribe(MyRowChangedEvent, table);
}

private void MyRowChangedEvent(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  //example of modifying a field on a row that has been created
  var parentEditOp = args.Operation;

  // avoid recursion
  if (_lastEdit != args.Guid)
  {
    //update field on change
    parentEditOp.Modify(args.Row, "ZONING", "New");

    _lastEdit = args.Guid;
  }
}

Determine if Geometry Changed while editing

private static FeatureLayer featureLayer;
private static void DetermineGeometryChange()
{
  featureLayer = MapView.Active?.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
  if (featureLayer == null)
    return;

  QueuedTask.Run(() =>
  {
    //Listen to the RowChangedEvent that occurs when a Row is changed.
    ArcGIS.Desktop.Editing.Events.RowChangedEvent.Subscribe(OnRowChangedEvent2, featureLayer.GetTable());
  });
}
private static void OnRowChangedEvent2(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  //Get the layer's definition
  var lyrDefn = featureLayer.GetFeatureClass().GetDefinition();
  //Get the shape field of the feature class
  string shapeField = lyrDefn.GetShapeField();
  //Index of the shape field
  var shapeIndex = lyrDefn.FindField(shapeField);
  //Original geometry of the modified row
  var geomOrig = args.Row.GetOriginalValue(shapeIndex) as Geometry;
  //New geometry of the modified row
  var geomNew = args.Row[shapeIndex] as Geometry;
  //Compare the two
  bool shapeChanged = geomOrig.IsEqual(geomNew);
}

Cancel a delete

public static void StopADelete()
{
  // subscribe to the RowDeletedEvent for the appropriate table
  Table table = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault().GetTable();
  RowDeletedEvent.Subscribe(OnRowDeletedEvent, table);
}

private static void OnRowDeletedEvent(RowChangedEventArgs args)
{
  // RowEvent callbacks are always called on the QueuedTask so there is no need 
  // to wrap your code within a QueuedTask.Run lambda.

  var row = args.Row;

  // cancel the delete if the feature is in Police District 5

  var fldIdx = row.FindField("POLICE_DISTRICT");
  if (fldIdx != -1)
  {
    var value = row[fldIdx].ToString();
    if (value == "5")
    {
      //cancel with dialog
      // Note - feature edits on Hosted and Standard Feature Services cannot be cancelled.
      args.CancelEdit("Delete Event\nAre you sure", true);

      // or cancel without a dialog
      // args.CancelEdit();
    }
  }
}

EditCompletedEvent

Subscribe to EditCompletedEvent

protected void subEditEvents()
{
  //subscribe to editcompleted
  var eceToken = EditCompletedEvent.Subscribe(onEce);
}

protected Task onEce(EditCompletedEventArgs args)
{
  //show number of edits
  Console.WriteLine("Creates: " + args.Creates.ToDictionary().Values.Sum(list => list.Count).ToString());
  Console.WriteLine("Modifies: " + args.Modifies.ToDictionary().Values.Sum(list => list.Count).ToString());
  Console.WriteLine("Deletes: " + args.Deletes.ToDictionary().Values.Sum(list => list.Count).ToString());
  return Task.FromResult(0);
}

Inspector

Load a feature from a layer into the inspector

// get the first feature layer in the map
var firstFeatureLayer = ArcGIS.Desktop.Mapping.MapView.Active.Map.GetLayersAsFlattenedList().
    OfType<ArcGIS.Desktop.Mapping.FeatureLayer>().FirstOrDefault();

// create an instance of the inspector class
var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector();
// load the feature with ObjectID 'oid' into the inspector
await inspector.LoadAsync(firstFeatureLayer, oid);

Load map selection into Inspector

// get the currently selected features in the map
var selectedFeatures = ArcGIS.Desktop.Mapping.MapView.Active.Map.GetSelection();
// get the first layer and its corresponding selected feature OIDs
var firstSelectionSet = selectedFeatures.ToDictionary().First();

// create an instance of the inspector class
var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector();
// load the selected features into the inspector using a list of object IDs
await inspector.LoadAsync(firstSelectionSet.Key, firstSelectionSet.Value);

Get selected feature's attribute value

QueuedTask.Run(() =>
{

  // get the currently selected features in the map
  var selectedFeatures = ArcGIS.Desktop.Mapping.MapView.Active.Map.GetSelection();

  // get the first layer and its corresponding selected feature OIDs
  var firstSelectionSet = selectedFeatures.ToDictionary().First();

  // create an instance of the inspector class
  var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector();

  // load the selected features into the inspector using a list of object IDs
  inspector.Load(firstSelectionSet.Key, firstSelectionSet.Value);

  //get the value of
  var pscode = inspector["STATE_NAME"];
  var myGeometry = inspector.Shape;
});

Load map selection into Inspector and Change Attributes

// get the currently selected features in the map
var selectedFeatures = ArcGIS.Desktop.Mapping.MapView.Active.Map.GetSelection();
// get the first layer and its corresponding selected feature OIDs
var firstSelectionSet = selectedFeatures.ToDictionary().First();

// create an instance of the inspector class
var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector();
// load the selected features into the inspector using a list of object IDs
await inspector.LoadAsync(firstSelectionSet.Key, firstSelectionSet.Value);

// assign the new attribute value to the field "Description"
// if more than one features are loaded, the change applies to all features
inspector["Description"] = "The new value.";
// apply the changes as an edit operation 
await inspector.ApplyAsync();

Get a layers schema using Inspector

QueuedTask.Run(() =>
{
  var firstFeatureLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<ArcGIS.Desktop.Mapping.FeatureLayer>().FirstOrDefault();

  // create an instance of the inspector class
  var inspector = new ArcGIS.Desktop.Editing.Attributes.Inspector();

  // load the layer
  inspector.LoadSchema(firstFeatureLayer);

  // iterate through the attributes, looking at properties
  foreach (var attribute in inspector)
  {
    var fldName = attribute.FieldName;
    var fldAlias = attribute.FieldAlias;
    var fldType = attribute.FieldType;
    int idxFld = attribute.FieldIndex;
    var fld = attribute.GetField();
    var isNullable = attribute.IsNullable;
    var isEditable = attribute.IsEditable;
    var isVisible = attribute.IsVisible;
    var isSystemField = attribute.IsSystemField;
    var isGeometryField = attribute.IsGeometryField;
  }
});

Inspector.AddValidate

var insp = new Inspector();
insp.LoadSchema(featLayer);
var attrib = insp.Where(a => a.FieldName == "Mineral").First();

attrib.AddValidate(() =>
{
  if (attrib.CurrentValue.ToString() == "Salt")
    return Enumerable.Empty<ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError>();
  else return new[] { ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError.Create("Error", ArcGIS.Desktop.Editing.Attributes.Severity.Low) };
});

Accessing Blob Fields

Read and Write blob fields with the attribute inspector

QueuedTask.Run(() =>
{
  //get selected feature into inspector
  var selectedFeatures = MapView.Active.Map.GetSelection();

  var insp = new Inspector();
  insp.Load(selectedFeatures.ToDictionary().Keys.First(), selectedFeatures.ToDictionary().Values.First());

  //read a blob field and save to a file
  var msw = new MemoryStream();
  msw = insp["Blobfield"] as MemoryStream;
  using (FileStream file = new FileStream(@"d:\temp\blob.jpg", FileMode.Create, FileAccess.Write))
  {
    msw.WriteTo(file);
  }

  //read file into memory stream
  var msr = new MemoryStream();
  using (FileStream file = new FileStream(@"d:\images\Hydrant.jpg", FileMode.Open, FileAccess.Read))
  {
    file.CopyTo(msr);
  }

  //put the memory stream in the blob field and save to feature
  var op = new EditOperation();
  op.Name = "Blob Inspector";
  insp["Blobfield"] = msr;
  op.Modify(insp);
  if (!op.IsEmpty)
  {
    var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Read and Write blob fields with a row cursor in a callback

QueuedTask.Run(() =>
{
  var editOp = new EditOperation();
  editOp.Name = "Blob Cursor";
  var featLayer = MapView.Active.Map.FindLayers("Hydrant").First() as FeatureLayer;

  editOp.Callback((context) =>
  {
    using (var rc = featLayer.GetTable().Search(null, false))
    {
      while (rc.MoveNext())
      {
        using (var record = rc.Current)
        {
          //read the blob field and save to a file
          var msw = new MemoryStream();
          msw = record["BlobField"] as MemoryStream;
          using (FileStream file = new FileStream(@"d:\temp\blob.jpg", FileMode.Create, FileAccess.Write))
          {
            msw.WriteTo(file);
          }

          //read file into memory stream
          var msr = new MemoryStream();
          using (FileStream file = new FileStream(@"d:\images\Hydrant.jpg", FileMode.Open, FileAccess.Read))
          {
            file.CopyTo(msr);
          }

          //put the memory stream in the blob field and save to feature
          record["BlobField"] = msr;
          record.Store();

        }
      }
    }
  }, featLayer.GetTable());
  if (!editOp.IsEmpty)
  {
    var result = editOp.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Accessing Raster Fields

Read from a raster field

QueuedTask.Run(() =>
{
  var sel = MapView.Active.Map.GetSelection();

  //Read a raster from a raster field as an InteropBitmap
  //the bitmap can then be used as an imagesource or written to disk
  var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();
  insp.Load(sel.ToDictionary().Keys.First(), sel.ToDictionary().Values.First());
  var ibmp = insp["Photo"] as System.Windows.Interop.InteropBitmap;
});

Write an image to a raster field

QueuedTask.Run(() =>
{
  var sel = MapView.Active.Map.GetSelection();

  //Insert an image into a raster field
  //Image will be written with no compression
  var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();
  insp.Load(sel.ToDictionary().Keys.First(), sel.ToDictionary().Values.First());
  insp["Photo"] = @"e:\temp\Hydrant.jpg";

  var op = new EditOperation();
  op.Name = "Raster Inspector";
  op.Modify(insp);
  if (!op.IsEmpty)
  {
    var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Write a compressed image to a raster field

QueuedTask.Run(() =>
{
  //Open the raster dataset on disk and create a compressed raster value dataset object
  var dataStore = new ArcGIS.Core.Data.FileSystemDatastore(new ArcGIS.Core.Data.FileSystemConnectionPath(new System.Uri(@"e:\temp"), ArcGIS.Core.Data.FileSystemDatastoreType.Raster));
  using (var fileRasterDataset = dataStore.OpenDataset<ArcGIS.Core.Data.Raster.RasterDataset>("Hydrant.jpg"))
  {
    var storageDef = new ArcGIS.Core.Data.Raster.RasterStorageDef();
    storageDef.SetCompressionType(ArcGIS.Core.Data.Raster.RasterCompressionType.JPEG);
    storageDef.SetCompressionQuality(90);

    var rv = new ArcGIS.Core.Data.Raster.RasterValue();
    rv.SetRasterDataset(fileRasterDataset);
    rv.SetRasterStorageDef(storageDef);

    var sel = MapView.Active.Map.GetSelection();

    //insert a raster value object into the raster field
    var insp = new ArcGIS.Desktop.Editing.Attributes.Inspector();
    insp.Load(sel.ToDictionary().Keys.First(), sel.ToDictionary().Values.First());
    insp["Photo"] = rv;

    var op = new EditOperation();
    op.Name = "Raster Inspector";
    op.Modify(insp);
    if (!op.IsEmpty)
    {
      var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
    }
  }
});

Inspector Provider Class

How to create a custom Feature inspector provider class

public class MyProvider : InspectorProvider
{
    private System.Guid guid = System.Guid.NewGuid();
    internal MyProvider()
    {
    }
    public override System.Guid SharedFieldColumnSizeID()
    {
        return guid;
    }

    public override string CustomName(Attribute attr)
    {
        //Giving a custom name to be displayed for the field FeatureID
        if (attr.FieldName == "FeatureID")
            return "Feature Identification";

        return attr.FieldName;
    }
    public override bool? IsVisible(Attribute attr)
    {
        //The field FontStyle will not be visible
        if (attr.FieldName == "FontStyle")
            return false;

        return true;
    }
    public override bool? IsEditable(Attribute attr)
    {
        //The field DateField will not be editable
        if (attr.FieldName == "DateField")
            return false;

        return true;
    }
    public override bool? IsHighlighted(Attribute attr)
    {
        //ZOrder field will be highlighted in the feature inspector grid
        if (attr.FieldName == "ZOrder")
            return true;

        return false;
    }

    public override IEnumerable<Attribute> AttributesOrder(IEnumerable<Attribute> attrs)
    {
        //Reverse the order of display
        var newList = new List<Attribute>();
        foreach (var attr in attrs)
        {
            newList.Insert(0, attr);
        }
        return newList;
    }

    public override bool? IsDirty(Attribute attr)
    {
        //The field will not be marked dirty for FeatureID if you enter the value -1
        if ((attr.FieldName == "FeatureID") && (attr.CurrentValue.ToString() == "-1"))
            return false;

        return base.IsDirty(attr);
    }

    public override IEnumerable<ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError> Validate(Attribute attr)
    {
        var errors = new List<ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError>();

        if ((attr.FieldName == "FeatureID") && (attr.CurrentValue.ToString() == "2"))
            errors.Add(ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError.Create("Value not allowed", ArcGIS.Desktop.Editing.Attributes.Severity.Low));

        if ((attr.FieldName == "FeatureID") && (attr.CurrentValue.ToString() == "-1"))
            errors.Add(ArcGIS.Desktop.Editing.Attributes.Attribute.ValidationError.Create("Invalid value", ArcGIS.Desktop.Editing.Attributes.Severity.High));

        return errors;
    }
}

Using the custom inspector provider class

var layer = ArcGIS.Desktop.Mapping.MapView.Active.Map.GetLayersAsFlattenedList().OfType<ArcGIS.Desktop.Mapping.FeatureLayer>().FirstOrDefault();

var provider = new MyProvider();
Inspector _featureInspector = provider.Create();
//Create an embeddable control from the inspector class to display on the pane
var icontrol = _featureInspector.CreateEmbeddableControl();

await _featureInspector.LoadAsync(layer, oid);
var attribute = _featureInspector.Where(a => a.FieldName == "FontStyle").FirstOrDefault();
var visibility = attribute.IsVisible; //Will return false

attribute = _featureInspector.Where(a => a.FieldName == "ZOrder").FirstOrDefault();
var highlighted = attribute.IsHighlighted; //Will return true

Working with the Sketch

Toggle sketch selection mode

//UseSelection = true; (UseSelection must be set to true in the tool constructor or tool activate)
private bool _inSelMode = false;

public bool IsShiftKey(MapViewKeyEventArgs k)
{
  return (k.Key == System.Windows.Input.Key.LeftShift ||
         k.Key == System.Windows.Input.Key.RightShift);
}

protected override async void OnToolKeyDown(MapViewKeyEventArgs k)
{
  //toggle sketch selection mode with a custom key
  if (k.Key == System.Windows.Input.Key.W)
  {
    if (!_inSelMode)
    {
      k.Handled = true;

      // Toggle the tool to select mode.
      //  The sketch is saved if UseSelection = true;
      if (await ActivateSelectAsync(true))
        _inSelMode = true;
    }
  }
  else if (!_inSelMode)
  {
    //disable effect of Shift in the base class.
    //Mark the key event as handled to prevent further processing
    k.Handled = IsShiftKey(k);
  }
}
protected override void OnToolKeyUp(MapViewKeyEventArgs k)
{
  if (k.Key == System.Windows.Input.Key.W)
  {
    if (_inSelMode)
    {
      _inSelMode = false;
      k.Handled = true;//process this one

      // Toggle back to sketch mode. If UseSelection = true
      //   the sketch will be restored
      ActivateSelectAsync(false);
    }
  }
  else if (_inSelMode)
  {
    //disable effect of Shift in the base class.
    //Mark the key event as handled to prevent further processing
    k.Handled = IsShiftKey(k);
  }
}

Listen to the sketch modified event

// SketchModified event is fired by 
//  - COTS construction tools (except annotation, dimension geometry types), 
//  - Edit Vertices, Reshape, Align Features
//  - 3rd party tools with FireSketchEvents = true


//Subscribe the sketch modified event
//ArcGIS.Desktop.Mapping.Events.SketchModifiedEvent.Subscribe(OnSketchModified);

private void OnSketchModified(ArcGIS.Desktop.Mapping.Events.SketchModifiedEventArgs args)
{
  // if not an undo operation
  if (!args.IsUndo)
  {
    // what was the sketch before the change?
    var prevSketch = args.PreviousSketch;
    // what is the current sketch?
    var currentSketch = args.CurrentSketch;
    if (currentSketch is Polyline polyline)
    {
      // Examine the current (last) vertex in the line sketch
      var lastSketchPoint = polyline.Points.Last();

      // do something with the last point
    }
  }
}

Listen to the before sketch completed event and modify the sketch

// BeforeSketchCompleted event is fired by 
//  - COTS construction tools (except annotation, dimension geometry types), 
//  - Edit Vertices, Reshape, Align Features
//  - 3rd party tools with FireSketchEvents = true


//Subscribe to the before sketch completed event
//ArcGIS.Desktop.Mapping.Events.BeforeSketchCompletedEvent.Subscribe(OnBeforeSketchCompleted);

private Task OnBeforeSketchCompleted(BeforeSketchCompletedEventArgs args)
{
  //assign sketch Z values from default surface and set the sketch geometry
  var modifiedSketch = args.MapView.Map.GetZsFromSurfaceAsync(args.Sketch).Result;
  args.SetSketchGeometry(modifiedSketch.Geometry);
  return Task.CompletedTask;
}

Listen to the sketch completed event

// SketchCompleted event is fired by 
//  - COTS construction tools (except annotation, dimension geometry types), 
//  - Edit Vertices, Reshape, Align Features
//  - 3rd party tools with FireSketchEvents = true


//Subscribe to the sketch completed event
//ArcGIS.Desktop.Mapping.Events.SketchCompletedEvent.Subscribe(OnSketchCompleted);

private void OnSketchCompleted(SketchCompletedEventArgs args)
{
  // get the sketch
  var finalSketch = args.Sketch;

  // do something with the sketch - audit trail perhaps
}

Custom construction tool that fires sketch events

internal class ConstructionTool1 : MapTool
{
  public ConstructionTool1()
  {
    IsSketchTool = true;
    UseSnapping = true;
    // Select the type of construction tool you wish to implement.  
    // Make sure that the tool is correctly registered with the correct component category type in the daml 
    SketchType = SketchGeometryType.Line;
    //Gets or sets whether the sketch is for creating a feature and should use the CurrentTemplate.
    UsesCurrentTemplate = true;

    // set FireSketchEvents property to true
    FireSketchEvents = true;
  }

  //  ...
}

Customizing the Sketch Symbol of a Custom Sketch Tool

//Custom tools have the ability to change the symbology used when sketching a new feature. 
//Both the Sketch Segment Symbol and the Vertex Symbol can be modified using the correct set method. 
//This is set in the activate method for the tool.
protected override Task OnToolActivateAsync(bool active)
{
  QueuedTask.Run(() =>
  {
    //Getting the current symbology options of the segment
    var segmentOptions = GetSketchSegmentSymbolOptions();
    //Modifying the primary and secondary color and the width of the segment symbology options
    var deepPurple = new CIMRGBColor();
    deepPurple.R = 75;
    deepPurple.G = 0;
    deepPurple.B = 110;
    segmentOptions.PrimaryColor = deepPurple;
    segmentOptions.Width = 4;
    segmentOptions.HasSecondaryColor = true;
    var pink = new CIMRGBColor();
    pink.R = 219;
    pink.G = 48;
    pink.B = 130;
    segmentOptions.SecondaryColor = pink;
    //Creating a new vertex symbol options instance with the values you want
    var vertexOptions = new VertexSymbolOptions(VertexSymbolType.RegularUnselected);
    var yellow = new CIMRGBColor();
    yellow.R = 255;
    yellow.G = 215;
    yellow.B = 0;
    var purple = new CIMRGBColor();
    purple.R = 148;
    purple.G = 0;
    purple.B = 211;
    vertexOptions.AngleRotation = 45;
    vertexOptions.Color = yellow;
    vertexOptions.MarkerType = VertexMarkerType.Star;
    vertexOptions.OutlineColor = purple;
    vertexOptions.OutlineWidth = 3;
    vertexOptions.Size = 5;

    //Setting the value of the segment symbol options
    SetSketchSegmentSymbolOptions(segmentOptions);
    //Setting the value of the vertex symbol options of the regular unselected vertices using the vertexOptions instance created above.
    SetSketchVertexSymbolOptions(VertexSymbolType.RegularUnselected, vertexOptions);
  });

  return base.OnToolActivateAsync(active);
}

SketchTool

Set a MiniToolbar, ContextMenuID

// daml entries
// 
// <menus>
//  <menu id="MyMenu" caption="Nav">
//    <button refID="esri_mapping_prevExtentButton"/>
//    <button refID="esri_mapping_fixedZoomInButton"/>
//  </menu>
// </menus>
// <miniToolbars>
//   <miniToolbar id="MyMiniToolbar">
//    <row>
//      <button refID="esri_mapping_fixedZoomInButton"/>
//      <button refID="esri_mapping_prevExtentButton"/>
//    </row>
//   </miniToolbar>
// </miniToolbars>

public class SketchToolWithToolbar : MapTool
{
  public SketchToolWithToolbar()
  {
    IsSketchTool = true;
    SketchType = SketchGeometryType.Line;
    SketchOutputMode = SketchOutputMode.Map;
    ContextMenuID = "MyMenu";
    ContextToolbarID = "MyMiniToolbar";
  }
}

Set a simple sketch tip

public class SketchToolWithSketchTip : MapTool
{
  public SketchToolWithSketchTip()
  {
    IsSketchTool = true;
    SketchType = SketchGeometryType.Line;
    SketchOutputMode = SketchOutputMode.Map;
    SketchTip = "hello World";
  }
}

Set a custom UI Sketch Tip

// 1. Add an embeddable control using VS template.  This is the daml entry

//<categories>
//  <updateCategory refID = "esri_embeddableControls">
//    <insertComponent id="SketchTip_EmbeddableControl1" className="EmbeddableControl1ViewModel">
//      <content className = "EmbeddableControl1View"/>
//    </insertComponent>
//  </updateCategory>
// </categories>

// 2. Define UI controls on the EmbeddableControl1View
// 3. Define properties on the EmbeddableControl1ViewModel which
//    bind to the UI controls on the EmbeddableControl1View

public class SketchToolWithUISketchTip : MapTool
{
  public SketchToolWithUISketchTip()
  {
    IsSketchTool = true;
    SketchType = SketchGeometryType.Line;
    SketchOutputMode = SketchOutputMode.Map;
    SketchTipID = "SketchTip_EmbeddableControl1";
  }


  protected override Task<bool> OnSketchModifiedAsync()
  {
    var sketchTipVM = SketchTipEmbeddableControl as EmbeddableControl1ViewModel;
    if (sketchTipVM != null)
    {
      // modify properties on the sketchTipVM
      QueuedTask.Run(async () =>
      {
        var sketch = await GetCurrentSketchAsync();
        var line = sketch as Polyline;
        var count = line.PointCount;

        sketchTipVM.Text = "Vertex Count " + count.ToString();
      });


    }

    return base.OnSketchModifiedAsync();
  }
  
}

Snapping

Configure Snapping - Turn Snapping on or off

//enable snapping
ArcGIS.Desktop.Mapping.Snapping.IsEnabled = true;

// disable snapping
ArcGIS.Desktop.Mapping.Snapping.IsEnabled = false;

Configure Snapping - Application SnapModes

// set only Point and Edge snapping modes, clear everything else
//At 2.x - ArcGIS.Desktop.Mapping.Snapping.SetSnapModes(SnapMode.Point, SnapMode.Edge);
ArcGIS.Desktop.Mapping.Snapping.SetSnapModes(
  new List<SnapMode>() { SnapMode.Point, SnapMode.Edge });

// clear all snap modes
//At 2.x - ArcGIS.Desktop.Mapping.Snapping.SetSnapModes();
ArcGIS.Desktop.Mapping.Snapping.SetSnapModes(null);


// set snap modes one at a time
ArcGIS.Desktop.Mapping.Snapping.SetSnapMode(SnapMode.Edge, true);
ArcGIS.Desktop.Mapping.Snapping.SetSnapMode(SnapMode.End, true);
ArcGIS.Desktop.Mapping.Snapping.SetSnapMode(SnapMode.Intersection, true);

// get current snap modes
var snapModes = ArcGIS.Desktop.Mapping.Snapping.SnapModes;

// get state of a specific snap mode
bool isOn = ArcGIS.Desktop.Mapping.Snapping.GetSnapMode(SnapMode.Vertex);

Configure Snapping - Layer Snappability

// is the layer snappable?
bool isSnappable = fLayer.IsSnappable;

// set snappability for a specific layer - needs to run on the MCT
await QueuedTask.Run(() =>
{
  // use an extension method
  fLayer.SetSnappable(true);

  // or use the CIM directly
  //var layerDef = fLayer.GetDefinition() as ArcGIS.Core.CIM.CIMGeoFeatureLayerBase;
  //layerDef.Snappable = true;
  //fLayer.SetDefinition(layerDef);
});


// turn all layers snappability off
layerList = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>();
await QueuedTask.Run(() =>
{
  foreach (var layer in layerList)
  {
    layer.SetSnappable(false);
  }
});

Configure Snapping - LayerSnapModes

layerList = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>();

// configure by layer
foreach (var layer in layerList)
{
  // find the state of the snapModes for the layer
  var lsm = ArcGIS.Desktop.Mapping.Snapping.GetLayerSnapModes(layer);
  bool vertexOn = lsm.Vertex;
  // or use 
  vertexOn = lsm.GetSnapMode(SnapMode.Vertex);

  bool edgeOn = lsm.Edge;
  // or use 
  edgeOn = lsm.GetSnapMode(SnapMode.Edge);

  bool endOn = lsm.End;
  // or use 
  endOn = lsm.GetSnapMode(SnapMode.End);

  // update a few snapModes 
  //   turn Vertex off
  lsm.SetSnapMode(SnapMode.Vertex, false);
  // intersections on
  lsm.SetSnapMode(SnapMode.Intersection, true);

  // and set back to the layer
  ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, lsm);


  // assign a single snap mode at once
  ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, SnapMode.Vertex, false);


  // turn ALL snapModes on
  ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, true);
  // turn ALL snapModes off
  ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layer, false);
}


// configure for a set of layers

// set Vertex, edge, end on for a set of layers, other snapModes false
var vee = new LayerSnapModes(false);
vee.Vertex = true;
vee.Edge = true;
vee.End = true;
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layerList, vee);


// ensure intersection is on for a set of layers without changing any other snapModes

// get the layer snapModes for the set of layers
var dictLSM = ArcGIS.Desktop.Mapping.Snapping.GetLayerSnapModes(layerList);
foreach (var layer in dictLSM.Keys)
{
  var lsm = dictLSM[layer];
  lsm.Intersection = true;
}
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(dictLSM);


// set all snapModes off for a list of layers
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(layerList, false);

Configure Snapping - Combined Example

// interested in only snapping to the vertices of a specific layer of interest and not the vertices of other layers
//  all other snapModes should be off.

// snapping must be on
ArcGIS.Desktop.Mapping.Snapping.IsEnabled = true;

// turn all application snapModes off
//At 2.x - ArcGIS.Desktop.Mapping.Snapping.SetSnapModes();
ArcGIS.Desktop.Mapping.Snapping.SetSnapModes(null);

// set application snapMode vertex on 
ArcGIS.Desktop.Mapping.Snapping.SetSnapMode(SnapMode.Vertex, true);

// ensure layer snapping is on
await QueuedTask.Run(() =>
{
  fLayer.SetSnappable(true);
});

// set vertex snapping only
var vertexOnly = new LayerSnapModes(false);
vertexOnly.Vertex = true;

// set vertex only for the specific layer, clearing all others
var dict = new Dictionary<Layer, LayerSnapModes>();
dict.Add(fLayer, vertexOnly);
ArcGIS.Desktop.Mapping.Snapping.SetLayerSnapModes(dict, true);  // true = reset other layers

Snap Options

//Set snapping options via get/set options
var snapOptions = ArcGIS.Desktop.Mapping.Snapping.GetOptions(myMap);
//At 2.x - snapOptions.SnapToSketchEnabled = true;
snapOptions.IsSnapToSketchEnabled = true;
snapOptions.XYTolerance = 100;
//At 2.x - snapOptions.ZToleranceEnabled = true;
snapOptions.IsZToleranceEnabled = true;
snapOptions.ZTolerance = 0.6;

//turn on snap tip display parts
snapOptions.SnapTipDisplayParts = (int)SnapTipDisplayPart.SnapTipDisplayLayer + (int)SnapTipDisplayPart.SnapTipDisplayType;

//turn off all snaptips
//snapOptions.SnapTipDisplayParts = (int)SnapTipDisplayPart.SnapTipDisplayNone;

//turn on layer display only
//snapOptions.SnapTipDisplayParts = (int)SnapTipDisplayPart.SnapTipDisplayLayer;

//At 2.x - snapOptions.GeometricFeedbackColor = ColorFactory.Instance.RedRGB;
snapOptions.SnapTipColor = ColorFactory.Instance.RedRGB;

ArcGIS.Desktop.Mapping.Snapping.SetOptions(myMap, snapOptions);

Edit Templates

Find edit template by name on a layer

ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
{
  //get the templates
  var map = ArcGIS.Desktop.Mapping.MapView.Active.Map;
  if (map == null)
    return;

  var mainTemplate = map.FindLayers("main").FirstOrDefault()?.GetTemplate("Distribution");
  var mhTemplate = map.FindLayers("Manhole").FirstOrDefault()?.GetTemplate("Active");
});

Find table templates belonging to a standalone table

ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
{
  var map = ArcGIS.Desktop.Mapping.MapView.Active.Map;
  if (map == null)
    return;
  //Get a particular table template
  var tableTemplate = map.FindStandaloneTables("Address Points").FirstOrDefault()?.GetTemplate("Residences");
  //Get all the templates of a standalone table
  var ownersTableTemplates = map.FindStandaloneTables("Owners").FirstOrDefault()?.GetTemplates();
  var statisticsTableTemplates = MapView.Active.Map.GetStandaloneTablesAsFlattenedList().First(l => l.Name.Equals("Trading Statistics")).GetTemplates();
});

Current template

EditingTemplate template = EditingTemplate.Current;

Change Default Edit tool for a template

public Task ChangeTemplateDefaultToolAsync(ArcGIS.Desktop.Mapping.FeatureLayer flayer,
                  string toolContentGUID, string templateName)
{
  return ArcGIS.Desktop.Framework.Threading.Tasks.QueuedTask.Run(() =>
  {

    // retrieve the edit template form the layer by name
    var template = flayer?.GetTemplate(templateName) as ArcGIS.Desktop.Editing.Templates.EditingTemplate;
    // get the definition of the layer
    var layerDef = flayer?.GetDefinition() as ArcGIS.Core.CIM.CIMFeatureLayer;
    if ((template == null) || (layerDef == null))
      return;

    if (template.DefaultToolID != this.ID)
    {
      bool updateLayerDef = false;
      if (layerDef.AutoGenerateFeatureTemplates)
      {
        layerDef.AutoGenerateFeatureTemplates = false;
        updateLayerDef = true;
      }

      // retrieve the CIM edit template definition
      var templateDef = template.GetDefinition();

      // assign the GUID from the tool DAML definition, for example
      // <tool id="TestConstructionTool_SampleSDKTool" categoryRefID="esri_editing_construction_polyline" ….>
      //   <tooltip heading="">Tooltip text<disabledText /></tooltip>
      //   <content guid="e58239b3-9c69-49e5-ad4d-bb2ba29ff3ea" />
      // </tool>
      // then the toolContentGUID would be "e58239b3-9c69-49e5-ad4d-bb2ba29ff3ea"

      //At 2.x -
      //templateDef.ToolProgID = toolContentGUID;
      templateDef.DefaultToolGUID = toolContentGUID;

      // set the definition back to 
      template.SetDefinition(templateDef);

      // update the layer definition too
      if (updateLayerDef)
        flayer.SetDefinition(layerDef);
    }
  });
}

Hide or show editing tools on templates

QueuedTask.Run(() =>
{
  //hide all tools except line tool on layer
  var featLayer = MapView.Active.Map.FindLayers("Roads").First();

  var editTemplates = featLayer.GetTemplates();
  var newCIMEditingTemplates = new List<CIMEditingTemplate>();

  foreach (var et in editTemplates)
  {
    //initialize template by activating default tool
    et.ActivateDefaultToolAsync();
    var cimEditTemplate = et.GetDefinition();
    //get the visible tools on this template
    var allTools = et.ToolIDs.ToList();
    //add the hidden tools on this template
    allTools.AddRange(cimEditTemplate.GetExcludedToolIDs().ToList());
    //hide all the tools then allow the line tool
  
    //At 2.x -
    //allTools.AddRange(cimEditTemplate.GetExcludedToolDamlIds().ToList());
    allTools.AddRange(cimEditTemplate.GetExcludedToolIDs().ToList());
    
    //At 2.x - 
    //cimEditTemplate.SetExcludedToolDamlIds(allTools.ToArray());
    //cimEditTemplate.AllowToolDamlID("esri_editing_SketchLineTool");
    
    cimEditTemplate.SetExcludedToolIDs(allTools.ToArray());
    cimEditTemplate.AllowToolID("esri_editing_SketchLineTool");
    newCIMEditingTemplates.Add(cimEditTemplate);
  }
  //update the layer templates
  var layerDef = featLayer.GetDefinition() as CIMFeatureLayer;
  // Set AutoGenerateFeatureTemplates to false for template changes to stick
  layerDef.AutoGenerateFeatureTemplates = false;
  layerDef.FeatureTemplates = newCIMEditingTemplates.ToArray();
  featLayer.SetDefinition(layerDef);
});

Create New Template using layer.CreateTemplate

var layer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<FeatureLayer>().FirstOrDefault();
if (layer == null)
  return;
QueuedTask.Run(() =>
{
  var insp = new Inspector();
  insp.LoadSchema(layer);

  insp["Field1"] = value1;
  insp["Field2"] = value2;
  insp["Field3"] = value3;

  var tags = new[] { "Polygon", "tag1", "tag2" };

  // set defaultTool using a daml-id 
  string defaultTool = "esri_editing_SketchCirclePolygonTool";

  // tool filter is the tools to filter OUT
  var toolFilter = new[] { "esri_editing_SketchTracePolygonTool" };

  // create a new template  
  var newTemplate = layer.CreateTemplate("My new template", "description", insp, defaultTool, tags, toolFilter);
});

Create New Table Template using table.CreateTemplate

var table = MapView.Active.Map.GetStandaloneTablesAsFlattenedList().FirstOrDefault();
if (table == null)
  return;
QueuedTask.Run(() =>
{
  var tableTemplate = table.GetTemplate("Template1");
  
  var definition = tableTemplate.GetDefinition();
  definition.Description = "New definition";
  definition.Name = "New name";
  //Create new table template using this definition
  table.CreateTemplate(definition);

  //You can also create a new table template using this extension method. You can use this method the same way you use the layer.CreateTemplate method.
  table.CreateTemplate("New template name", "Template description", tags: new string[] { "tag 1", "tag 2" });
});

Update a Table Template

QueuedTask.Run(() =>
{
  var tableTemplate = table.GetTemplate("Template1");

  var definition = tableTemplate.GetDefinition();
  definition.Description = "New definition";
  definition.Name = "New name";
  // update the definition
  tableTemplate.SetDefinition(definition);
});

Create Annotation Template

// get an anno layer
AnnotationLayer annoLayer = MapView.Active.Map.GetLayersAsFlattenedList().OfType<AnnotationLayer>().FirstOrDefault();
if (annoLayer == null)
  return;

QueuedTask.Run(() =>
{
  Inspector insp = null;
  // get the anno feature class
  var fc = annoLayer.GetFeatureClass() as ArcGIS.Core.Data.Mapping.AnnotationFeatureClass;

  // get the featureclass CIM definition which contains the labels, symbols
  var cimDefinition = fc.GetDefinition() as ArcGIS.Core.Data.Mapping.AnnotationFeatureClassDefinition;
  var labels = cimDefinition.GetLabelClassCollection();
  var symbols = cimDefinition.GetSymbolCollection();

  // make sure there are labels, symbols
  if ((labels.Count == 0) || (symbols.Count == 0))
    return;

  // find the label class required
  //   typically you would use a subtype name or some other characteristic
  // in this case lets just use the first one

  var label = labels[0];

  // each label has a textSymbol
  // the symbolName *should* be the symbolID to be used
  var symbolName = label.TextSymbol.SymbolName;
  int symbolID = -1;
  if (!int.TryParse(symbolName, out symbolID))
  {
    // int.TryParse fails - attempt to find the symbolName in the symbol collection
    foreach (var symbol in symbols)
    {
      if (symbol.Name == symbolName)
      {
        symbolID = symbol.ID;
        break;
      }
    }
  }
  // no symbol?
  if (symbolID == -1)
    return;

  // load the schema
  insp = new Inspector();
  insp.LoadSchema(annoLayer);

  // ok to assign these fields using the inspector[fieldName] methodology
  //   these fields are guaranteed to exist in the annotation schema
  insp["AnnotationClassID"] = label.ID;
  insp["SymbolID"] = symbolID;

  // set up some additional annotation properties
  AnnotationProperties annoProperties = insp.GetAnnotationProperties();
  annoProperties.FontSize = 36;
  annoProperties.TextString = "My Annotation feature";
  annoProperties.VerticalAlignment = VerticalAlignment.Top;
  annoProperties.HorizontalAlignment = HorizontalAlignment.Justify;

  insp.SetAnnotationProperties(annoProperties);

  var tags = new[] { "Annotation", "tag1", "tag2" };

  // use daml-id rather than guid
  string defaultTool = "esri_editing_SketchStraightAnnoTool";

  // tool filter is the tools to filter OUT
  var toolFilter = new[] { "esri_editing_SketchCurvedAnnoTool" };

  // create a new template 
  var newTemplate = annoLayer.CreateTemplate("new anno template", "description", insp, defaultTool, tags, toolFilter);
});

Remove a table template

var table = MapView.Active.Map.GetStandaloneTablesAsFlattenedList().FirstOrDefault();
if (table == null)
  return;
QueuedTask.Run(() =>
{
  var tableTemplate = table.GetTemplate("Template1");
  //Removing a table template
  table.RemoveTemplate(tableTemplate);
  //Removing a template by name
  table.RemoveTemplate("Template2");
});

Active Template Changed

ArcGIS.Desktop.Editing.Events.ActiveTemplateChangedEvent.Subscribe(OnActiveTemplateChanged);

async void OnActiveTemplateChanged(ArcGIS.Desktop.Editing.Events.ActiveTemplateChangedEventArgs args)
{
  // return if incoming template is null
  if (args.IncomingTemplate == null)
    return;

  // Activate two-point line tool for Freeway template in the Layers map
  if (args.IncomingTemplate.Name == "Freeway" && args.IncomingMapView.Map.Name == "Layers")
    await args.IncomingTemplate.ActivateToolAsync("esri_editing_SketchTwoPointLineTool");
}

Annotation

Annotation Construction Tool

//In your config.daml...set the categoryRefID
//<tool id="..." categoryRefID="esri_editing_construction_annotation" caption="Create Anno" ...>

//Sketch type Point or Line or BezierLine in the constructor...
//internal class AnnoConstructionTool : MapTool  {
//  public AnnoConstructionTool()  {
//    IsSketchTool = true;
//    UseSnapping = true;
//    SketchType = SketchGeometryType.Point;
//

protected async override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
  if (CurrentTemplate == null || geometry == null)
    return false;

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

  var insp = CurrentTemplate.Inspector;
  var result = await QueuedTask.Run(() =>
  {
    // get the annotation properties class
    AnnotationProperties annoProperties = insp.GetAnnotationProperties();
    // set custom annotation properties
    annoProperties.TextString = "my custom text";
    annoProperties.Color = ColorFactory.Instance.RedRGB;
    annoProperties.FontSize = 24;
    annoProperties.FontName = "Arial";
    annoProperties.HorizontalAlignment = ArcGIS.Core.CIM.HorizontalAlignment.Right;
    annoProperties.Shape = geometry;
    // assign annotation properties back to the inspector
    insp.SetAnnotationProperties(annoProperties);

    // Queue feature creation
    createOperation.Create(CurrentTemplate.Layer, insp);

    if (!createOperation.IsEmpty)
    {
      // Execute the operation
      return createOperation.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
    }
    else
      return false;
  });
  return result;
}

Programmatically start Edit Annotation

var plugin = FrameworkApplication.GetPlugInWrapper("esri_editing_EditVerticesText");
if (plugin.Enabled)
  ((ICommand)plugin).Execute(null);

Programmatically Create an Annotation Feature

await QueuedTask.Run(() =>
{
  // annoLayer is ~your~ Annotation layer...
  // pnt is ~your~ Annotation geometry ...
  var op = new EditOperation();
  // Use the inspector
  var insp = new Inspector();
  insp.LoadSchema(annoLayer);
  // get the annotation properties from the inspector
  AnnotationProperties annoProperties = insp.GetAnnotationProperties();
  // change the annotation text 
  annoProperties.TextString = DateTime.Now.ToLongTimeString();
  // change font color to green
  annoProperties.Color = ColorFactory.Instance.GreenRGB;
  // change the horizontal alignment
  annoProperties.HorizontalAlignment = HorizontalAlignment.Center;
  annoProperties.Shape = pnt;
  // set the annotation properties back on the inspector
  insp.SetAnnotationProperties(annoProperties);
  // create the annotation
  op.Create(annoLayer, insp); 
  if (!op.IsEmpty)
  {
    var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Update Annotation Text

await QueuedTask.Run(() =>
{
  //annoLayer is ~your~ Annotation layer...

  // use the inspector methodology
  //at 2.x - var insp = new Inspector(true);
  var insp = new Inspector();
  insp.Load(annoLayer, oid);

  // get the annotation properties
  AnnotationProperties annoProperties = insp.GetAnnotationProperties();
  // set the attribute
  annoProperties.TextString = "Hello World";
  // assign the annotation proeprties back to the inspector
  insp.SetAnnotationProperties(annoProperties);

  //create and execute the edit operation
  EditOperation op = new EditOperation();
  op.Name = "Update annotation";
  op.Modify(insp);
  if (!op.IsEmpty)
  {
    var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Modify Annotation Shape

await QueuedTask.Run(() =>
{
  //Don't use 'Shape'....Shape is the bounding box of the annotation text. This is NOT what you want...
  //
  //var insp = new Inspector();
  //insp.Load(annoLayer, oid);
  //var shape = insp["SHAPE"] as Polygon;
  //...wrong shape...

  //Instead, we must use the AnnotationProperties

  //annoLayer is ~your~ Annotation layer
  //at 2.x - var insp = new Inspector(true);
  var insp = new Inspector();
  insp.Load(annoLayer, oid);

  AnnotationProperties annoProperties = insp.GetAnnotationProperties();
  var shape = annoProperties.Shape;
  if (shape.GeometryType != GeometryType.GeometryBag)
  {
    var newGeometry = GeometryEngine.Instance.Move(shape, 10, 10);
    annoProperties.Shape = newGeometry;
    insp.SetAnnotationProperties(annoProperties);

    EditOperation op = new EditOperation();
    op.Name = "Change annotation angle";
    op.Modify(insp);
    if (!op.IsEmpty)
    {
      var result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
    }
  }
});

Modify Annotation Text Graphic

await QueuedTask.Run(() =>
{

  var selection = annoLayer.GetSelection();
  if (selection.GetCount() == 0)
    return;

  // use the first selelcted feature 
  //at 2.x - var insp = new Inspector(true);
  var insp = new Inspector();
  insp.Load(annoLayer, selection.GetObjectIDs().FirstOrDefault());

  // getAnnoProperties should return null if not an annotation feature
  AnnotationProperties annoProperties = insp.GetAnnotationProperties();
  // get the textGraphic
  CIMTextGraphic textGraphic = annoProperties.TextGraphic;

  // change text
  textGraphic.Text = "Hello world";

  // set x,y offset via the symbol
  var symbol = textGraphic.Symbol.Symbol;
  var textSymbol = symbol as CIMTextSymbol;
  textSymbol.OffsetX = 2;
  textSymbol.OffsetY = 3;

  textSymbol.HorizontalAlignment = HorizontalAlignment.Center;

  // load the updated textGraphic
  annoProperties.LoadFromTextGraphic(textGraphic);
  // assign the annotation properties back 
  insp.SetAnnotationProperties(annoProperties);

  EditOperation op = new EditOperation();
  op.Name = "modify symbol";
  op.Modify(insp);
  if (!op.IsEmpty)
  {
    bool result = op.Execute(); //Execute and ExecuteAsync will return true if the operation was successful and false if not
  }
});

Undo / Redo

Undo/Redo the Most Recent Operation

//undo
if (MapView.Active.Map.OperationManager.CanUndo)
  MapView.Active.Map.OperationManager.UndoAsync();//await as needed

//redo
if (MapView.Active.Map.OperationManager.CanRedo)
  MapView.Active.Map.OperationManager.RedoAsync();//await as needed

Topology Properties

Get List of available topologies in the map

QueuedTask.Run(async () =>
{
  var map = MapView.Active.Map;
  //Get a list of all the available topologies for the map
  var availableTopologies = await map.GetAvailableTopologiesAsync();

  var gdbTopologies = availableTopologies.OfType<GeodatabaseTopologyProperties>();
  var mapTopologies = availableTopologies.OfType<MapTopologyProperties>();
});

Get the properties of the active topology in the map

var map = MapView.Active.Map;
var activeTopologyProperties = await map.GetActiveTopologyAsync();
var isMapTopology = activeTopologyProperties is MapTopologyProperties;
var isGdbTopology = activeTopologyProperties is GeodatabaseTopologyProperties;
var isNoTopology = activeTopologyProperties is NoTopologyProperties;

Get map topology properties

var mapTopoProperties = await map.GetTopologyAsync("Map") as MapTopologyProperties;
var tolerance_m = mapTopoProperties.Tolerance;
var defaultTolerance_m = mapTopoProperties.DefaultTolerance;

Get geodatabase topology properties by name

var topoProperties = await map.GetTopologyAsync("TopologyName") as GeodatabaseTopologyProperties;

var workspace = topoProperties.WorkspaceName;
var topoLayer = topoProperties.TopologyLayer;
var clusterTolerance = topoProperties.ClusterTolerance;

Set Map Topology as the current topology

if (map.CanSetMapTopology())
{
  //Set the topology of the map as map topology
  mapTopoProperties = await map.SetMapTopologyAsync() as MapTopologyProperties;
}

Set 'No Tpology' as the current topology

if (map.CanClearTopology())
{
  //Clears the topology of the map - no topology
  await map.ClearTopologyAsync();
}

Set the current topology by name

if (map.CanSetActiveTopology("TopologyName"))
{
  await map.SetActiveTopologyAsync("TopologyName");
}

Set the current topology by topologyProperties

if (map.CanSetActiveTopology(gdbTopoProperties))
{
  await map.SetActiveTopologyAsync(gdbTopoProperties);
}

Map Topology

Build Map Topology

private async Task BuildGraphWithActiveView()
{
  await QueuedTask.Run(() =>
  {
    //Build the map topology graph
    MapView.Active.BuildMapTopologyGraph<TopologyDefinition>(async topologyGraph =>
    {
      //Getting the nodes and edges present in the graph
      var topologyGraphNodes = topologyGraph.GetNodes();
      var topologyGraphEdges = topologyGraph.GetEdges();

      foreach (var node in topologyGraphNodes)
      {
        // do something with the node
      }
      foreach (var edge in topologyGraphEdges)
      {
        // do something with the edge
      }

      MessageBox.Show($"Number of topo graph nodes are:  {topologyGraphNodes.Count}.\n Number of topo graph edges are {topologyGraphEdges.Count}.", "Map Topology Info");
    });
  });
}

Attributes Pane Context MenuItems

Retrieve SelectionSet from command added to Attribute Pane Context Menu

await QueuedTask.Run(async () =>
{
  var selSet = FrameworkApplication.ContextMenuDataContextAs<SelectionSet>();
  if (selSet == null)
    return;

  int count = selSet.Count;
  if (count == 0)
    return;

  var op = new EditOperation();
  op.Name = "Delete context";
  op.Delete(selSet);
  await op.ExecuteAsync();
});

Ground to Grid

G2G Settings

CIMGroundToGridCorrection correction = null;
bool isCorecting = correction.IsCorrecting();   // equivalent to correction != null && correction.Enabled;
bool UsingOffset = correction.UsingDirectionOffset();   // equivalent to correction.IsCorrecting() && correction.UseDirection;
double dOffset = correction.GetDirectionOffset(); // equivalent to correction.UsingDirectionOffset() ? correction.Direction : DefaultDirectionOffset;
bool usingDistanceFactor = correction.UsingDistanceFactor();  // equivalent to correction.IsCorrecting() && correction.UseScale;
bool usingElevation = correction.UsingElevationMode(); // equivalent to correction.UsingDistanceFactor() && c.ScaleType == GroundToGridScaleType.ComputeUsingElevation;
bool usingSFactor = correction.UsingConstantScaleFactor();  //; equivalent to correction.UsingDistanceFactor() && correction.ScaleType == GroundToGridScaleType.ConstantFactor;
double dSFactor = correction.GetConstantScaleFactor(); // equivalent to correctionc.UsingDistanceFactor() ? correction.ConstantScaleFactor : DefaultConstantScaleFactor;

EditingOptions

Get/Set Editing Options

//toggle, switch option values
var options = ApplicationOptions.EditingOptions;

options.EnforceAttributeValidation = !options.EnforceAttributeValidation;
options.WarnOnSubtypeChange = !options.WarnOnSubtypeChange;
options.InitializeDefaultValuesOnSubtypeChange = !options.InitializeDefaultValuesOnSubtypeChange;
options.UncommitedAttributeEdits = (options.UncommitedAttributeEdits == 
  UncommitedEditMode.AlwaysPrompt) ? UncommitedEditMode.Apply : UncommitedEditMode.AlwaysPrompt;

options.StretchGeometry = !options.StretchGeometry;
options.StretchTopology = !options.StretchTopology;
options.UncommitedGeometryEdits = (options.UncommitedGeometryEdits == 
  UncommitedEditMode.AlwaysPrompt) ? UncommitedEditMode.Apply : UncommitedEditMode.AlwaysPrompt;

options.ActivateMoveAfterPaste = !options.ActivateMoveAfterPaste;
options.ShowFeatureSketchSymbology = !options.ShowFeatureSketchSymbology;
options.FinishSketchOnDoubleClick = !options.FinishSketchOnDoubleClick;
options.AllowVertexEditingWhileSketching = !options.AllowVertexEditingWhileSketching;
options.ShowDeleteDialog = !options.ShowDeleteDialog;
options.EnableStereoEscape = !options.EnableStereoEscape;
options.DragSketch = !options.DragSketch;
options.ShowDynamicConstraints = !options.ShowDynamicConstraints;
options.IsDeflectionDefaultDirectionConstraint = 
  !options.IsDeflectionDefaultDirectionConstraint;
options.IsDirectionDefaultInputConstraint = !options.IsDirectionDefaultInputConstraint;
options.ShowEditingToolbar = !options.ShowEditingToolbar;
options.ToolbarPosition = (options.ToolbarPosition == ToolbarPosition.Bottom) ? 
          ToolbarPosition.Right : ToolbarPosition.Bottom;
options.ToolbarSize = (options.ToolbarSize == ToolbarSize.Medium) ? 
          ToolbarSize.Small : ToolbarSize.Medium;
options.MagnifyToolbar = !options.MagnifyToolbar;

options.EnableEditingFromEditTab = !options.EnableEditingFromEditTab;
options.AutomaticallySaveEdits = !options.AutomaticallySaveEdits;
options.AutoSaveByTime = !options.AutoSaveByTime;
options.SaveEditsInterval = (options.AutomaticallySaveEdits) ? 20 : 10;
options.SaveEditsOperations = (options.AutomaticallySaveEdits) ? 60 : 30;
options.SaveEditsOnProjectSave = !options.SaveEditsOnProjectSave;
options.ShowSaveEditsDialog = !options.ShowSaveEditsDialog;
options.ShowDiscardEditsDialog = !options.ShowDiscardEditsDialog;
options.DeactivateToolOnSaveOrDiscard = !options.DeactivateToolOnSaveOrDiscard;
options.NewLayersEditable = !options.NewLayersEditable;

Get/Set Editing Annotation Options

var eOptions = ApplicationOptions.EditingOptions;

var followLinkedLines = eOptions.AutomaticallyFollowLinkedLineFeatures;
var followLinedPolygons = eOptions.AutomaticallyFollowLinkedPolygonFeatures;
var usePlacementProps = eOptions.UseAnnotationPlacementProperties;
var followMode = eOptions.AnnotationFollowMode;
var placementMode = eOptions.AnnotationPlacementMode;

     
eOptions.AnnotationFollowMode = AnnotationFollowMode.Parallel;
eOptions.AnnotationPlacementMode = AnnotationPlacementMode.Left;

Get Sketch Vertex Symbology Options

var options = ApplicationOptions.EditingOptions;

//Must use QueuedTask
QueuedTask.Run(() =>
{
  //There are 4 vertex symbol settings - selected, unselected and the
  //current vertex selected and unselected.
  var reg_select = options.GetVertexSymbolOptions(VertexSymbolType.RegularSelected);
  var reg_unsel = options.GetVertexSymbolOptions(VertexSymbolType.RegularUnselected);
  var curr_sel = options.GetVertexSymbolOptions(VertexSymbolType.CurrentSelected);
  var curr_unsel = options.GetVertexSymbolOptions(VertexSymbolType.CurrentUnselected);

  //to convert the options to a symbol use
  //GetPointSymbol
  var reg_sel_pt_symbol = reg_select.GetPointSymbol();
  //ditto for reg_unsel, curr_sel, curr_unsel
});

Get Sketch Segment Symbology Options

//var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() =>
{
  var seg_options = options.GetSegmentSymbolOptions();
  //to convert the options to a symbol use
  //SymbolFactory. Note: this is approximate....sketch isn't using the
  //CIM directly for segments
  var layers = new List<CIMSymbolLayer>();
  var stroke0 = SymbolFactory.Instance.ConstructStroke(seg_options.PrimaryColor, 
    seg_options.Width, SimpleLineStyle.Dash);
  layers.Add(stroke0);
  if (seg_options.HasSecondaryColor) {
    var stroke1 = SymbolFactory.Instance.ConstructStroke(
      seg_options.SecondaryColor, seg_options.Width, SimpleLineStyle.Solid);
    layers.Add(stroke1);
  }
  //segment symbology only
  var sketch_line = new CIMLineSymbol() {
    SymbolLayers = layers.ToArray()
  };
});

Set Sketch Vertex Symbol Options

//var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() =>
{
  //change the regular unselected vertex symbol
  //default is a green, hollow, square, 5pts. Change to
  //Blue outline diamond, 10 pts
  var vertexSymbol = new VertexSymbolOptions(VertexSymbolType.RegularUnselected);
  vertexSymbol.OutlineColor = ColorFactory.Instance.BlueRGB;
  vertexSymbol.MarkerType = VertexMarkerType.Diamond;
  vertexSymbol.Size = 10;

  //Are these valid?
  if (options.CanSetVertexSymbolOptions(
       VertexSymbolType.RegularUnselected, vertexSymbol)) {
    //apply them
    options.SetVertexSymbolOptions(VertexSymbolType.RegularUnselected, vertexSymbol);
          }
});

Set Sketch Segment Symbol Options

//var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() =>
{
  //change the segment symbol primary color to green and
  //width to 1 pt
  var segSymbol = options.GetSegmentSymbolOptions();
  segSymbol.PrimaryColor = ColorFactory.Instance.GreenRGB;
  segSymbol.Width = 1;

  //Are these valid?
  if (options.CanSetSegmentSymbolOptions(segSymbol)) {
    //apply them
    options.SetSegmentSymbolOptions(segSymbol);
          }
});

Set Sketch Vertex Symbol Back to Default

//var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() =>
{
  //ditto for reg selected and current selected, unselected
  var def_reg_unsel = 
    options.GetDefaultVertexSymbolOptions(VertexSymbolType.RegularUnselected);
  //apply default
  options.SetVertexSymbolOptions(
    VertexSymbolType.RegularUnselected, def_reg_unsel);
});

Set Sketch Segment Symbol Back to Default

//var options = ApplicationOptions.EditingOptions;
QueuedTask.Run(() =>
{
  var def_seg = options.GetDefaultSegmentSymbolOptions();
  options.SetSegmentSymbolOptions(def_seg);
});

VersioningOptions

Get and Set Versioning Options

var vOptions = ApplicationOptions.VersioningOptions;

vOptions.DefineConflicts = (vOptions.DefineConflicts == ConflictDetectionType.ByRow) ? 
  ConflictDetectionType.ByColumn : ConflictDetectionType.ByRow;
vOptions.ConflictResolution = (
  vOptions.ConflictResolution == ConflictResolutionType.FavorEditVersion) ? 
    ConflictResolutionType.FavorTargetVersion : ConflictResolutionType.FavorEditVersion;
vOptions.ShowConflictsDialog = !vOptions.ShowConflictsDialog;
vOptions.ShowReconcileDialog = !vOptions.ShowReconcileDialog;

Utility Network

Home

ProSnippets: Editing

Clone this wiki locally