ProGuide Custom settings - Esri/arcgis-pro-sdk GitHub Wiki

Language:      C#
Subject:       Framework
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 will discuss defining custom Project and Application level settings using the Pro SDK. Project and Application settings can be accessed off the Options property page dialog, via Options on the Pro backstage.

In this Topic

Custom project settings

Custom project settings are saved within a Pro project. The Pro SDK provides two overrides off the Module class to allow you to read and write custom settings to/from the project respectively:

OnReadSettingsAsync is called on your Module by the Framework whenever a project is opened. Use the passed in ModuleSettingsReader to access your settings, stored as key/value pairs. OnWriteSettingsAsync is called each time the project is saved. Use the passed in ModuleSettingsWriter to persist your values as strings.

internal class Module1 : Module {

   protected override Task OnReadSettingsAsync(ModuleSettingsReader settings) {
      //the project is being opened
      object value = settings.Get("MyModule.Setting1");
      if (value != null) {
         //Do something with the read property
      ...
   }

   protected override Task OnWriteSettingsAsync(ModuleSettingsWriter settings) {
       //the project is being saved
       settings.Add("MyModule.Setting1", _customValue);
       ...
   }

In the first part of this guide, we will add a property page to the Pro Options, Project settings "property sheet". On our property page we will provide two custom settings:

  1. A true/false flag value which can be toggled via a checkbox
  2. A custom string which can be edited via a textbox.

These settings will be saved with each project and restored whenever a project that contains them is opened.

ProjectSettings

Step 1: Creating the Module add-in project:

Create an ArcGIS Pro Add-in module project in Visual Studio. Call it "CustomSettings". 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.

Step 2: Creating the project settings "View" and "ViewModel"

  • Add an ArcGIS Pro Property sheet item using the Pro SDK item templates to your add-in project. Name the sheet ProjectSettings.xaml. Its View Model, ProjectSettingsViewModel.cs, will be automatically created by the template.

Note that the ProjectSettingsViewModel class derives from ArcGIS.Desktop.Framework.Contracts.Page base class.

Step 3: Modify the ProjectSettings Property Sheet

Out of the box, the template defines a button to show the custom dialog. Because we will be using the existing Pro project options property sheet category we can delete the button and related code behind.

  • In the config.daml, remove the button element and group added within the "controls" group. This button is not needed to display the the Property sheet.
 <modules>
    <insertModule id="CustomSettings_Module" className="Module1" autoLoad="false" caption="Module1">
       <-- DELETE this group -->
        <group id="CustomSettings_Group1" caption="Group 1" appearsOnAddInTab="true">
          <!-- host controls within groups -->
          <button refID="CustomSettings_ProjectSettings_ShowPropertySheet" size="large" />
        </group>
      </groups>
      <controls>
        <!-- DELETE this button -->
        <button id="CustomSettings_ProjectSettings_ShowPropertySheet" caption="Show ProjectSettings" .../>
      </controls>
    </insertModule>
  • In the ProjectSettingsViewModel.cs, remove the ProjectSettings_ShowButton class.
  • In the ProjectSettingsViewModel.cs, remove the DataUIContent public string property. It is not needed.
internal class ProjectSettingsViewModel : Page {
  ...
  //Delete!
  public string DataUIContent {
     ...
  }

//Delete!
internal class ProjectSettings_ShowButton : Button {
 ...
}

Step 4: Modify the ProjectSettings Property Sheet

Design the new project settings property page View and set up its ViewModel properties.

Within ProjectSettings.xaml:

  • Delete the existing text block
  • Add a check box and text box WPF controls. Bind their IsChecked and Text properties, respectively, to a ModuleSetting1 and ModuleSetting2 property as shown below.

Note the Esri styles used to style the controls.

WPF Control Binding
Check box IsChecked="{Binding ModuleSetting1}"
Text box Text="{Binding ModuleSetting2, UpdateSourceTrigger=PropertyChanged}

ProjectSettings.xaml

…
<Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="Auto"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <CheckBox Margin="16,8,0,0" Grid.Row="0" Content="Module Setting 1" IsChecked="{Binding ModuleSetting1}"/>
    <StackPanel Grid.Row="1" Margin="16,8,0,0" Orientation="Horizontal">
      <TextBlock Text="Some string" VerticalAlignment="Center" Style="{DynamicResource Esri_TextBlockDialog}"/>
      <TextBox Margin="6,0,0,0" Text="{Binding ModuleSetting2, UpdateSourceTrigger=PropertyChanged}" Width="200"
               VerticalAlignment="Center"/>
    </StackPanel>
</Grid>
  • In your ProjectSettingsViewModel.cs create a bool public property ModuleSetting1 and a string public property ModuleSetting2. These are the properties you bound your view to in the XAML.

ProjectSettingsViewModel.cs:

    private bool _moduleSetting1;
    public bool ModuleSetting1 {
      get { return _moduleSetting1; }
      set {
        if (SetProperty(ref _moduleSetting1, value, () => ModuleSetting1))
          //You must set "IsModified = true" to have your CommitAsync called
          base.IsModified = true;
      }
    }

    private string _moduleSetting2;
    public string ModuleSetting2 {
      get { return _moduleSetting2; }
      set {
        if (SetProperty(ref _moduleSetting2, value, () => ModuleSetting2))
          //You must set "IsModified = true" to have your CommitAsync called
          base.IsModified = true;
      }
    }

Notice that, if any of our properties are changed, we set our IsModified flag to be true. This signals to the containing property page that our content has been changed. Refer to topic10573.html.

Step 5: Add the Property Page to the Project Options in the Config.daml

The Pro SDK Property Sheet template adds new property pages, by default, to a custom property sheet category in your Config.daml. We need to change the property sheet category for our page from the custom one to the Pro Options category. Additionally, we need to make sure our page will show up in the "Project" section on the Pro Options property sheet dialog.

  • In the DAML, find the <propertySheets> collection (it should be at the bottom of the DAML file). Change the insertSheet element to be an updateSheet element: i.e. Change this <insertSheet ...></insertSheet> to this <updateSheet ...></updateSheet>.
  • Change the page element to be an insertPage element: i.e. Change this <page ...></page> to this <insertPage ...></insertPage>.
  • Delete all the attributes on your newly named "updateSheet" element (id, caption, hasGroups).
  • Add a refID="esri_core_optionsPropertySheet" attribute. This is referencing the Pro options cateogry (which we are updating).
  • Change the group="Group 1" attribute on the <insertPage ...> element to group="Project". This will add our page to the Project group on the Pro options dialog.

Your completed DAML should look like this:

 ...
  <propertySheets>
    <updateSheet refID="esri_core_optionsPropertySheet">
      <insertPage id="esri_sdk_PropertyPageProjectSettings" caption="Sample Project Settings" className="ProjectSettingsViewModel" group="Project">
        <content className="ProjectSettingsView" />
      </insertPage>
    </updateSheet>
  </propertySheets>

The caption of our page is "Sample Project Settings". This is the caption that will appear in the list of Project property pages.

ProjectSettings2

Note: Setting the group attribute to “Application” would add our property page to the Application section of the Options dialog. We will see that in the second half of the guide.

Step 6: Implement the Module class’ Overrides to Read and Write The New Project Settings.

In the Module1.cs file, create a Dictionary to store your custom project settings. You will use the the properties in ProjectSettingsViewModel to populate this dictionary with the user choices.

Module1.cs

        private Dictionary<string, string> _moduleSettings = new Dictionary<string, string>();
        internal Dictionary<string, string> Settings {
            get { return _moduleSettings; }
            set { _moduleSettings = value; }
        }
  • Override the OnReadSettingsAsync method. Framework will invoke this method on our Module when the project is opened or at any time when Pro reads the project settings.
        private bool hasSettings = false;
        protected override Task OnReadSettingsAsync(ModuleSettingsReader settings) {
          // set the flag
          hasSettings = true;
          // clear existing setting values
          _moduleSettings.Clear();

          if (settings == null) return Task.FromResult(0);

	  // Settings defined in the Property sheet’s viewmodel.	
          string[] keys = new string[] {"Setting1", "Setting2"};
          foreach (string key in keys) {
            object value = settings.Get(key);
            if (value != null) {
              if (_moduleSettings.ContainsKey(key))
                _moduleSettings[key] = value.ToString();
              else
                _moduleSettings.Add(key, value.ToString());
            }
          }
          return Task.FromResult(0);
        }
  • Override the OnWriteSettingsAsync method. Framework calls this method on our Module any time project settings are to be saved.
        protected override Task OnWriteSettingsAsync(ModuleSettingsWriter settings){
          foreach (string key in _moduleSettings.Keys) {
            settings.Add(key, _moduleSettings[key]);
          }
          return Task.FromResult(0);
        } 

Step 7: Transfer Stored project Settings To and From the ProjectSettingsViewModel and Module.

In the ProjectSettingsViewModel.cs initialize private properties to store the “original values” of the project settings. Use the InitializeAsync override on the Page.

    private bool _origModuleSetting1 = true;
    private string _origModuleSetting2 = "";

    protected override Task InitializeAsync() {
      // get the settings
      Dictionary<string, string> settings = Module1.Current.Settings;

      // assign to the values biniding to the controls
      if (settings.ContainsKey("Setting1"))
        ModuleSetting1 = System.Convert.ToBoolean(settings["Setting1"]);
      else
        ModuleSetting1 = true;

      if (settings.ContainsKey("Setting2"))
        ModuleSetting2 = settings["Setting2"];
      else
        ModuleSetting2 = "";

     // keep track of the original values (used for comparison when saving)
     _origModuleSetting1 = ModuleSetting1;
     _origModuleSetting2  = ModuleSetting2;

      return Task.FromResult(0);
    }
  • In the Page class, whenever "Ok" is pressed on the parent property sheet (in our case, the Pro options dialog), its CommitAsync method is called. In our CommitAsync we need to transfer the project settings to our Module and set the current project’s state to “Dirty” to force the application to ask to "save changes" when the project is closed.
  // Determines if the current settings are different from the original.
    private bool IsDirty() {
      if (_origModuleSetting1 != ModuleSetting1)
        return true;
      if (_origModuleSetting2 != ModuleSetting2)
        return true;
      return false;
    }

  protected override Task CommitAsync() {
      if (IsDirty()) {
        // store the new settings in the dictionary ... save happens in OnProjectSave
        Dictionary<string, string> settings = Module1.Current.Settings;

        if (settings.ContainsKey("Setting1"))
          settings["Setting1"] = ModuleSetting1.ToString();
        else
          settings.Add("Setting1", ModuleSetting1.ToString());

        if (settings.ContainsKey("Setting2"))
          settings["Setting2"] = ModuleSetting2;
        else
          settings.Add("Setting2", ModuleSetting2);

        // set the project dirty
        Project.Current.SetDirty(true);
      }
      return Task.FromResult(0);
    }

Step 7: Auto Load The Add-in

We want our Add-in to load when ArcGIS Pro initializes. This way, it will be initialized when the first project is opened. In the Config.daml file, set the autoload attribute of the <insertModule ...> element to true.

…
<modules>
    <insertModule id="CustomSettings_Module" className="Module1" autoLoad="true" caption="Module1">
…

Step 8: Build The Add-in and Test the Settings

Build the add-in project and launch the debugger. ArcGIS Pro will launch. Open any project and test the new Project settings. Go to the backstage. Click on the Options button. You should see your custom property page in the list of Project pages.

  • Click on the "Sample Project Settings" entry in the Project options. Change the values on the page
  • Click ok. Reopen the page. They should be persisted.
  • Close the project. You should be prompted to save.

If you save the project, check that when you re-open it, your persisted values are shown in the "Sample Project Settings" page.

Extra Considerations

To reset your custom settings when a project without custom settings is opened, either set them back (to defaults) on the ProjectClosedEvent or when it opens, on the ProjectOpenedEvent. OnReadSettingsAsync is only called if a project contains custom settings for your Module. It is called before ProjectOpenedEvent.

We will use ProjectClosedEvent (topic9265.html) to reset our hasSettings flag and ProjectOpenedEvent (topic9319.html) to set the defaults if OnReadSettingsAsync was not called.

Add the following code to your Module1.cs class:

  //Add this using to the top of the source file
  using ArcGIS.Desktop.Core.Events;
  ...


  private Module1() {
      ProjectOpenedEvent.Subscribe(OnProjectOpen);
      ProjectClosedEvent.Subscribe(OnProjectClosed);
  }

   private void OnProjectClosed(ProjectEventArgs args) {
       // reset the flag
       hasSettings = false;
   }

   private void OnProjectOpen(ProjectEventArgs args) {
       // if flag has not been set then we didn't enter OnReadSettingsAsync
       if (!hasSettings)
          _moduleSettings.Clear();
       }
   }
   

Custom application settings

ArcGIS Pro Application level custom settings can be added to the existing Pro application settings via a .NET application settings file. Settings are modified via a .NET Properties.Settings class that we will define with our own custom properties and values. Settings are persisted to a user.config file in the logged-on user’s AppData folder and will be read at add-in initialization when the Settings class instance (in your add-in) is instantiated. Settings are saved when you explicitly call “save” on your Settings class instance (e.g. in response to a user changing settings on your custom application property sheet).

Note: The steps below assume you have previously added the custom Project settings page to the Project options as described in the first part of this Guide, or have familiarized yourself with those steps.

Step 1: Creating the application settings "View" and "ViewModel"

Repeat the same procedure as before with custom Project settings. Add a new property page. Call it ApplicationSettings.xaml. Remove the default button and button code behind from the ApplicationSettingsViewModel.cs view model.

Step 2: Design the new application settings property page View and set up the ViewModel properties

  • Add two check boxes to the ApplicationSettings.xaml and bind their IsChecked properties to GeneralSetting and OtherSetting properties. Note: You are also adding two expanders bound to IsGeneralExpanded and IsOtherExpanded properties respectively.
<Grid Margin="10,10,10,10" Grid.IsSharedSizeScope="True">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto"/>
                <RowDefinition Height="Auto"/>
            </Grid.RowDefinitions>
            <Expander Grid.Row="0" Header="General" Margin="0" IsExpanded="{Binding IsGeneralExpanded, FallbackValue=True}">
                <CheckBox Margin="16,8,0,0" Grid.Row="0" Content="Add a setting here" IsChecked="{Binding GeneralSetting}"/>

            </Expander>
            <Expander Grid.Row="1" Header="Other" Margin="0,15,0,0" IsExpanded="{Binding IsOtherExpanded, FallbackValue=True}">
                <CheckBox Margin="16,8,0,0" Grid.Row="0" Content="Add another setting here" IsChecked="{Binding OtherSetting}"/>
            </Expander>
        </Grid>

In the ApplicationSettingsViewModel.cs create four bool public properties:

  1. GeneralSetting and OtherSetting to hold the custom application settings
  2. IsGeneralExpanded and IsOtherExpanded to control the expander state on the UI.

Two private bool properties _origGeneralSetting and _origOtherSetting are created to store the previous values for the application settings.

ApplicationSettingsViewModel.cs:

       private bool _origGeneralSetting;
       private bool _origOtherSetting;

        private bool _generalSetting;
        public bool GeneralSetting {
            get { return _generalSetting; }
            set {
                if (SetProperty(ref _generalSetting, value, () => GeneralSetting))
                    base.IsModified = true;
            }
        }

        private bool _otherSetting;
        public bool OtherSetting {
            get { return _otherSetting; }
            set {
                if (SetProperty(ref _otherSetting, value, () => OtherSetting))
                    base.IsModified = true;
            }
        }

    private static bool _isGeneralExpanded = true;
    public bool IsGeneralExpanded {
      get { return _isGeneralExpanded; }
      set { SetProperty(ref _isGeneralExpanded, value, () => IsGeneralExpanded); }
    }

    private static bool _isOtherExpanded = true;
    public bool IsOtherExpanded {
      get { return _isOtherExpanded; }
      set { SetProperty(ref _isOtherExpanded, value, () => IsOtherExpanded); }
    }

Step 3: Define and declare the new property sheet in the add-in’s Config.daml

We want the new property page to appear in Pro’s Options dialog under the “Application” section. Repeat the process you followed in Step 5: Add the Property Page to the Project Options in the Config.daml above.

Set the group attribute of the page element to group="Application"

…
  <propertySheets>
    <updateSheet refID="esri_core_optionsPropertySheet">
      <insertPage id="esri_sdk_PropertyPageAppSettings" caption="Sample App Settings" className="ApplicationSettingsViewModel" group="Application">
        <content className="ApplicationSettingsView" />
      </insertPage>
</updateSheet>
  </propertySheets>

ApplicationSettings

(You may have to scroll the Application options to see your custom entry)

Step 4: Add a Settings.settings file (or "Settings1.settings" which is the default) to define the user preferences to ArcGIS Pro's user.config file

The settings defined in the the ApplicationSettings and ApplicationSettingsViewModel will be configured in the add-in project's Settings1.settings file. The .NET Framework allows you to store Application settings dynamically via a user.config file in the current user's "AppData" location (Managing Application Settings (.NET))

Any Application settings user.config file stored in the AppData location for ArcGISPro.exe will be read, at startup, by Application .NET property settings classes. For Pro, this location would be similar to C:\Users\[UserName]\AppData\Roaming\ESRI\ArcGISPro_StrongName_yhpsrysqpn4fvmb0spwbakt5o5e50din\[Version]. Your add-in's Settings.Settings class (or Settings1.Settings if you used the default name) will be initialized when the Add-in is initialized.

  • Add a new item to the project of type "Settings".
  • Enter the new custom application settings defined in the ApplicationSettings and ApplicationSettingViewModel into the Settings files as follows:

Settings

  • ArcGIS Pro stores all settings in the user.config file located in the "Roaming" folder in AppData (In earlier versions - pre 2.6 - the user.config file was located in the "Local" folder in the AppData). To use the Roaming folder to store your custom application settings, make sure the "Roaming" attribute of each of your settings properties is set to true (the "Scope" remains as 'User'): Roaming

Visual studio will automatically generate a Settings class for you, with those properties, that derives from System.Configuration.ApplicationSettingsBase.

Step 5: Implement the application settings property page overrides

The ApplicationSettingsViewModel derives from ArcGIS.Desktop.Framework.Page. This class exposes overrides that can be called when the Property Sheet is Initialized, Committed and Cancelled.

  • In the ApplicationSettingsViewModel .cs access the Settings class instance and populate the application settings using the Initialize override.
        #region Page Overrides

        protected override Task InitializeAsync() {
            // get the default settings
            var settings = CustomSettings.Properties.Settings.Default;
            //or var settings = ProAppModule1.Settings1.Default; 
            //or var settings = ..... MySettingsClass.Default;
            //etc.
            //- depends on the name of your addin default namespace and settings class

            // assign to the values binding to the controls
            _generalSetting = settings.GeneralSetting;
            _otherSetting = settings.OtherSetting;

            // keep track of the original values (used for comparison when saving)
            _origGeneralSetting = GeneralSetting;
            _origOtherSetting = OtherSetting;

            return Task.FromResult(0);
        }
  • Commit the custom applications settings back to your custom .NET Settings class. It will persist them to your user.config file automatically. We only commit the settings if the settings have changed using a custom IsDirty method.
  private bool IsDirty() {
            if (_origGeneralSetting != GeneralSetting) {
                return true;
            }
            if (_origOtherSetting != OtherSetting) {
                return true;
            }
            return false;
        }

        protected override Task CommitAsync() {
            if (IsDirty()) {
                // save the new settings
                var settings = CustomSettings.Properties.Settings.Default;
               //or var settings = ProAppModule1.Settings1.Default; 
              //or var settings = ..... MySettingsClass.Default;
              //etc.
              //- depends on the name of your addin default namespace and settings class

                settings.GeneralSetting = GeneralSetting;
                settings.OtherSetting = OtherSetting;

                settings.Save();
            }
            return Task.FromResult(0);
        }

Notice that we save the changes immediately whenever the application settings are changed. Our changes are not tied to the Project "save" nor to Project events.

Step 6: Build the add-in and test the settings

Build the add-in project and launch the debugger. ArcGIS Pro will launch. Open any project and test the new Application settings page. Change your custom application settings and click "Ok" on the Pro Options dialog.

When you stop and restart the application, your "Sample App Settings" page should reflect your last saved application values.

Step 7: Optional: Persist Application settings across multiple Pro versions

Custom application settings can be persisted across multiple versions of Pro. These are the steps to persist the custom application settings:

  • In Visual Studio, open the Settings file in Designer mode.
  • Add an additional property of type "bool" called UpgradeNeeded. Set its default value to True. Be sure to set its Roaming property to True also.
  • Click the "View Code" button. ViewCode
  • A Settings.cs class file (or Settings1.cs depending on the name of your settings) is created. In the class constructor, add this code snippet:
if (UpgradeNeeded)
    {
       UpgradeNeeded = false;
       Save();
       Upgrade();
    }

The completed constructor should look similar to:

 internal sealed partial class Settings  {

 public Settings() {
   if (UpgradeNeeded)
   {
      UpgradeNeeded = false;
      Save();
      Upgrade();
   }
}
  • Now, when you upgrade to a newer version of ArcGIS Pro, the values of the two application settings - IsOtherExpanded and GeneralSetting will be persisted to the newer version.

To see a sample with a complete workflow download the BackStage_PropertyPage sample from the arcgis-pro-sdk-community-samples repo.

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