ProGuide Tray buttons - Esri/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Map Authoring
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          10/06/2024
ArcGIS Pro:    3.4
Visual Studio: 2022

Tray buttons can be found at the bottom of a map view or a layout view. There are a number of different types of tray buttons. For more information on Map Tray Buttons, see the Map Tray Buttons section in the Map Authoring ProConcepts. For Layout Tray Button information see the Layout Tray Button section in the Layouts ProConcepts.

This ProGuide demonstrates how to create each of the three Tray Buttons types.

  • Map Tray Button - will change the map extent to the visible extent of all layers in the map.
  • Layout Tray Toggle Button - toggles on and off layout guides that have been authored.
  • Map Tray Popup Toggle Button - toggles the editing mini toolbar visibility and re-creates on the popup, UI from the Editing backstage options controlling the mini toolbar.

Note: One consideration when creating map tray buttons at 3.0 is that the visible limit is 7 buttons. So any extra that are registered will not show up.

The code used to illustrate this add-in can be found at Tray Buttons.

Prerequisites

Create a new add-in, and name the project TrayButtons. 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.

Map Tray Button

Step 1. Create a new map tray button.
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tray Button and name the item ZoomToVisibleExtentTrayButton.cs. Review the code provided by the Visual Studio template.

In the Initialize method, the TrayButtonType is set. In this example we will keep the TrayButtonType as Button because this tray button only executes code when clicked and doesn't need to manage the IsChecked or Toggled state.

 protected override void Initialize()
 {
   base.Initialize();

   // set the button type
   //  change for different button types
   ButtonType = TrayButtonType.Button;

   // ClickCommand is used for TrayButtonType.Button only
   ClickCommand = new RelayCommand(DoClick);
 }

Step 2. Add the code that gets executed when the button is clicked.
Add the following method that will update the map extent to the extent of all visible layers when the button is clicked.

  public Task<bool> ZoomToVisibleLayersAsync()
  {
    //Get the active map view.
    if (this.MapView == null)
      return Task.FromResult(false);

    //Zoom to all visible layers in the map.
    var visibleLayers = this.MapView.Map.Layers.Where(l => l.IsVisible);
    return this.MapView.ZoomToAsync(visibleLayers);
  }

Call the ZoomToVisibleLayersAsync method from the DoClick() method, which is found by expanding the TrayButtonType.Button region

  private void DoClick()
  {
    // do something when the tray button is clicked
    ZoomToVisibleLayersAsync();
  }

Step 3. Modify the config file.
The default image assigned to a MapTrayButton is a generic color button. To change it, open the config.daml and from the DAML icon reference page find the zoom to extent icon. It's name is ZoomFullExtent16 or ZoomFullExtent32. These names will replace GenericButtonRed32 and GenericButtonRed16 in the config.daml entry. Additionally, in the same content line, is the tooltipHeading and tooltip. Change the heading to: “Zoom to visible layers extent”. Change the tooltip to: "This tray button will zoom to the extent of it's visible layers”.

 <content L_name="ZoomToVisibleExtentTrayButton" 
  largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/ZoomFullExtent32.png" 
  smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/ZoomFullExtent16.png" 
  L_tooltipHeading="Zoom to visible layers extent" 
  L_tooltip="This tray button will zoom the map to the extent of it's visible layers." />

Step 4. Build and test the Add-in.
Once the project builds successfully, open ArcGIS Pro and open a project with multiple layers in the map. Verify there is a new button with the Zoom to Full Extent icon in the map view tray region. The tray button is on the bottom left of the map, to the right of the scale control and other editing tray buttons. Change the extent of the map and click the new tray button to see it correctly update the extent.

Step 5. Optionally clean-up unused code.
Delete the regions containing code for the TrayButtonType.ToggleButton and TrayButtonType.PopupToggleButton and optionally delete the View and view model files that are unnecessary. Delete the entire tabs section so nothing is added to the addin tab.

        <!-- uncomment to have the control hosted on a separate tab-->
	<!--tabs>
        <<tab id="TrayButtons_Tab1" caption="New Tab">
          <group refID="TrayButtons_Group1"/>
        </tab>>
        </tabs-->
	<!--groups>
          <comment this out if you have no controls on the Addin tab to avoid
              an empty group>		 
        </groups-->
	<!--controls>
        </controls-->

