This tutorial explains how you can customize an existing handles provider to:

  • Change the default settings of a handles provider

  • Customize the handles used by a handles provider

  • Make sure that the customized handles provider is used during editing

Other customizations

You need to customize a handles provider for advanced use cases only. For other customization methods, see this article.

The code snippets in this tutorial are available in the editing sample.

See this article for more information about editing.

Configuring a handles provider

You can change the configuration of most handles provider implementations. For example, you can change the minimum and maximum point counts for the PolylineHandlesProviderPolylineHandlesProviderPolylineHandlesProvider and PolylineRingHandlesProviderPolylineRingHandlesProviderPolylineRingHandlesProvider, or you can change the handles they use.

The following sample code shows you how to create and customize a handles provider.

Program: Creating a handles provider
auto handlesProvider = std::make_shared<PolylineHandlesProvider>();
handlesProvider->setMaxPointCount(8);
var handlesProvider = new PolylineHandlesProvider {MaxPointCount = 8};
val handlesProvider = PolylineHandlesProvider()
handlesProvider.maxPointCount = 8

After creating a handles provider, you must configure it so that it’s used during editing:

  1. Make sure that an IFeatureHandlesProviderIFeatureHandlesProviderIFeatureHandlesProvider uses the geometry handles provider.

    Program: Let the feature handles provider use the custom geometry handles provider
    // Register the custom handles provider in a default composite with a high priority, so that it is used before all other handles providers
    // Using a composite instead of directly using the custom PolylineHandlesProvider makes sure that other geometries remain supported.
    auto geometryHandlesProvider = CompositeGeometryHandlesProvider::createDefault();
    geometryHandlesProvider->add(handlesProvider, Priority::high());
    
    // Make sure the custom geometry handles provider is used to editing the features
    auto featureHandlesProvider = std::make_shared<FeatureHandlesProvider>();
    featureHandlesProvider->setGeometryHandlesProvider(geometryHandlesProvider);
    // Register the custom handles provider in a default composite with a high priority, so that it is used before all other handles providers
    // Using a composite instead of directly using the custom PolylineHandlesProvider makes sure that other geometries remain supported.
    var geometryHandlesProvider = CompositeGeometryHandlesProvider.CreateDefault();
    geometryHandlesProvider.Add(handlesProvider, Priority.High);
    
    // Make sure the custom geometry handles provider is used to editing the features
    var featureHandlesProvider = new FeatureHandlesProvider {GeometryHandlesProvider = geometryHandlesProvider};
    // Register the custom handles provider in a default composite with a high priority, so that it is used before all other handles providers
    // Using a composite instead of directly using the custom PolylineHandlesProvider makes sure that other geometries remain supported.
    val geometryHandlesProvider = CompositeGeometryHandlesProvider.createDefault()
    geometryHandlesProvider.add(handlesProvider, Priority.High)
    
    // Make sure the custom geometry handles provider is used to editing the features
    val featureHandlesProvider = FeatureHandlesProvider()
    featureHandlesProvider.geometryHandlesProvider = geometryHandlesProvider
  2. Configure that IFeatureHandlesProviderIFeatureHandlesProviderIFeatureHandlesProvider in an IFeatureEditConfigurationIFeatureEditConfigurationIFeatureEditConfiguration

    Program: Configure the feature handles provider in a feature edit configuration
    class CustomEditConfiguration final : public IFeatureEditConfiguration {
    public:
      void edit(const Feature& /*feature*/, LayerId /*layerId*/, const std::shared_ptr<Map>& /*map*/, FeatureEditConfigurationBuilder& builder) const override {
        // Use a custom handles provider for all features
        builder.handlesProvider(_featureHandlesProvider).submit();
      }
    
    private:
      std::shared_ptr<IFeatureHandlesProvider> _featureHandlesProvider;
    };
    private sealed class CustomEditConfiguration : IFeatureEditConfiguration
    {
        private readonly IFeatureHandlesProvider _featureHandlesProvider;
    
        public void Edit(Feature feature, ulong layerId, Map map, FeatureEditConfigurationBuilder builder)
        {
            // Use a custom handles provider for all features
            builder.HandlesProvider(_featureHandlesProvider).Submit();
        }
    }
    private class CustomEditConfiguration : IFeatureEditConfiguration {
        private val featureHandlesProvider: IFeatureHandlesProvider
    
        override fun edit(
            feature: Feature,
            layerId: Long,
            map: Map,
            builder: FeatureEditConfigurationBuilder
        ) {
            // Use a custom handles provider for all features
            builder.handlesProvider(featureHandlesProvider).submit()
        }
    }
  3. Configure that IFeatureEditConfigurationIFeatureEditConfigurationIFeatureEditConfiguration on the FeatureLayerFeatureLayerFeatureLayer.

    Program: Configuring the feature edit configuration on the layer
    // Use a custom edit configuration that changes the editing behavior for this layer
    auto customEditConfiguration = std::make_shared<CustomEditConfiguration>();
    
    // Register the configuration. This makes the layer editable by default
    return FeatureLayer::newBuilder()
        .model(model) //
        .title("Tutorial 1")
        .editConfiguration(customEditConfiguration)
        .build();
    // Use a custom edit configuration that changes the editing behavior for this layer
    var customEditConfiguration = new CustomEditConfiguration();
    
    // Register the configuration. This makes the layer editable by default
    return FeatureLayer.NewBuilder()
        .Model(model)
        .Title("Tutorial 1")
        .EditConfiguration(customEditConfiguration)
        .Build();
    // Use a custom edit configuration that changes the editing behavior for this layer
    val customEditConfiguration = CustomEditConfiguration()
    
    // Register the configuration. This makes the layer editable by default
    return FeatureLayer.newBuilder()
        .model(model) //
        .title("Tutorial 1")
        .editConfiguration(customEditConfiguration)
        .build()

