ProGuide Map Pane Impersonation - kataya/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Map Authoring
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

Use the "Impersonation" pattern when you need to show a custom pane that interacts with the Table of Contents associated with the current map

Overview

In Pro, a pane or "View" can be paired with a default tool, a default tab, and a content control which is hosted in the contents pane (a.k.a. the "Table of Contents" or "TOC"). The context of the application (contents, visible tabs, default tool, etc.) always reflects the context associated with the active pane. Therefore, when the active pane is switched in Pro, if it is a different "type" (map, scene, layout, or whichever) the Pro UI context likewise changes. For example, moving from a map pane to the catalog pane or to a layout pane will change the context from map to catalog or layout meaning the contents control changes, the default tool changes, and the visible tabs (and active tab) will change as well.

In some cases, incoming panes may not provide context or only partial context and, in those cases, those aspects of application context not provided will not be set. For example, if an incoming pane does not provide a contents control for the TOC then the contents will not be set (and the TOC appears to "blank out"). Also, tabs associated with just one particular view type may be hidden and the active tool deactivated.

In the screenshot below, on the left, the Pro UI context has been set relative to a map pane being active. On the right, the custom pane "Pane1" has been activated. Because Pane1 has no associated content control the TOC has gone blank. The map, insert, analysis, view, edit, and imagery tabs have all been hidden because, contextually, they depended on a map (being active).

Blank contents

Impersonation

In certain cases, pane providers want to retain the context of the active map or scene whenever their pane is activated. This is accomplished through a pattern called "Impersonation". In impersonation, the pane provider impersonates a specific map or scene view to retain its context.

For example, when the Pro attribute table pane is activated off a layer context menu, notice that the context of the associated active map view does not change:

Impersonation

The currently active tab, tool, and TOC content have not changed because the attribute table pane is impersonating the active map.

The remainder of this ProGuide steps you through the process of implementing a custom pane that will impersonate an active map or scene view. When your custom pane is opened, the context of the currently active map or scene will be retained. The code for this ProGuide can be found at Impersonate Map Pane Sample.

Prerequisites

Step 1

Create a new ArcGIS Pro Module Add-in, and name the project "ImpersonateMapPane". 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 Map Pane Impersonation item to the add-in. Select the ArcGIS Pro Map Pane Impersonation from the ArcGIS templates.

ProGuide: MapAuthoring - Map Pane Impersonation

Just keep the defaults and click Add. Examine the changes made to the config.daml file after adding the new pane.

<controls>
  <!-- add your controls here -->
  <button id="ImpersonateMapPane_ImpersonateMapPane1_OpenButton" caption="Open ImpersonateMapPane 1" 
           className="ImpersonateMapPane1_OpenButton" loadOnClick="true" 
           smallImage="Images\GenericButtonGreen16.png" largeImage="Images\GenericButtonGreen32.png" 
           condition="esri_mapping_mapPane">
    <tooltip heading="Open Pane">Open Pane<disabledText /></tooltip>
  </button>
</controls>

<panes>
  <pane id="ImpersonateMapPane_ImpersonateMapPane1" caption="ImpersonateMapPane 1" 
           className="ImpersonateMapPane1ViewModel" 
           smallImage="Images\GenericButtonGreen16.png" 
           defaultTab="esri_mapping_homeTab" defaultTool="esri_mapping_navigateTool">
    <content className="ImpersonateMapPane1View" />
  </pane>
</panes>

The daml shows we have a new button and a new pane. The pane follows the standard View-ViewModel pattern used in ArcGIS Pro with the ViewModel defined in the <pane className="..." attribute and the view defined in the child <content className="..." (same as with dock panes, property pages, etc).

Step 2

Examine the boilerplate code generated in ImpersonateMapPane1ViewModel.cs. First, examine the class declaration:


internal class ImpersonateMapPane1ViewModel : TOCMapPaneProviderPane {

To make a pane that supports impersonation, it must derive from TOCMapPaneProviderPane. TOCMapPaneProviderPane provides the majority of the logic that is required for impersonation.https://pro.arcgis.com/en/pro-app/latest/sdk/api-reference/#topic16596.html.

In the ImpersonateMapPane1ViewModel.cs class file, scroll down to the static Create method. Create was generated by the template to create the impersonation pane. It takes, as its argument, the map view that it will impersonate when it is opened.

internal static ImpersonateMapPane1ViewModel Create(MapView mapView)
{
  var view = new CIMGenericView();
  view.ViewType = _viewPaneID;
  view.ViewProperties = new Dictionary<string, object>();
  view.ViewProperties["MAPURI"] = mapView.Map.URI;

  var newPane = FrameworkApplication.Panes.Create(_viewPaneID, new object[] { view }) as ImpersonateMapPane1ViewModel;
  newPane.Caption = $"Impersonate {mapView.Map.Name}";
  return newPane;
}

The Create method instantiates a CIMGenericView which is used to capture the id or "Map.URI" of the map view to be impersonated. FrameworkApplication.Panes.Create() is used to create the pane and will invokes your public ImpersonateMapPane1ViewModel constructor:

public ImpersonateMapPane1ViewModel(CIMView view)
      : base(view) {
      //Set to true to change docking behavior
      _dockUnderMapView = false;
    }

Note: Change the value of _dockUnderMapView to true if you want your pane to act like the attribute table pane and dock underneath its "impersonated" map view.

The template also generates boilerplate code for Task InitializeAsync(). Within InitializeAsync(), the Map.URI of the active map view (originally passed in as the argument to static ImpersonateMapPane1ViewModel Create(MapView mapView) and stored in the CIMGenericView) is extracted and passed to the base class via Task SetMapURI(string mapURI).

Within SetMapURI(), the base class code provides the relevant logic to find the active map view and initiate the impersonation. If you choose to modify the generated InitializeAsync() implementation, you must call SetMapURI and the base.InitializeAsync() or your pane will not impersonate.