Layout Tray Toggle Button

Step 1. Create a new layout tray toggle button.
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Layout Tray Button item. Change the name to LayoutGuideToggle.cs. This button will toggle the layout guides on and off. In the Initialize method change the button type to 'ToggleButton'. We also want to subscribe to the LayoutEvent to allow the button state to be updated when the guides are turned on or off by the user using either the Layout Contents context menu or the ruler context menu. When this changes, the tray button needs to respond by showing the correct state.

    protected override void Initialize()
    {
        base.Initialize();
        // set the button type
        // change for different button types
        ButtonType = TrayButtonType.ToggleButton;

        // subscribe to LayoutEvent for when the user
        // toggles the Guides via other parts of the UI
        // (for example the Layout context menu in the TOC)
        LayoutEvent.Subscribe(OnLayoutChanged);
    }

Step 2. Set the Initial state.

In the OnButtonLoaded method set the initial state of the button to match the existing state of the guides. Use the SetCheckedNoCallback to set the button IsChecked state without the OnButtonChecked callback being called.

  protected override void OnButtonLoaded()
  {
    base.OnButtonLoaded();

    // get the initial state
    var lyt = this.Layout;
    if (lyt != null)
    {
      QueuedTask.Run(() =>
      { 
        var lyt_cim = lyt.GetDefinition();
        this.SetCheckedNoCallback(lyt_cim.Page.ShowGuides); 
      });
    }
  }

Step 3. Add the code that gets executed when the button is clicked.
In the OnButtonChecked() method add the code to synchronize the state of the guides with the tray button checked property. Use the layout CIM definition. Note that updating the layout definition will cause a LayoutEvent with hint = LayoutEventHint.PageChanged to fire. This is the same event that fires when the guide state is updated via the UI. Add a boolean property in order to differentiate between when the button fires the LayoutEvent and when the event is fired as a result of UI interaction. The boolean property will be used to ensure the event handler code is not run when the LayoutEvent is fired programmatically (in step 4).

    
    private bool _ignoreEvent = false;
    
    /// <summary>
    /// Called when the toggle button check state changes. This will turn on and off 
    /// layout guides that have already been authored.
    /// </summary>
    protected override void OnButtonChecked()
    {
      // get the checked state
      var isChecked = this.IsChecked;

      // Turn on guides that are already setup in an existing layout
      var lyt = this.Layout;

      QueuedTask.Run(() =>
      {
         // updating the layout definition will cause a LayoutEvent with
         // hint = LayoutEventHint.PageChanged to fire. 
         // When we make the change programmatically we dont want to respond 
         // to the event.
         _ignoreEvent = true;      
         var lyt_cim = lyt.GetDefinition();
         lyt_cim.Page.ShowGuides = isChecked;

         lyt.SetDefinition(lyt_cim);
         _ignoreEvent = false;
     });
   }

Step 4. Add the OnLayoutChanged callback

Add the code which will respond to the LayoutEvent. This is the OnLayoutChanged method. Check the Hint to ensure it is the LayoutEventHint.PageChanged and use the SetCheckedNoCallback to update the button checked state with the ShowGuides property of the Layout page.

  private void OnLayoutChanged(LayoutEventArgs args)
  {
    var layout = args.Layout;
    if (args.Hint == LayoutEventHint.PageChanged)
    {
      // exit if we're ignoring the event
      if (_ignoreEvent)
        return;

      var oldpge = args.OldPage;
      QueuedTask.Run(() =>
      {
        var lyt_cim = layout.GetDefinition();

        // compare the ShowGuides property in the CIMPage
        if (lyt_cim.Page.ShowGuides != oldpge.ShowGuides)
          // if they are different, make sure the traybutton shows 
          // the correct checked state.
          // SetCheckedNoCallback ensures OnButtonChecked isn't fired.
          SetCheckedNoCallback(lyt_cim.Page.ShowGuides);
      });
    }
  }

