ArcGIS Blog

Developers

ArcGIS Maps SDK for .NET

Migrating your legacy .NET SketchEditor code to the new GeometryEditor

By Jennifer Nery

Code examples for this blog are written in C# using ArcGIS Maps SDK for .NET; but the Geometry Editor concepts all apply to Java, Kotlin, Qt, and Swift.

The 200.2 release of the ArcGIS Maps SDKs for Native Apps paves the way to a brand new GeometryEditor in .NET. Learning from developing the SketchEditor, we made significant improvements in its replacement. It is now built from the same core C++ to provide your applications a consistent, intuitive, easy to use, highly customizable, more reliable, and performant editor. This single core implementation will enable us to fulfill your most requested enhancements, like snapping and curve editing, more efficiently across all the Native SDKs.

In this article, I will cover the following topics to help you decide when and how to navigate the code migration process for .NET:

Functional equivalence

The SketchEditor is deprecated in the 200.2 release, replaced with an API that has a set of comparable functionalities.

To name a few, the GeometryEditor enables you to:

  • interactively or programmatically create or update a point, polyline, polygon, and multipoint geometries
  • edit vertices, draw in freehand mode, or start with predefined shapes
  • transform geometries by inserting or deleting vertices, moving, rotating, or scaling the geometries
  • undo and redo any edit made on the geometry and be notified when the geometry changes
  • update the visibility and appearance of the tool handles used in vertex editing, rotation, and scale

You will notice that the start methods take remarkably similar parameters. There are two main groups: Draw, creates a new geometry based on type, and edit, updates a specified geometry. The GeometryEditor in support of these operations responds to the same map interaction using mouse, stylus, or touch devices; and similar routine calls for insert, move, delete, and replace.

The following table highlights the equivalent support for drawing and editing in the GeometryEditor.

SketchEditor

GeometryEditor

Draw

StartAsync(SketchCreationMode, bool) : Task<Geometry> Start(GeometryType)
StartAsync(SketchCreationMode, SketchEditConfiguration) : Task<Geometry>

Edit

StartAsync(Geometry) : Task<Geometry> Start(Geometry)
StartAsync(Geometry, SketchCreationMode) : Task<Geometry>
StartAsync(Geometry, SketchCreationMode, SketchEditConfiguration) : Task<Geometry>

You will notice that the SketchEditConfiguration parameter remains an accessible configuration from the Tool property in the GeometryEditor. The SketchEditor and its supporting classes have a counterpart in the GeometryEditor, and they are as follows:

Old

New

SketchEditor GeometryEditor
SketchCreationMode GeometryType, ShapeToolType, FreehandTool
SketchEditConfiguration InteractionConfiguration
SketchStyle GeometryEditorStyle
SketchVertex GeometryEditorVertex, GeometryEditorMidVertex, GeometryEditorGeometry, GeometryEditorPart
SketchResizeMode GeometryEditorScaleMode

Improvements

Given the migration table above, you might ask why the SketchCreationMode enum has three items replacing it. The GeometryEditor (through the Tool property) enables vertex editing, freehand drawing, or transforming shapes with VertexTool, FreehandTool or ShapeTool, respectively.

Previously, predefined shapes were only available when creating a polygon geometry.

Notice that while there is no circle type, the ShapeTool’s Configuration property has a ScaleMode that, when set to Uniform, will create a circle:

var tool = ShapeTool.Create(ShapeToolType.Ellipse);
tool.Configuration.ScaleMode = GeometryEditorScaleMode.Uniform;

The animation below demonstrates interactively drawing geometries with the GeometryEditor.

Demonstrates interactively drawing geometries with the GeometryEditor.

With the Tool property, you can also configure allowable transformations for interaction and customize the style of these tool handles without interrupting the edit session.

The selected element reports which interaction options are available through its properties.

The animation below demonstrates interactively editing geometries while customizing tool handles without interrupting the GeometryEditor’s edit session.

Demonstrates interactively editing geometries while customizing tool handles without interrupting the GeometryEditor’s edit session.

With the GeometryEditor, you can do more than update a single vertex programmatically. The select, delete, and move methods act on a SelectedElement property. This element can be the entire geometry, a vertex, a mid-vertex, or a single part in a multipart geometry.

To add a new part, enable the Tool’s part creation and clear all selection before the next interaction or call to insert vertex. For transformation to apply on all parts, select the entire geometry before the next interaction or call to rotate/scale.

The animation below demonstrates drawing and editing a multipart polygon with the GeometryEditor.

Demonstrates drawing and editing a multipart polygon with the GeometryEditor.

.NET support

One noticeable difference .NET developers may find is the absence of ICommand properties and task-based asynchronous methods in the GeometryEditor. While data binding is supported with all GeometryEditor classes implementing INotifyPropertyChanged, ICommand properties that update the state of controls is usually coupled with business logic. Therefore, commands are best implemented in your own ViewModel.

Some examples of data binding are:

  • A one-way binding to the GeometryEditor’s IsStarted (formerly, IsEnabled) property can change the visibility of your controls.
  • A two-way binding to a slider’s value property can directly update the Opacity of the GeometryEditor’s tool handles.

Unlike these simple properties, commands tend to do more than update the View and perform operations on the Model. So, the GeometryEditor exposes methods and properties that will help you build commands instead. We realized that the presumptions we made in completing your draw or edit session in the SketchEditor should account for your application logic.

