ProGuide Custom settings - kataya/arcgis-pro-sdk GitHub Wiki
Language: C#
Subject: Framework
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
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
- Creating custom project settings
- Creating application level 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
to read your settings (topic10521.html) -
OnWriteSettingsAsync
to write your settings (topic10522.html)
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:
- A true/false flag value which can be toggled via a checkbox
- 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.
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.
- 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.
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 {
...
}
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 propertyModuleSetting2
. 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.
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 togroup="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.
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.
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);
}
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);
}
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">
…
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.
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:
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();
}
}
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.
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.
- Add two check boxes to the ApplicationSettings.xaml and bind their IsChecked properties to
GeneralSetting
andOtherSetting
properties. Note: You are also adding two expanders bound toIsGeneralExpanded
andIsOtherExpanded
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:
-
GeneralSetting
andOtherSetting
to hold the custom application settings -
IsGeneralExpanded
andIsOtherExpanded
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); }
}
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>
Step 4: Add a Settings.settings file 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 Settings.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.exe_StrongName_yhpsrysqpn4fvmb0spwbakt5o5e50din\[Version]
. Your add-in's Settings.Settings class would 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:
- Note: ArcGIS Pro 2.6 and higher stores all settings in the user.config file located in the "Roaming" folder in AppData. In previous versions, user.config file was located in the "Local" folder in the AppData. In order to use the Roaming folder to store your custom application settings, make sure the "Roaming" attribute of your settings is set to true (the "Scope" remains as 'User'):
Visual studio will automatically generate a Settings
class for you, with those properties, that derives from System.Configuration.ApplicationSettingsBase
.
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
CustomSettings.Properties.Settings settings = CustomSettings.Properties.Settings.Default;
// 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
CustomSettings.Properties.Settings settings = CustomSettings.Properties.Settings.Default;
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.
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.
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 a new property of type "bool" called UpgradeNeeded. Set its default value to True.
- Click the "View Code" button.
- A Settings.cs class file is created. In the class constructor, add this code snippet:
if (UpgradeNeeded) { UpgradeNeeded = false; Save(); Upgrade(); }
- 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.
With the ArcGIS Pro 2.6 release, custom application settings are now supported for the "Roaming" profile. Specifically, this means a user's user.config file will now be stored and used from the "Roaming" folder in AppData. The exact location would be similar to C:\Users\[UserName]\AppData\Roaming\ESRI\ArcGISPro.exe_StrongName_yhpsrysqpn4fvmb0spwbakt5o5e50din\[Version]
. In previous versions, the user.config file was always located in the "Local" folder in AppData.
Migrating Add-ins to use the Roaming profile
If you have existing add-ins that were authored with versions of Pro older than 2.6 that use custom application settings, and you want to move those settings from the Local to the Roaming profile, please use the following procedure (note: for existing add-ins that continue to use the 'Local' profile, the application will continue to read and honor your custom properties (from Local) as normal. Migrating to Roaming is not required):
- In your add-in or configuration project, change the Roaming property attribute of the custom setting to be True.
- Install Pro 2.6 (or newer version). All the properties in the Local\User.config will migrate to Roaming\user.config the first time you launch Pro.
-
Note: If Pro 2.6 (or newer version) has already been launched before the change in the add-in was made:
- Delete HKEY_CURRENT_USER\Software\ESRI\ArcGISPro\Settings\UserConfigMovedToRoaming key or set its value to 0.
- Delete or rename the user.config in the Roaming folder. Launch Pro 2.6 or the newer version again. The registry key change will trigger Pro to migrate the settings from the Local\user.config to the Roaming\user.config.
With the help of the ArcGIS Pro SDK you are able to create your own custom Project settings. A Property sheet is created using XAML and the settings are defined in the ViewModel as public properties. The View Model also writes/reads these settings to a dictionary. The Module class’ overrides OnReadSettings Async and OnWriteSettingsAsync are then used to read and write these settings via the dictionary when a project is opened or saved.
Using the MVVM pattern you can also create an Application level custom settings for the current user. Application settings uses the standard .NET Settings mechanism to store our custom application settings in a config file that will be read by the parent application for the current logged-on user.