Step 5. Modify the config file.
Change the icon for the layout tray button. Use the Rulers16 and Rulers32 images. Update the icon, tooltip heading and tooltip as below:

 <content L_name="LayoutGuideToggle" 
 largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/Rulers32.png" 
 smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/Rulers16.png" 
  L_tooltipHeading="Layout Guide Toggle" 
  L_tooltip="This tray button turns on guides that have been setup for the layout." />

Step 6. Optionally clean-up unused code.
To clean up the code, delete the region PopupToggleButton and optionally the TrayButtonType.Button region with associated ClickCommand line in Initialize all found in the LayoutGuideToggle.cs file. Optionally the LayoutGuideTogglePopupView.xaml and the LayoutGuideTogglePopupViewModel.cs can be deleted because they are not used.

Step 7. Build and test the Add-in.
Build the project and open ArcGIS Pro with a project that has a layout, or optionally create a new layout. Follow the help to add guides to the layout, the guides are created by right clicking on the rulers. Author them any way that allows you to tell when they are on and off. Use the tray button to toggle the state of the guides. Turn the guides on using the Pro UI (using either the Layout Contents context menu or the ruler context menu) and see the tray button toggle state update.

Close ArcGIS Pro and return to Visual Studio.

Map Tray Popup Button

The tray popup is a small popup window that opens when the mouse hovers over the tray button in the status bar. When clicked on, the tray button will still execute the code in OnButtonChecked(). The Tray Button SDK template includes a view and view model for developers to follow the recommended pattern for adding user interface controls to the tray popup window.
EditingToolbarOptions.png

Step 1. Create a new map tray button.
Add a new ArcGIS Pro Add-ins | ArcGIS Pro Map Tray Button and name the item MiniToolbarTrayButton.cs. Change the ButtonType to PopupToggleButton in the Initialize method.

  protected override void Initialize()
  {
    base.Initialize();
    // set the button type
    // change for different button types
    ButtonType = TrayButtonType.PopupToggleButton;
  }

Step 2. Set the initial state.
In the OnButtonLoaded set the initial state of the toggle button using the ApplicationOperations.EditingOptions.ShowEditingToolbar property. Use the SetCheckedNoCallback function to ensure that the OnButtonChecked callback doesn't fire.

 protected override void OnButtonLoaded()
 {
   base.OnButtonLoaded();
   // get the initial state
   this.SetCheckedNoCallback(ApplicationOptions.EditingOptions.ShowEditingToolbar);
 }

Step 3. Add the code that gets executed when the button is clicked.
Add the following code to the OnButtonChecked method. This code will update the Show Editing Toolbar option when the button is checked depending on its previous state. Additionally note that the popup viewmodel IsChecked property is updated to ensure it stays in synch with the tray button IsChecked property.

    protected override void OnButtonChecked()
    {
      // get the checked state
      var isChecked = this.IsChecked;

      //Use the Editing Options Show Editing Toolbar to control the mini toolbar
      var options = ApplicationOptions.EditingOptions;
      options.ShowEditingToolbar = isChecked;

      // refresh the popup VM checked state
      if ((_popupVM != null) && (_popupVM.IsChecked != this.IsChecked))
        _popupVM.IsChecked = this.IsChecked;
    }

Step 4. Update the EditingOptions when the popup checked state changes.

In the MiniToolbarTrayButton.cs file, the MiniToolbarTrayButtonPopupViewModel_PropertyChanged method exists to synchronize the tray button IsChecked state with the popup viewmodel IsChecked state when it changes. Add an additional line to the property changed handler to update the Show Editing Toolbar option when the IsChecked state changes.

  private void MiniToolbarTrayButtonPopupViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
  {
    if (_popupVM == null)
      return;
  
    // make sure MapTrayButton class has correct checked state when it changes on the VM
    if (e.PropertyName == nameof(MiniToolbarTrayButtonPopupViewModel.IsChecked))
    {
      // Since we are changing IsChecked in OnButtonChecked
      //We don't want property notification to trigger (another) callback to OnButtonChecked
      this.SetCheckedNoCallback(_popupVM.IsChecked);
      ApplicationOptions.EditingOptions.ShowEditingToolbar = _popupVM.IsChecked;
    }
  }