Commands use Methods/Properties

AddCommand InsertVertex(MapPoint) when IsStarted
DeleteCommand DeleteSelectedElement() when SelectedElement exists
UndoCommand Undo() when CanUndo
RedoCommand Redo() when CanRedo
CancelCommand Stop() when IsStarted
CompleteCommand Stop() when IsStarted and Geometry passes validation

Previously, the StartAsync() method with drawAndEdit: false, when awaited, provided a convenient way to return a completed geometry using the following gestures for these creation modes:

  • point: tap
  • polyline/polygon: double tap
  • freehand or shapes: pointer up or release

However, with this logic baked into the SketchEditor, you were unable to define the gesture, key combination, or state that determines the readiness of the geometry. With more control, you could include logic to do things like validate more than just the minimum number of vertices for this geometry, add multiple parts to this geometry, or perhaps even tie this draw or edit with a GeometryEngine method that cuts, reshapes, or auto completes with another geometry.

Asynchronous start task with geometry result

To create an asynchronous start task similar to what the SketchEditor provided, use a TaskCompletionSource<Geometry> to set the geometry result and handle its cancellation or any exception.

  • To set the geometry result:
    • Decide which events to use
      • Platform events such as keyboard, gesture, or speech
      • GeoView events like tapped, double tapped, or holding
    • Perform validations on the Geometry property
    • Call Stop(), do any post processing on its return value, and set the result
  • To handle cancellation or an exception:
    • Set canceled when IsStarted becomes false
    • Set exception to propagate any error that occurs

The code example below assumes your ViewModel has created a GeometryEditor instance used by your MapView. For brevity of this snippet, I’m using the GeometryEditor’s PropertyChanged event. You can use the appropriate geometry builder’s IsSketchValid or be more creative about validating the geometry. The code awaiting the result of this start method should then be prepared to handle TaskCanceledException.

public Task<Geometry> StartAsync(GeometryType geometryType)
{
    var tcs = new TaskCompletionSource<Geometry>();

    PropertyChangedEventHandler? onPropertyChanged = null;
    onPropertyChanged = (s, e) =>
    {
        if (e.PropertyName == nameof(GeometryEditor.Geometry) && 
IsSketchValid() && _geometryEditor.Stop() is Geometry geometry)
        {
            _geometryEditor.PropertyChanged -= onPropertyChanged;
            tcs.TrySetResult(geometry);
        }
        else if (e.PropertyName == nameof(GeometryEditor.IsStarted) &&
!_geometryEditor.IsStarted)
        {
            _geometryEditor.PropertyChanged -= onPropertyChanged;
            tcs.TrySetCanceled();
        }
    };
    _geometryEditor.PropertyChanged += onPropertyChanged;

    try
    {
        _geometryEditor.Start(geometryType);
    }
    catch (Exception ex)
    {
        tcs.TrySetException(ex);
    }
    return tcs.Task;
}

You can also call Stop() when the ENTER or ESC keys are pressed or when certain conditions in your app are satisfied. You could enforce certain feature attribute values with the geometry change, snap updated geometry with another feature, or ensure that the edit abides by specific rules. For example, an update to a utility network feature should be backed by network rules.

ICommand properties

To create ICommand properties, it is important to know the following:

  • what it needs to do (action),
  • when it can do it (predicate), and
  • when its ability to do it changes (raise predicate changed).

You can use the GeometryEditor methods and subscribe to a PropertyChanged event to be notified when a method can execute. Using .NET Community Toolkit’s RelayCommand, an undo command can be implemented this way:

private IRelayCommand? _undoCommand;
public IRelayCommand UndoCommand => _undoCommand ??=
    new RelayCommand(() => _geometryEditor.Undo(),
() => _geometryEditor.CanUndo);

private void OnPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == nameof(GeometryEditor.CanUndo))
        UndoCommand.NotifyCanExecuteChanged();
}

Conclusion

I hope this article has helped illustrate how the GeometryEditor now supersedes the legacy SketchEditor in capabilities. With:

  • Better shape support, you can transform new shapes and create predefined shapes as line geometries.
  • Enhanced configuration, you can refine allowable transformations to a specific geometry editor element.
  • Complete multipart support, you can create, update, move, rotate, or scale individual parts.
  • Improved programmatic support, you can now call rotate or scale; and have delete and move methods act on selected element.

By providing your applications greater flexibility, you can tailor the GeometryEditor to satisfy existing SketchEditor workflows. With more fine-grained control, you can incorporate your business requirements. For .NET developers migrating to GeometryEditor from SketchEditor, while data binding comes for free, ICommand and asynchronous tasks need to move to your ViewModel using equivalent GeometryEditor methods and properties.

I invite you to try out the new Create and edit geometries sample available in all platforms in the Native SDKs GitHub repos to see the GeometryEditor in action. You can also read more about capturing geometry edits  for query and edit  or spatial and data analysis.

In the future, you can expect enhancements like snapping, rule-based editing, curve support, and more in the GeometryEditor. The SketchEditor and related classes will be removed in the next major release. I therefore encourage you to migrate your code as soon as possible.

I look forward to hearing how you have enhanced your application with the GeometryEditor. Please send your feedback through the ArcGIS Maps SDKs for Native Apps Esri Community.

Share this article