 protected async override Task InitializeAsync()
    {
     //This is the Map.URI from active view passed to the static Create method
      var uri = ((CIMGenericView)_cimView).ViewProperties["MAPURI"] as string;
      await this.SetMapURI(uri);//<-- the impersonation is established here
      await base.InitializeAsync();
    }

Step 3

Now we will modify the out of the box pane view model. Copy and paste the following method into your ImpersonateMapPane1ViewModel class definition:

private static string FormatXml(string xml)
    {
      var doc = new XmlDocument();
      var sb = new StringBuilder();
      doc.LoadXml(xml);
      var xmlWriterSettings = new XmlWriterSettings { Indent = true, OmitXmlDeclaration = true };
      doc.Save(XmlWriter.Create(sb, xmlWriterSettings));
      return sb.ToString();
    }

Add a using System.Xml; to the top of the ImpersonateMapPane1ViewModel.cs class file to resolve the references to XmlDocument, XmlWriterSettings, and XmlWriter.

Add the following private field "_viewDefinition" to your ImpersonateMapPane1ViewModel:

internal class ImpersonateMapPane1ViewModel : TOCMapPaneProviderPane
  {
    private const string _viewPaneID = "ImpersonateMapPane_ImpersonateMapPane1";
    private string _viewDefinition = "";

Add the following line _viewDefinition = FormatXml(view.ToXml()); to your ImpersonateMapPane1ViewModel constructor (we are retrieving the xml definition of our own CIM View to use as content for our pane):

public ImpersonateMapPane1ViewModel(CIMView view)
      : base(view) {
      //Set to true to change docking behavior
      _dockUnderMapView = false;
      _viewDefinition = FormatXml(view.ToXml());
    }

Add a custom property ViewXML to your ImpersonateMapPane1ViewModel class:

public string ViewXML
    {
      get
      {
        return _viewDefinition;
      }
    }

Step 4

Next, we are going to customize the ImpersonateMapPane1 view. Open the ImpersonateMapPane1.xaml.

Replace the existing content of the pane with the XAML below:

<UserControl x:Class="ImpersonateMapPane.ImpersonateMapPane1View" ....>

<Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"></RowDefinition>
      <RowDefinition Height="*"></RowDefinition>
    </Grid.RowDefinitions>
    <StackPanel Grid.Row="0"  Orientation="Horizontal" HorizontalAlignment="Center">
      <TextBlock Text="Impersonating:" Style="{DynamicResource Esri_TextBlockRegular}" FontSize="20" Margin="0,0,10,0"></TextBlock>
      <TextBlock Text="{Binding MapURI, Mode=OneWay}" Style="{DynamicResource Esri_TextBlockRegular}" FontSize="20"></TextBlock>
    </StackPanel>
    <Border Grid.Row="1" BorderThickness="2" BorderBrush="{DynamicResource Esri_BorderBrush}" Margin="10">
      <TextBox Text="{Binding ViewXML, Mode=OneWay}" TextWrapping="Wrap" ScrollViewer.VerticalScrollBarVisibility="Auto"
               IsReadOnly="True"/>
    </Border>
  </Grid>

We are adding a caption at the top of the pane to display the Map Uri of the map we will impersonate and a textbox that will display the content of our underlying CIM View retrieved via a binding to the ViewXML property we added in Step 2.

Step 5

Compile the project. Fix any compilation errors (if you do have errors check you did not miss any { or } or similar when copy-pasting the code).

Run the add-in (via "Start" on the Visual Studio toolbar).

Step 6

When ArcGIS Pro opens, navigate to the location of your CommunitySampleData download folder. Within the CommunitySampleData folder, open the "Interacting with Maps\Interacting with Maps.aprx" project file. Open the map if a map view didn't open. Observe its context (table of contents, active tab, active tool, etc.).

Click on the Add-in tab. Click on the "Open ImpersonateMapPane 1 button"

Open ImpersonateMapPane 1 button

The button creates an instance of the ImpersonateMapPane1ViewModel and activates it.

ImpersonateMapPane1ViewModel is active

Notice that your custom ImpersonateMapPane1ViewModel pane has been activated. Notice that because it is impersonating the previously active map, the application UI context is unchanged (from that of the map).

Add other maps or scenes to the project and set different of them active. Click on the add-in button to create more impersonation pane instances. Whichever map or scene is active when you create a custom pane will be the map or scene it impersonates.

Step 7

Close the map or scene your pane is impersonating. Notice that your impersonation pane closes automatically as well. This is the most common use case. That is, if the "impersonat-ed" pane on which your context depends is closed, then you (the "impersonat-ing" pane) also close. The assumption being you are impersonating a given pane probably because you need to retain its contents.

If, for some reason, you do not want your custom pane to close when the impersonated map pane is closed, you can override the OnNoActiveMapView() method. Try adding this code into your ImpersonateMapPane1ViewModel class.

protected override void OnNoActiveMapView()
    {
    }

The base class method (you are overriding) handles the closing of the impersonating pane. With this empty declaration we simply bypass that closing logic.

Run your impersonation pane. Then, close the map view you are impersonating. Now, your pane remains open however, the associated map view contents still closed (within the TOC). The key point to remember is that the contents being displayed in the Contents pane is owned by the map view and not your pane. When the impersonated pane is closed, so is its associated contents.

Impersonation pane with mapview closed

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