ProGuide Dynamic Pop up Menu - kataya/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Map Exploration
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 guide demonstrates how to create a map tool that uses a dynamic pop-up menu. You use the tool to select (by point click) a single feature on a map where multiple features overlap each other. See ProConcepts Map Exploration, MapTool for more information on the MapTool pattern.

Prerequisites

  • Download and install the sample data required for this guide as instructed in Arcgis Pro SDK Community Samples Releases.
  • Create a new ArcGIS Pro Module Add-in, and name the project MapToolWithDynamicMenu. If you are not familiar with the ArcGIS Pro SDK, you can follow the steps in the ProGuide Build your first add-in to get started.
  • Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tool item to the add-in project, and name the item MapToolShowMenu.
  • There is no template to construct a dynamic pop-up menu, so you will construct the class from scratch in Step 1 and Step 3.

Step 1

Modify the Config.daml file tool item as follows:

  • Change the caption to "Show Selection Pop-up".
  • Change the tool heading to "Show Selection Pop-up" and the ToolTip text to "Click on overlapping point features on this map to pop-up a feature selection menu."
  • Add a new dynamicMenu tag under the controls tag as shown below.
...
<tool id="MapToolWithDynamicMenu_MapToolShowMenu" caption="Show Selection Pop-up" 
              className="MapToolShowMenu" loadOnClick="true" 
              smallImage="Images\GenericButtonRed16.png" 
              largeImage="Images\GenericButtonRed32.png" condition="esri_mapping_mapPane" keytip="z2">
  <tooltip heading="Show Selection Pop-up">
    Click on overlapping point features on this map to pop-up a feature selection menu.
    <disabledText />
  </tooltip>
</tool>
<dynamicMenu id="DynamicMenu_SelectPoint"
        className="DynamicSelectPointMenu"
        caption="Point Selection" />
...

Build the sample and validate the UI on the ArcGIS Pro ribbon.

6MapTool.png

Step 2

The required selection functions must be implemented in the MapToolShowMenu class.

First, the sketch type set in the constructor is changed to SketchGeometryType.Point. Next, SketchOutputMode in the constructor is changed to SketchOutputMode.Screen. MapView.SelectFeatures and MapView.GetFeatures throw a System.ArgumentException if a 3D query is attempted in SketchOutputMode.Map, so you need to change the sketch projection to screen coordinates.

public MapToolShowMenu()
{
  IsSketchTool = true;
  SketchType = SketchGeometryType.Point;
  SketchOutputMode = SketchOutputMode.Screen;
}

The OnSketchCompleteAsync method can be overwritten to implement the required selection functionality. Since you "await" functions within your identify implementation, you have to add the "async" keyword to the OnSketchCompleteAsync method declaration. The MapTool base class provides an ActiveMapView property that provides a link to the current active map view. The MapView, in turn, provides a method GetFeatures that can be used to get all features that intersect a given geometry. The resulting features are manipulated into a list of triple tuple objects consisting of BasicFeatureLayer Name, the Object ID field Name, and the object id. This list of triple tuples is then passed into ShowContextMenu, which creates and displays the dynamic pop-up menu.

protected override async Task<bool> OnSketchCompleteAsync(Geometry geometry)
{
  var bottomRight = new Point();
  IList<Tuple<string, string, long>> tripleTuplePoints = 
            new List<Tuple<string, string, long>>();
  var hasSelection = await QueuedTask.Run(() =>
  {
    // geometry is a point
    var clickedPnt = geometry as MapPoint;
    if (clickedPnt == null) return false;
    // pixel tolerance
    var tolerance = 3;
    //Get the client point edges
    var topLeft = new Point(clickedPnt.X - tolerance, clickedPnt.Y + tolerance);
    bottomRight = new Point(clickedPnt.X + tolerance, clickedPnt.Y - tolerance);
    //convert the client points to Map points
    var mapTopLeft = MapView.Active.ClientToMap(topLeft);
    var mapBottomRight = MapView.Active.ClientToMap(bottomRight);
    //create a geometry using these points
    Geometry envelopeGeometry = EnvelopeBuilder.CreateEnvelope(mapTopLeft, mapBottomRight);
    if (envelopeGeometry == null) return false;
    //Get the features that intersect the sketch geometry.
    var result = ActiveMapView.GetFeatures(geometry);
    foreach (var kvp in result)
    {
      var bfl = kvp.Key;
      // only look at points
      if (kvp.Key.ShapeType != esriGeometryType.esriGeometryPoint) continue;
      var layerName = bfl.Name;
      var oidName = bfl.GetTable().GetDefinition().GetObjectIDField();
      foreach (var oid in kvp.Value)
      {
        tripleTuplePoints.Add(new Tuple<string, string, long>(layerName, oidName, oid));
      }
    }
    return true;
  });
  if (hasSelection)
  {
    ShowContextMenu(bottomRight, tripleTuplePoints);
  }
  return true;
}