Changing the handles of a handle provider

For more fine-grained control over the handles used by a handles provider, you can implement your own handle factory. For example, if you implement your own handle factory for polylines, you can decide which handles it creates and uses to:

  • Move points

  • Remove points

  • Insert points

  • Change the elevation of a point

You can customize the handle factories for other geometries in a similar way. The handle to translate a FeatureFeatureFeature is provided by an IFeatureHandlesProviderIFeatureHandlesProviderIFeatureHandlesProvider. You can use the handle factory for the default implementation, FeatureHandlesProviderFeatureHandlesProviderFeatureHandlesProvider to customize the translate handle.

The following sections show you how to implement and use a handle factory.

Using a handle factory wrapper

You can configure custom handle factory implementations, typically wrappers, on the corresponding handles provider.

Program: Configuring custom handle factory implementations
// A custom handle factory provides fine-grained control over the handles that are used by the handles provider
auto defaultHandleFactory = handlesProvider->getHandleFactory();
auto customHandleFactory = std::make_shared<CustomPolylineHandleFactory>(defaultHandleFactory);
handlesProvider->setHandleFactory(customHandleFactory);
// A custom handle factory provides fine-grained control over the handles that are used by the handles provider
var defaultHandleFactory = handlesProvider.HandleFactory;
var customHandleFactory = new CustomPolylineHandleFactory(defaultHandleFactory);
handlesProvider.HandleFactory = customHandleFactory;
// A custom handle factory provides fine-grained control over the handles that are used by the handles provider
val defaultHandleFactory = handlesProvider.handleFactory
val customHandleFactory = CustomPolylineHandleFactory(defaultHandleFactory)
handlesProvider.handleFactory = customHandleFactory

Omitting a handle

To omit a type of handle, the corresponding factory method must return null.

Program: Omitting a handle type
std::shared_ptr<IEditHandle> CustomPolylineHandleFactory::createInsertPointHandle(const std::shared_ptr<Observable<std::shared_ptr<Polyline>>>& /*polyline*/,
                                                                                  size_t /*insertIndex*/,
                                                                                  const std::shared_ptr<IPointEditAction>& /*editAction*/,
                                                                                  const std::shared_ptr<FeatureEditContext>& /*context*/) {
  // Make sure that PolylineHandlesProvider doesn't create insert handles anymore
  // Note that it will still create append and prepend handles though.
  return nullptr;
}
public override IEditHandle CreateInsertPointHandle(Observable<Polyline> polyline, uint insertIndex, IPointEditAction action, FeatureEditContext context)
{
    // Make sure that PolylineHandlesProvider doesn't create insert handles anymore
    // Note that it will still create append and prepend handles though.
    return null;
}
override fun createInsertPointHandle(
    polyline: Observable<Polyline?>,
    insertIndex: Long,
    editAction: IPointEditAction,
    context: FeatureEditContext
): IEditHandle? {
    // Make sure that PolylineHandlesProvider doesn't create insert handles anymore
    return null
}

