ProGuide Editing tool - kataya/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Editing
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          11/24/2020
ArcGIS Pro:    2.7
Visual Studio: 2017, 2019

This ProGuide shows how to build an editing tool to interact with and modify existing features. The code used to illustrate this add-in can be found at Sketch Tool Demo Sample.

Prerequisite

Create a new add-in, and add a map tool to the add-in called CutTool.cs.

Step 1

In the constructor, change the SketchType of the map tool to a line geometry type. This is the geometry of the map tool feedback used to symbolize the user interaction. For this example, you're using a single solid line that adds a linear segment on each mouse click.

// select the type of tool feedback you wish to implement.  
SketchType = SketchGeometryType.Line;
// a sketch feedback is needed
IsSketchTool = true;
// the geometry is needed in map coordinates
SketchOutputMode = ArcGIS.Desktop.Mapping.SketchOutputMode.Map;

Step 2

Replace the code in the OnSketchCompleteAsync method. Assign a method to modify features in the editor, and, since the API calls need to happen on the CIM thread, use a lambda function to handle the operation using the sketch geometry. For now, you simply declare the method. You will specify the code in step 4.

protected override Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
    return QueuedTask.Run(() => ExecuteCut(geometry));
}

protected Task<bool> ExecuteCut(Geometry geometry)
{
    return Task.FromResult(true);
}

Step 3

Open the Config.daml file and go to the entry for the tool. Add a categoryRefID attribute setting it to categoryRefID="esri_editing_CommandList". This specifies the tool is to appear in the Modify Features pane. Also add the content xml node to the tool specification. The attribute L_group creates an entry in the pane with the specified name. The tool daml should be as below

<tool id="ProAppModule1_CutTool" categoryRefID="esri_editing_CommandList" 
      caption="CutTool" className="CutTool" loadOnClick="true" 
      smallImage="Images\GenericButtonRed16.png" 
      largeImage="Images\GenericButtonRed32.png">
  <tooltip heading="Tooltip Heading">Tooltip text<disabledText /></tooltip>
  <content L_group="Pro SDK Samples" gallery2d="true" gallery3d="false" />
</tool>

ProGuide: Editing - SketchTool L_group

The attributes gallery2d and gallery3d specify whether the tool is shown in the favorites group in the gallery for 2D or 3D editing tools on the Edit ribbon. In this guide, you'll add the tool to the favorites group in the 2D gallery. Note that the tool will always be shown in the galleries regardless of this tag setting.

ProGuide: Editing - SketchTool gallery2d

The modified xml configuration for the editing tool should look similar to the following definition:

<tool id="ProAppModule1_CutTool" categoryRefID="esri_editing_CommandList" 
      caption="CutTool" className="CutTool" loadOnClick="true" 
      smallImage="Images\GenericButtonRed16.png" 
      largeImage="Images\GenericButtonRed32.png">
  <tooltip heading="Tooltip Heading">Tooltip text<disabledText /></tooltip>
  <content L_group="Pro SDK Samples" gallery2d="true" gallery3d="false" />
</tool>

Step 4

Go back to the CutTool.cs file and add the following code to the ExecuteCut method. Since you're performing an edit, create a new instance of an EditOperation to scope the modifies. See EditOperation in the ProConcept documentation to learn about its functionality.

// create an edit operation
EditOperation cutOperation = new EditOperation();
cutOperation.Name = "Cut Elements";
cutOperation.ProgressMessage = "Working...";
cutOperation.CancelMessage = "Operation canceled.";
cutOperation.ErrorMessage = "Error cutting polygons";
cutOperation.SelectModifiedFeatures = false;
cutOperation.SelectNewFeatures = false;

In the next step, you'll set up the target for the edit operation. For this example, you'll use the sketch geometry to perform a cut against all editable polygon features in the active map.

// create a collection of feature layers that can be edited
var editableLayers = ActiveMapView.Map.GetLayersAsFlattenedList()
    .OfType<FeatureLayer>()
    .Where(lyr => lyr.CanEditData() == true).Where(lyr => 
    lyr.ShapeType == esriGeometryType.esriGeometryPolygon);

Step 5

For each identified target layer, do the following:

  1. Search for the features that are crossing the sketch geometry.
  2. Get the underlying feature class and determine the field index for the "Description" attribute field.
