Hello,
I am working on a configuration for ArcGIS Pro that is using a dynamic menu. When populating the menu, I need to make some determinations in order to know whether or not to add certain menu items.
Several of these require the use of QueuedTask. I need to know if the user has right-clicked on an element. So I get the mouse position, convert it to client and then convert to Map. This needs to be done in the QueuedTask.Run as converting from screen to client and client to map require this.
However, as soon as I try this, the context menu does not get populated. I've marked the OnPopup method as async in order to use await on the QueuedTask to no avail.
What am I doing wrong? Is there an alternative to this that I am missing?
This happens if I hijack the context menu for the ArcgIS Pro select tool (esri_mapping_selection2DContextMenu). In this case, I am updating the menu and removing the existing commands and replacing it with my dynamic menu.
If I use my own tool, I have a bit more control because of the OnMouseDown/Async methods and the ability to show the context menu when needed. However, I don't get the selection smarts built into the select tool.
Any ideas would be greatly appreciated.
Kris
Hi Kris,
Are you using FrameworkApplication.CreateContextMenu to create and display the Dynamic menu?
If possible, can you please post a code snippet?
Thanks!
Uma
Hi Uma,
No, I am not calling that directly as I am "hijacking" the ArcGIS Pro select tool.
Here is the DAML I am using:
Here is my definition of my dynamic menu. This would be under my insertmodule/controls section
<dynamicMenu id="SelectToolContextMenu" className="WaterGISPro.UI.Buttons.General.SelectToolContextMenu" disableIfBusy="true" caption="OpenFlows" />
Then later in the file, I have this:
<updateModule refID="esri_mapping">
<menus>
<updateMenu refID="esri_mapping_selection2DContextMenu" contextMenu="true">
<insertDynamicMenu refID="SelectToolContextMenu" inline="true" />
<deleteButton refID="esri_core_editCopyButton" />
<deleteButton refID="esri_core_editPasteButton" />
<deleteButton refID="esri_core_editDeleteButton" />
<deleteButton refID="esri_mapping_exploreContext" />
<deleteButton refID="esri_mapping_locateReverseGeocode" />
<deleteButton refID="esri_mapping_selectContext" />
<deleteButton refID="esri_editing_selectAllInLayer" />
<deleteButton refID="esri_mapping_zoomToSelectionButton" />
<deleteButton refID="esri_mapping_panToSelectionButton" />
<deleteButton refID="esri_mapping_clearSelectionButton" />
<deleteButton refID="esri_editing_EditVerticesMove" />
<deleteButton refID="esri_editing_EditVerticesRotate" />
<deleteButton refID="esri_editing_EditVerticesScale" />
<deleteButton refID="esri_editing_EditVerticesModifyFeature" />
<deleteButton refID="esri_editing_ShowAttributes" />
</updateMenu>
</menus>
</updateModule>
The above essentially removes all commands from the built-in context menu and replaces it with my own dynamic menu so I can control the contents. i would prefer a "<clearButtons />" as that would avoid having to keep this up-to-date (as my manager pointed out) but it is OK for now.
In my SelectToolContextMenu class, I am overriding OnPopup. I added the async keyword and this is the first code to execute:
protected override async void OnPopup()
{
var position1 = MouseCursorPosition.GetMouseCursorPosition();
MapPoint esriPoint = await QueuedTask.Run(() =>
{
Point client = MapView.Active.ScreenToClient(position1);
return MapView.Active.ClientToMap(client);
});
AddReference(DAML.Button.esri_mapping_exploreContext);
AddReference(DAML.Button.esri_mapping_selectContext);
}
However, it doesn't appear that await works here. When I try to use the right-click button with the select by rectangle tool active, I get a little black box approximately 3x3 or 4x4 pixels in size. It looks like control is returned but it doesn't load any of the context menu. As you can see, after the await call above, I cam adding some context menu items using AddReference. These are never executed which results in the described "black box".
Kris
Hi Kris
Since you are using Pro's Select by rectangle tool, you don't need the mouse click position. Pro's selection tool does that for you. So just remove those lines in your OnPopup override and it will work.
protected override void OnPopup()
{
//var position1 = MouseCursorPosition.GetMouseCursorPosition();
//Point client = MapView.Active.ScreenToClient(position1);
//MapPoint esriPoint = await QueuedTask.Run(() =>
//{
// return MapView.Active.ClientToMap(client);
//});
AddReference("esri_mapping_exploreContext");
AddReference("esri_mapping_selectContext");
}
Hi Uma,
OK but that doesn't solve the problem I am trying to address.
I might be using the builtin ArcGIS tool for selection but I am hijacking the context menu to provide custom commands (see the DAML that I included in my prior post). I am telling the DAML so it can instantiate and use my custom DynamicMenu subclass. In my subclass, in the OnPopup method, is where I populate the context menu.
In order to determine if one of those commands should be shown, I need to determine if the click location is within 25% of the coordinate (using Envelope.Expand I use 1.25 for the two parameters and false for the ratio). This would tell me if I should add it to the context menu. I believe Envelope.Expand also requires the MCT (if I recall correctly).
But in order to do that, I need to get the MapPoint - to do that I get the mouse position, convert to client coordinates and then convert to Map coordinates. The calls to ScreenToClient and ClientToMap are required to be on the MCT using QueuedTask. But I need that to be completed before continuing hence the need to use await and that requires adding async to the method definition.
Hopefully I've clarified what I am trying to do. if there is a way to subclass the ArcGIS selection tool, I would much rather do that. What I really need is its selection logic which is more than I can possibly implement myself (it's smart enough for a single click selection or a rectangle selection). Plus, the context menu works with any of the selection tools - by polygon, by rectangle, lasso, etc. That's nice, too.
If you have an alternative approach, I am all ears.
Kris
Hi Kris,
The normal pattern is for the tool to execute the “MapToClient, ClientToMap” logic in its mousedown or mouseup handler and not the context menu in OnPopup. However, if you are trying to force your code to execute synchronously in the OnPopup callback you can try accessing the Task Result property rather than using async/await. This is typically not recommended as it is a blocking call.
Var esriPoint = QueuedTask.Run(() => ……..).Result;
Hi Uma,
I 100% understand about the standard pattern. This is just one of those rare use cases I think.
And your idea worked. When i was trying this out originally I did try using Result but not quite in the way you described it.
I will make sure the team knows to use this approach as a very, very, very last resort.
Thank you for your quick responses. It is very much appreciated.
Kris
Hi Uma,
I discussed your idea with my manager. Although it does work in the case I tested, there is still the possibility of a lockup since it is a blocking call.
Other than writing our own map tool (subclassing MapTool), do you have any alternative ideas?
Is there any way to hook into the MouseDown or MouseUp event of the ArcGIS select tool?
Thanks again for your quick turnaround answers.
Kris
Hi Kris
I discussed this with the developer here in the Pro team to confirm. The only alternative is to write your own tool.
Thanks Uma. Appreciate your feedback and assistance.
I love working with the AGP SDK. It's an awesome API.
Kris