Replacing a handle

To replace a handle, you must re-implement the corresponding factory method. In the following example, we replace the handle to remove points from a polyline.

Program: Replacing a handle
std::shared_ptr<IEditHandle> CustomPolylineHandleFactory::createRemovePointHandle(const std::shared_ptr<Observable<std::shared_ptr<Polyline>>>& polyline,
                                                                                  size_t pointIndex,
                                                                                  const std::shared_ptr<IPointEditAction>& editAction,
                                                                                  const std::shared_ptr<FeatureEditContext>& /*context*/) {
  // Derive an observable Point from the observable Polyline. This point will be updated whenever the polyline is changed
  auto observableLocation = ObservablePolylineUtil::derivePointAtIndex(polyline, pointIndex);

  // Create a handle that is initially positioned on this location
  auto handle = std::make_shared<PointEditHandle>(observableLocation);

  // The handle will call the edit action when clicking on it using the right mouse button
  handle->addOnClickAction(editAction, 1).mouseButton(MouseButton::right()).cursor(MouseCursor::arrow());

  // The handle will call the edit action when performing a touch long press action
  handle->addOnTouchLongPressAction(editAction).cursor(MouseCursor::arrow());

  // Use a small red icon
  handle->setRegularIcon(std::make_shared<BasicIcon>(IconType::FilledRectangle, 6, Color(255, 0, 0, 64)));
  handle->setHighlightedIcon(std::make_shared<BasicIcon>(IconType::FilledRectangle, 6, Color(255, 0, 0, 192)));
  return handle;
}
public override IEditHandle CreateRemovePointHandle(Observable<Polyline> polyline, uint pointIndex, IPointEditAction action, FeatureEditContext context)
{
    // Derive an observable Point from the observable Polyline. This point will be updated whenever the polyline is changed
    var observableLocation = ObservablePolylineUtil.DerivePointAtIndex(polyline, pointIndex);

    // Create a handle that is initially positioned on this location
    var handle = new PointEditHandle(observableLocation);

    // The handle will call the edit action when clicking on it using the right mouse button
    handle.AddOnClickAction(action, 1).MouseButton(MouseButton.Right).Cursor(MouseCursor.Arrow);

    // The handle will call the edit action when performing a touch long press action
    handle.AddOnTouchLongPressAction(action).Cursor(MouseCursor.Arrow);

    // The handle will call the edit action when performing a touch long press action
    handle.AddOnTouchLongPressAction(action).Cursor(MouseCursor.Arrow);

    // Use a small red icon
    handle.RegularIcon = new BasicIcon(BasicIcon.IconType.FilledRectangle, 6, Color.FromArgb(64, 255, 0, 0));
    handle.HighlightedIcon = new BasicIcon(BasicIcon.IconType.FilledRectangle, 6, Color.FromArgb(192, 255, 0, 0));
    return handle;
}
override fun createRemovePointHandle(
    polyline: Observable<Polyline?>,
    pointIndex: Long,
    editAction: IPointEditAction,
    context: FeatureEditContext
): IEditHandle {
    // Derive an observable Point from the observable Polyline. This point will be updated whenever the polyline is changed
    val observableLocation = ObservablePolylineUtil.derivePointAtIndex(polyline, pointIndex)

    // Create a handle that is initially positioned on this location
    val handle = PointEditHandle(observableLocation)

    // The handle will call the edit action when performing a touch long press action
    handle.addOnTouchLongPressAction(editAction).cursor(MouseCursor.Arrow)

    // Use a small red icon
    handle.regularIcon = BasicIcon(
        BasicIcon.IconType.FilledRectangle,
        6,
        Color.valueOf(Color.argb(64, 255, 0, 0))
    )
    handle.highlightedIcon = BasicIcon(
        BasicIcon.IconType.FilledRectangle, 6, Color.valueOf(
            Color.argb(192, 255, 0, 0)
        )
    )
    return handle
}

Adding a handle

You can’t use the handle factories to add an extra handle. It requires a different approach. For a demonstration, see this tutorial.