Step 5. Edit the view to create the user interface.

Open the file MiniToolbarTrayButtonPopupView.xaml to see the popup designer and template xaml code. Find the line <!--content--> and replace it with the code below. This is where the radio buttons to control the position and size of the mini toolbar will be defined. The new xaml creates a grid with 3 rows, the middle row is created for space. A second grid is added to row 1 of the first grid and will hold the radio buttons inside its rows. Also notice the IsChecked attribute of each control has a value with a name that describes what the control will do. We will use that name in the ViewModel to create a property to complete it's binding.

    <Grid Margin="20,20,20,20">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition Height="10" />
          <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
          <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="10" />
            <RowDefinition Height="Auto" />
          </Grid.RowDefinitions>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60" SharedSizeGroup="Position"/>
            <ColumnDefinition Width="60" SharedSizeGroup="Position"/>
            <ColumnDefinition Width="70" SharedSizeGroup="Position"/>
            <ColumnDefinition Width="Auto" SharedSizeGroup="Position"/>
          </Grid.ColumnDefinitions>
          <TextBlock Grid.Row="0" Grid.Column="0" 
                             Text="Position"/>
          <RadioButton Grid.Row="0" Grid.Column="1" GroupName="ToolbarPosition" 
                           IsChecked="{Binding IsToolbarLeft, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Left" />
          <RadioButton Grid.Row="0" Grid.Column="2" GroupName="ToolbarPosition" 
                           IsChecked="{Binding IsToolbarBottom, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Bottom"/>
          <RadioButton Grid.Row="0" Grid.Column="3" GroupName="ToolbarPosition" 
                           IsChecked="{Binding IsToolbarRight, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Right"/>
          <TextBlock Grid.Row="2" Grid.Column="0" 
                             Text="Size"  />
          <RadioButton Grid.Row="2" Grid.Column="1" GroupName="ToolbarSize"
                           IsChecked="{Binding IsToolbarSmall, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Small"/>
          <RadioButton Grid.Row="2" Grid.Column="2" GroupName="ToolbarSize" 
                           IsChecked="{Binding IsToolbarMedium, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Medium"/>
          <RadioButton Grid.Row="2" Grid.Column="3" GroupName="ToolbarSize" 
                           IsChecked="{Binding IsToolbarLarge, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
                           Content="Large"/>
        </Grid>
        <CheckBox Grid.Row="2" IsChecked="{Binding MagnificationOn}"
                          Content="Magnify Toolbar"/>
      </Grid>

Step 6. Edit the view model to create the xaml binding properties.

Open the MiniToolbarTrayButtonPopupViewModel.cs file to create the matching properties for each control to bind with. Each property in the view model will access the appropriate EditingOptions property. Add the following code below the IsChecked property in the view model file.

    public bool IsToolbarLeft
    {
      get => ApplicationOptions.EditingOptions.ToolbarPosition.Equals(ToolbarPosition.Left);
      set 
      {
        if (value != IsToolbarLeft)
          ApplicationOptions.EditingOptions.ToolbarPosition = ToolbarPosition.Left; 
      }
    }
    public bool IsToolbarBottom
    {
      get => ApplicationOptions.EditingOptions.ToolbarPosition.Equals(ToolbarPosition.Bottom);
      set
      {
        if (value != IsToolbarBottom)
          ApplicationOptions.EditingOptions.ToolbarPosition = ToolbarPosition.Bottom;
      }
    }
    public bool IsToolbarRight
    {
      get => ApplicationOptions.EditingOptions.ToolbarPosition.Equals(ToolbarPosition.Right);
      set
      {
        if (value != IsToolbarRight)
          ApplicationOptions.EditingOptions.ToolbarPosition = ToolbarPosition.Right;
      }
    }
    public bool IsToolbarSmall
    {
      get => ApplicationOptions.EditingOptions.ToolbarSize.Equals(ToolbarSize.Small);
      set
      {
        if (value != IsToolbarSmall)
          ApplicationOptions.EditingOptions.ToolbarSize = ToolbarSize.Small;
      }
    }
    public bool IsToolbarMedium
    {
      get => ApplicationOptions.EditingOptions.ToolbarSize.Equals(ToolbarSize.Medium);
      set
      {
        if (value != IsToolbarMedium)
          ApplicationOptions.EditingOptions.ToolbarSize = ToolbarSize.Medium;
      }
    }
    public bool IsToolbarLarge
    {
      get => ApplicationOptions.EditingOptions.ToolbarSize.Equals(ToolbarSize.Large);
      set
      {
        if (value != IsToolbarLarge)
          ApplicationOptions.EditingOptions.ToolbarSize = ToolbarSize.Large;
      }
    }

    public bool MagnificationOn
    {
      get => ApplicationOptions.EditingOptions.MagnifyToolbar;
      set 
      {
        if (value != MagnificationOn)
        {
          ApplicationOptions.EditingOptions.MagnifyToolbar = value;
        }
      }
    }

