This tutorial explains how you can add more handles to an existing handles provider.

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.

Step 1 - Add an extra handle to an existing IEditHandles

The best way to add an extra handle to a set of handles is to wrap an existing IEditHandlesIEditHandlesIEditHandles instance, and add the handle. In this tutorial, we use two classes for this:

The following code shows how you can do this. You can find the full code in the sample.

Program: Wrapping an IEditHandles
class CustomFeatureEditHandles final : public EditHandlesWrapper {
public:
  explicit CustomFeatureEditHandles(std::shared_ptr<Observable<Feature>> feature,
                                    std::shared_ptr<IFeatureEditCallback> featureEditCallback,
                                    std::shared_ptr<IEditHandles> delegateHandles,
                                    const std::shared_ptr<FeatureEditContext>& context)
      : EditHandlesWrapper(std::move(delegateHandles)), _feature{std::move(feature)}, _featureEditCallback{std::move(featureEditCallback)} {
    // Create a handle that controls the transparency
    _transparencyHandle = createTransparencyHandle(context);
  }
  std::vector<std::shared_ptr<IEditHandle>> getList() const override {
    auto handles = EditHandlesWrapper::getList();
    if (_transparencyHandle) {
      handles.emplace_back(_transparencyHandle);
    }
    return handles;
  }

private:
  std::shared_ptr<PointEditHandle> createTransparencyHandle(const std::shared_ptr<FeatureEditContext>& context) {
    // Create a handle that changes the transparency when the handle is dragged
    return handle;
  }
};
internal sealed class CustomFeatureEditHandles : EditHandlesWrapper
{
    private readonly Observable<Feature> _feature; // The current feature
    private readonly IFeatureEditCallback _featureEditCallback; // Called by the transparency handle when changing the feature state
    private Observable<Polyline> _polyline; // The current polyline. This is derived from _feature
    private IInvalidationCallback _featureInvalidateCallback; // Used to detect feature changes, so that the handle location can be updated
    private readonly PointEditHandle _transparencyHandle; // The handle used to edit the transparency

    public CustomFeatureEditHandles(Observable<Feature> feature, IFeatureEditCallback featureEditCallback, IEditHandles delegateHandles, FeatureEditContext context) :
        base(delegateHandles)
    {
        _feature = feature;
        _featureEditCallback = featureEditCallback;
        // Create a handle that controls the transparency
        _transparencyHandle = CreateTransparencyHandle(context);
    }

    public override IList<IEditHandle> GetList()
    {
        var handles = base.GetList();
        if (_transparencyHandle == null) return handles;
        handles = new List<IEditHandle>(handles) { _transparencyHandle };
        return handles;
    }

    private PointEditHandle CreateTransparencyHandle(FeatureEditContext context)
    {
        // Create a handle that changes the transparency when the handle is dragged
        return handle;
    }
}
private class CustomFeatureEditHandles(// The current feature
    private val feature: Observable<Feature>, // Called by the transparency handle when changing the feature state
    private val featureEditCallback: IFeatureEditCallback,
    delegateHandles: IEditHandles,
    context: FeatureEditContext
) :
    EditHandlesWrapper(delegateHandles) {
    private lateinit var polyline // The current polyline. This is derived from _feature
            : Observable<Polyline?>
    private val transparencyHandle // The handle used to edit the transparency
            : PointEditHandle

    init {
        // Create a handle that controls the transparency
        transparencyHandle = createTransparencyHandle(context)
    }

    override fun getList(): List<IEditHandle> {
        return super.getList().toMutableList().apply {
            add(transparencyHandle)
        }
    }

    private fun createTransparencyHandle(context: FeatureEditContext): PointEditHandle {
        // Create a handle that changes the transparency when the handle is dragged
        return handle
    }

    companion object {
        private fun deriveLocationFromTransparency(
            polyline: Polyline?,
            feature: Feature
        ): Point? {
            return polyline?.let {
                val transparency = feature.getValue<Double>(EditingModelFactory.transparencyPropertyPath)!!.apply { coerceIn(0.1, 1.0) }
                val location0 = polyline.getPoint(0)
                val location1 = polyline.getPoint(1)
                val fraction = transparency * 0.25
                val geodesy = GeodesyCalculations.create(it.reference, it.interpolationType)
                val newLocation = geodesy.interpolate(location0, location1, -fraction)
                return Point(it.reference, newLocation!!)
            }
        }

        fun dot(v1: Coordinate, v2: Coordinate): Double {
            return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
        }

        fun parametricDistanceOfClosestPointOnLine(
            pt: Coordinate,
            p0: Coordinate,
            p1: Coordinate
        ): Double {
            val dt0 = Coordinate(pt.x - p0.x, pt.y - p0.y, pt.z - p0.z)
            val d10 = Coordinate(p1.x - p0.x, p1.y - p0.y, p1.z - p0.z)
            val denominator = dot(d10, d10)
            return if (denominator != 0.0) {
                dot(dt0, d10) / denominator
            } else 0.0
        }

        fun deriveTransparencyFromLocation(
            handleLocation: Point,
            polyline: Polyline
        ): Double {
            val location0 = polyline.getPoint(0)
            val location1 = polyline.getPoint(1)

            var closestPointParam = parametricDistanceOfClosestPointOnLine(handleLocation.location, location0, location1);
            closestPointParam = Math.min(closestPointParam, 0.0);
            val closestPoint = Coordinate(
                location0.x + (location1.x - location0.x) * closestPointParam,
                location0.y + (location1.y - location0.y) * closestPointParam,
                location0.z + (location1.z - location0.z) * closestPointParam
            )
            val geodesy = GeodesyCalculations.create(polyline.reference, polyline.interpolationType)
            val distance01 = geodesy.distance2D(location0, location1)
            val distanceTransparency = geodesy.distance2D(location0, closestPoint)
            if (distance01 == null || distanceTransparency == null) {
                return 1.0
            }
            val transparency = distanceTransparency / (distance01 * 0.25)
            return transparency.coerceIn(0.1, 1.0)
        }
    }
}

