Select to view content in your preferred language

Best practice for changing cursor in ArcGIS Pro MapTool without lag

488
1
03-26-2026 10:31 PM
rdc_hirohara
Occasional Contributor

Hello,

I am developing an ArcGIS Pro SDK MapTool in C#.

I want to change the mouse cursor depending on whether the current mouse position is on a selected polygon feature or not.
For example:

  • show `Hand` when the mouse is on the selected feature
  • show `Cross` otherwise

At first, I implemented the check inside `OnToolMouseMove` using `QueuedTask.Run`, something like:

protected override void OnToolMouseMove(MapViewMouseEventArgs args)
{
    _ = QueuedTask.Run(() =>
    {
        var mapPoint = MapView.Active.ClientToMap(args.ClientPoint);
        bool isHit = GeometryEngine.Instance.Intersects(_selectedGeometry, mapPoint);
        Cursor = isHit ? Cursors.Hand : Cursors.Cross;
    });

    base.OnToolMouseMove(args);
}

However, this caused noticeable lag.
It looks like `QueuedTask` calls keep getting queued during mouse movement, and cursor updates become delayed.

I improved the behavior by preventing multiple simultaneous cursor checks and only processing the latest mouse position, but I still have these questions:

  1. Is there any official or recommended best practice for changing the cursor dynamically in a `MapTool` based on mouse position?
  2. Is there any way to avoid `ClientToMap` on every mouse move while still keeping correct hit-testing?
  3. Is the default `MapTool` cursor documented anywhere?I am currently using `Cursors.Cross` as the non-hit cursor, but I am not sure whether that matches ArcGIS Pro’s default tool cursor.
  4. More generally, what is the recommended pattern for high-frequency mouse-move processing in a `MapTool` when MCT access is required?

For context, the selected geometry may sometimes include multiple polygons, and possibly polygons with holes, so replacing the hit-test with a very rough custom approximation is not always ideal.

If anyone has sample code, recommendations, or experience with this kind of cursor handling, I would appreciate it.

Thank you.

0 Kudos
1 Reply
sjones_esriau
Esri Contributor

Hey there,

Traditional OnToolMouseMove tricks wont work here but you can use some Task secret sauce (TaskCompletionSource).

    private Geometry _firstSelectedGeometry = null;
    private TaskCompletionSource<MapPoint> _tcs;
    private System.Windows.Point _lastClientPoint;
    private bool _processing = false; 

    protected override void OnToolMouseMove(MapViewMouseEventArgs args)
    {
      _lastClientPoint = args.ClientPoint;

      // If a conversion is already in progress, don't start another
      if (_processing)
        return;

      _processing = true;
      _tcs = new TaskCompletionSource<MapPoint>();

      QueuedTask.Run(() =>
        {
          var mapPoint = MapView.Active.ClientToMap(args.ClientPoint);
          _tcs.TrySetResult(mapPoint);
        });

      // Continue on UI thread when MCT finishes
      _ = HandleMapPointAsync();

      base.OnToolMouseMove(args);
    }

    private async Task HandleMapPointAsync()
    {
      try
      {
        var mapPoint = await _tcs.Task;
        bool isHit = GeometryEngine.Instance.Intersects(_firstSelectedGeometry, mapPoint);
        Cursor = isHit ? Cursors.Hand : Cursors.Cross;
      }
      finally
      {
        _processing = false;
      }
    }

 

Why this works

  • Only one ClientToMap is ever queued.
  • Latest mouse position always wins.

If you have complex geometry you could still do a pre-hit test with the evelope (.Contains) but this may not be required.

Ps. This is not definitive Esri code....

0 Kudos