// for each of the layers 
foreach (FeatureLayer editableFeatureLayer in editableLayers)
{
    // find the features crossed by the sketch geometry
    var rowCursor = editableFeatureLayer.Search(geometry, SpatialRelationship.Crosses);

    // get the feature class associated with the layer
    Table fc = editableFeatureLayer.GetTable();

    // find the field index for the 'Description' attribute
    int descriptionIndex = -1;
    descriptionIndex = fc.GetDefinition().FindField("Description");

For each returned feature, test whether the feature is completely intersected by the sketch geometry by using the GeometryEngine.Instance.Relate method. The string argument is detailed in the Dimensionally Extended Nine-Intersection Model. If the geometry is completely intersected, store its ObjectID in a list, and modify the Description attribute as part of the edit operation.

// add the feature IDs into our prepared list
while (rowCursor.MoveNext())
{
    var feature = rowCursor.Current as Feature;
    if (feature.GetShape() != null)
    {
        // we are looking for polygons are completely intersected by the cut line
        if (GeometryEngine.Instance.Relate(geometry, feature.GetShape(), "TT*F*****"))
        {
            // add the current feature to the overall list of features to cut
            cutOIDs.Add(rowCursor.Current.GetObjectID());

            // adjust the attribute before the cut
            if (descriptionIndex != -1)
                cutOperation.Modify(rowCursor.Current, descriptionIndex, "Pro Sample");
        }
    }
}

Step 6

For every polygon layer, cue the cut for all the collected ObjectIDs. Once the code enumerates all the layers, execute the edit operation. Regardless of the number of modified features and polygon layers, there will be only one edit operation listed in the Undo/Redo stack for the active view.

// create an edit operation
EditOperation cutOperation = new EditOperation();
...
{ 
    // for each layer perform the cut
    ...
    // add the elements to cut into the edit operation
    cutOperation.Cut(editableFeatureLayer, cutOIDs, geometry);
}

//execute the operation
var operationResult = cutOperation.Execute();

return Task.FromResult(operationResult);

Step 7

To emphasize the interactive operation, you may want to change the sketch symbol. You can influence the appearance of the sketch by overriding the OnSketchModifiedAsync method. In this example, you'll change the style, width, and color of the line sketch after the second vertex.

protected override async Task<bool> OnSketchModifiedAsync()
{
    // retrieve the current sketch geometry
    Polyline cutGeometry = await base.GetCurrentSketchAsync() as Polyline;

    await QueuedTask.Run(() =>
    {
        // if there are more than 2 vertices in the geometry
        if (cutGeometry.PointCount > 2)
        {
            // adjust the sketch symbol
            var symbolReference = base.SketchSymbol;
            if (symbolReference == null)
            {
                var cimLineSymbol = SymbolFactory.Instance.ConstructLineSymbol(ColorFactory.Instance.RedRGB, 
                    3, SimpleLineStyle.DashDotDot);
                base.SketchSymbol = cimLineSymbol.MakeSymbolReference();
            }
            else
            {
                symbolReference.Symbol.SetColor(ColorFactory.Instance.RedRGB);
                base.SketchSymbol = symbolReference;
            }
        }
    });

    return true;
}

Step 8

For the last step, you'll set up a custom action after the edit operation has successfully completed. Switch to the Module1.cs file and override the Initialize method for the add-in. Subscribe to the EditCompletedEvent and define a callback method called ReportNumberOfRowsChanged.

protected override bool Initialize()
{
    // subscribe to the completed edit operation event
    EditCompletedEvent.Subscribe(ReportNumberOfRowsChanged);

    return true;
}

The callback method filters out the modifies that happened during the edit operation and reports the number of modifies back to the user.

/// <summary>
/// Method containing actions as the result of the EditCompleted (the operation) event.
/// This method reports the total number of changed rows/features.
/// </summary>
/// <param name="editArgs">Argument containing the layers where edits occurred and what 
/// types of changes.</param>
/// <returns></returns>
private Task<bool> ReportNumberOfRowsChanged(EditCompletedEventArgs editArgs)
{
    // get the dictionary containing the modifies on the current feature 
    // operation 
    var editChanges = editArgs.Modifies;

    // use this variable to store the total number of modifies
    int countOfModifies = editChanges.Values.Sum(list => list.Count);

    if (countOfModifies > 0)
        MessageBox.Show($"{countOfModifies.ToString()} features changed");
    else
        MessageBox.Show("The current edit operation did not contain any row/feature 
                         modification.");

    return Task.FromResult(true);
}

For additional information on edit sketch tools consult ProConcepts Editing, sketch tools.

⚠️ **GitHub.com Fallback** ⚠️