Step 2 - Create a handles provider

To make sure that our custom IEditHandlesIEditHandlesIEditHandles is used, we must provide it through an IFeatureHandlesProviderIFeatureHandlesProviderIFeatureHandlesProvider implementation. In this tutorial, we introduce the CustomFeatureHandlesProvider class for this purpose.

This class mainly delegates to an existing IFeatureHandlesProviderIFeatureHandlesProviderIFeatureHandlesProvider, and also creates a new CustomFeatureEditHandles instance when we call it.

Program: Creating an IFeatureHandlesProvider
CustomFeatureHandlesProvider::CustomFeatureHandlesProvider() : _delegateHandlesProvider(std::make_shared<FeatureHandlesProvider>()) {
}

bool CustomFeatureHandlesProvider::canProvide(const std::shared_ptr<Observable<Feature>>& feature, const std::shared_ptr<FeatureEditContext>& context) const {
  return _delegateHandlesProvider->canProvide(feature, context);
}

std::shared_ptr<IEditHandles> CustomFeatureHandlesProvider::provide(std::shared_ptr<Observable<Feature>> feature,
                                                                    const std::shared_ptr<FeatureEditContext>& context,
                                                                    std::shared_ptr<IFeatureEditCallback> featureEditCallback) const {
  auto delegateHandles = _delegateHandlesProvider->provide(feature, context, featureEditCallback);
  return std::make_shared<CustomFeatureEditHandles>(std::move(feature), std::move(featureEditCallback), delegateHandles, context);
}
public sealed class CustomFeatureHandlesProvider : IFeatureHandlesProvider
{
    private readonly IFeatureHandlesProvider _delegateHandlesProvider;

    public CustomFeatureHandlesProvider()
    {
        _delegateHandlesProvider = new FeatureHandlesProvider();
    }

    public bool CanProvide(Observable<Feature> feature, FeatureEditContext context)
    {
        return _delegateHandlesProvider.CanProvide(feature, context);
    }

    public IEditHandles Provide(Observable<Feature> feature, FeatureEditContext context, IFeatureEditCallback featureEditCallback)
    {
        var delegateHandles = _delegateHandlesProvider.Provide(feature, context, featureEditCallback);
        return new CustomFeatureEditHandles(feature, featureEditCallback, delegateHandles, context);
    }
}
object CustomFeatureHandlesProvider : IFeatureHandlesProvider {
    private val delegateHandlesProvider = FeatureHandlesProvider()

    override fun canProvide(feature: Observable<Feature>, context: FeatureEditContext): Boolean {
        return delegateHandlesProvider.canProvide(feature, context)
    }

    override fun provide(
        feature: Observable<Feature>,
        context: FeatureEditContext,
        featureEditCallback: IFeatureEditCallback
    ): IEditHandles? {
        return delegateHandlesProvider.provide(feature, context, featureEditCallback)?.let {
            CustomFeatureEditHandles(feature, featureEditCallback, it, context)
        }
    }
}

Step 3 - Use the custom feature handles provider

We need to make sure that the editing framework uses our custom handles provider, so we must configure it in an IFeatureEditConfigurationIFeatureEditConfigurationIFeatureEditConfiguration, and set that configuration on the FeatureLayerFeatureLayerFeatureLayer.

Program: Creating an IFeatureHandlesProvider
// 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 2")
    .editConfiguration(customEditConfiguration)
    .painter(std::make_shared<TransparencyFeaturePainter>())
    .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 2")
    .EditConfiguration(customEditConfiguration)
    .Painter(new TransparencyFeaturePainter())
    .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 2")
    .editConfiguration(customEditConfiguration)
    .painter(TransparencyFeaturePainter)
    .build()