ProGuide Dynamic Pop up Menu - Esri/arcgis-pro-sdk GitHub Wiki
Language: C#
Subject: Map Exploration
Contributor: ArcGIS Pro SDK Team <[email protected]>
Organization: Esri, http://www.esri.com
Date: 10/06/2024
ArcGIS Pro: 3.4
Visual Studio: 2022
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."
...
<tool id="MapToolWithDynamicMenu_MapToolShowMenu" caption="Show Selection Pop-up"
className="MapToolShowMenu" loadOnClick="true"
smallImage="GenericButtonRed16" largeImage="GenericButtonRed32"
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>
...
Build the sample and validate the UI on the ArcGIS Pro ribbon.
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 System.Windows.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 System.Windows.Point(clickedPnt.X - tolerance, clickedPnt.Y + tolerance);
bottomRight = new System.Windows.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 = EnvelopeBuilderEx.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.ToDictionary<BasicFeatureLayer>())
{
var basicFeatureLayer = kvp.Key;
// only look at points
if (basicFeatureLayer.ShapeType != esriGeometryType.esriGeometryPoint) continue;
var layerName = basicFeatureLayer.Name;
var oidName = basicFeatureLayer.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;
}
Step 3 Return to the config.daml file and modify it as follows
- Add a new dynamicMenu tag under the tool tag in the contents group as shown below.
<tool id="MapToolWithDynamicMenu_MapToolShowMenu"
...
</tool>
<dynamicMenu id="DynamicMenu_SelectPoint"
className="DynamicSelectPointMenu"
caption="Point Selection" />
Because we have added this dynamicMenu manually you will needs to add a class file to your project to define the DynamicSelectPointMenu class (specified in the className attribute above). Name the file DynamicSelectPointMenu.cs. We will return to this file in a moment.
Within the MapToolShowMenu.cs file, add the ShowContextMenu method. This will performs the actual display of the dynamic menu. First, use the FrameworkApplication's CreateContextMenu method to create an instance of the 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 code below also uses a static method on DynamicSelectPointMenu called SetMenuPoints which will 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
In the DynamicSelectPointMenu.cs file, change the class definition to the following
internal class DynamicSelectPointMenu : DynamicMenu
{
}
DynamicSelectPointMenu will use 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 Content.
Add the following code to the DynamicSelectPointMenu class. A brief explanation of the code follows. The static method called SetMenuPoints is used to cache the tripleTuplePoints (holding your selected feature record set) into an internal variable. These items will form the menu items to be displayed in the OnPopup method. Each menu item will be defined to reference the image and have a click action - OnMenuItemClicked along with a reference to the feature via its layer name and objectID before being added to the dynamic menu. The OnMenuItemClicked method takes the layer name and objectID of the menu item, finds the layer in the map and uses the Select method with a query filter referencing the objectID to select the feature in the active map.
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:
Choose an item from the popup menu add see the feature be selected in the map.