Step 7. Ensure the popup UI shows correct values.
All of the options on our popup UI can be altered by accessing the BackStage editing options. For this reason we need to ensure that our tray button UI shows the correct values when it is displayed. Update the OnShowPopup method in the MiniToolbarTrayButton.cs file to include the _popupVM.ShowCorrectStates method.

    protected override void OnShowPopup()
    {
      base.OnShowPopup();
      // track property changes
      if (!_subscribed)
      {
        _popupVM.PropertyChanged += MapTrayButton1PopupViewModel_PropertyChanged;
        _subscribed = true;
      }
      _popupVM.ShowCorrectStates();
    }

Add the ShowCorrectStates function to the MiniToolbarTrayButtonPopupViewModel.cs file. This forces each of the bindable properties to be refreshed so the values are displayed correctly in the UI.

    internal void ShowCorrectStates()
    {
      NotifyPropertyChanged(nameof(IsToolbarLeft));
      NotifyPropertyChanged(nameof(IsToolbarBottom));
      NotifyPropertyChanged(nameof(IsToolbarRight));
      NotifyPropertyChanged(nameof(IsToolbarSmall));
      NotifyPropertyChanged(nameof(IsToolbarMedium));
      NotifyPropertyChanged(nameof(IsToolbarLarge));
      NotifyPropertyChanged(nameof(MagnificationOn));
    }

Step 8. Modify the config file.
If you have not done so already delete the entire tabs section so nothing is added to the addin tab.

        <!-- uncomment to have the control hosted on a separate tab-->
	<!--tabs>
        <<tab id="TrayButtons_Tab1" caption="New Tab">
          <group refID="TrayButtons_Group1"/>
        </tab>>
        </tabs-->
	<!--groups>
          <comment this out if you have no controls on the Addin tab to avoid
              an empty group>		 
        </groups-->
	<!--controls>
        </controls-->

Add a tooltip and associated header for the mini toolbar tray button. Optionally change the large and small image to an icon that helps identify this tray popup button.

 <content L_name="MiniToolbarTrayButton" 
  largeImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed32.png" 
  smallImage="pack://application:,,,/ArcGIS.Desktop.Resources;component/Images/GenericButtonRed16.png" 
  L_tooltipHeading="Mini Toolbar Tray Popup Button" 
  L_tooltip="This tray button will toggle the Editing Mini Toolbar and allow you to control its properties"/>

Step 9. Build and test the Add-in.

Once the project builds successfully, open ArcGIS Pro and open a project with editable layers in the map. Verify there is a new button to the right of the zoom to full extent icon in the map view tray bar or if you didn't create the zoom to visible extent tray button it should be right next to the Corrections tray button.
*As noted above only 7 tray buttons will show up. So if it isn't visible make sure you have not added more tray buttons than the limit.

Toggling the tray button main toggle will make the editing mini toolbar no longer visible. Toggling it back on will make the toolbar visible again. Hovering over the tray button icon will open the popup. It should look like this:

MiniToolbarPopup.png

With the toolbar toggled back on test out the checkboxes that control the position and size of the toolbar to verify it works.

Controlling AutoClose Behavior

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