Finally, you need to add a method that performs the actual display of the dynamic menu (with the Id DynamicMenu_SelectPoint specified in config.daml under the dynamicMenu tag). First, use the FrameworkApplication's CreateContextMenu method to create an instance of your DynamicMenu_SelectPoint pop-up menu. The resulting contextMenu object is of the type System.Windows.Controls.ContextMenu, and there are a few events and properties in this class that are noteworthy: contextMenu.Closed allows you to hook an event when the pop-up menu is closed, and the IsOpen property displays the pop-up menu when set to true. The class for DynamicMenu_SelectPoint is specified in the config.daml file as shown below in the className attribute:

<dynamicMenu id="DynamicMenu_SelectPoint" className="DynamicSelectPointMenu" ... />

Use a static method in DynamicSelectPointMenu called SetMenuPoints to pass the tripleTuplePoints (holding your selected feature record set) to the dynamic pop-up menu.

private void ShowContextMenu(System.Windows.Point screenLocation, 
            IList<Tuple<string, string, long>> tripleTuplePoints)
{
  var contextMenu = FrameworkApplication.CreateContextMenu("DynamicMenu_SelectPoint", 
                        () => screenLocation);
  if (contextMenu == null) return;
  DynamicSelectPointMenu.SetMenuPoints(tripleTuplePoints);
  contextMenu.Closed += (o, e) =>
  {
    // nothing to do
    System.Diagnostics.Debug.WriteLine(e);
  };
  contextMenu.IsOpen = true;
}

Step 3

Add the DynamicSelectPointMenu that handles the dynamic pop-up menu and the feature selection.

DynamicSelectPointMenu uses an image in its pop-up display. Add the image (use the file shown below) to the image folder of your project, name the image esri_PntFeature.png, and set the build action to Resource. esri_PntFeature.png As shown in the previous step, the static method in DynamicSelectPointMenu called SetMenuPoints is used to pass in the tripleTuplePoints (holding your selected feature record set), which are added as menu items in the OnPopup method.

internal class DynamicSelectPointMenu : DynamicMenu
{
  internal delegate void ClickAction(Tuple<string, string, long> selectedTrippTuple);
  private const string ImagePath =
    @"pack://application:,,,/MapToolWithDynamicMenu;Component/Images/esri_PntFeature.png";
  private static IList<Tuple<string, string, long>> _menuItems 
                = new List<Tuple<string, string, long>>();
  public static void SetMenuPoints(IList<Tuple<string, string, long>> tripleTuples)
  {
    _menuItems = new List<Tuple<string, string, long>>(tripleTuples);
  }   
  protected override void OnPopup()
  {
    if (_menuItems == null || _menuItems.Count == 0)
    {
      this.Add("No features found", "", false, true, true);
    }
    else
    {
      ClickAction theAction = OnMenuItemClicked;
      Add("Select feature:", "", false, true, true);
      foreach (var tuple in _menuItems)
      {
        var layer = tuple.Item1;
        var oid = tuple.Item3;
        Add($"{layer}: Id {oid}",
            ImagePath, 
            false, true, false, theAction, tuple);
      }
    }
  }

  private static void OnMenuItemClicked(Tuple<string, string, long> selectedTrippTuple)
  {
    QueuedTask.Run(() =>
    {
      var mapView = MapView.Active;
      var layers =
        mapView.Map.GetLayersAsFlattenedList()
          .Where(l => l.Name.Equals(selectedTrippTuple.Item1,
            StringComparison.CurrentCultureIgnoreCase));
      foreach (var featureLayer in layers.OfType<FeatureLayer>())
      {
        // select the features with a given OID
        featureLayer.Select(new QueryFilter()
        {
          WhereClause = $@"{selectedTrippTuple.Item2} = {selectedTrippTuple.Item3}"
        });
        break;
      }
    });
  }
}

Step 4

Rebuild the add-in. Fix any compilation errors.

Step 5

Debug the add-in. Run the debugger and start ArcGIS Pro. Open the C:\Data\Interacting with Maps\Interacting with Maps.aprx project that contains a dataset that works with this sample. Try the selection tool over a map area with a high point feature density as shown here:

6MapTool2D.png

6MapTool2D_2.png

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