Advanced CustomizationCharles Macleod, Steve Van Esch
Advanced Customization and Extensibility
• Pro Extensibility Overview
- Custom project and application settings
- Project options
- Multiple Add-ins
- UI Customization via DAML
- Versioning
- Categories
- Embeddable Controls for plug and play UI
Subhead Here
Advanced Customization – Project SettingsSubhead Here
• Project settings are persisted in the project
• Override OnReadSettingsAsync and OnWriteSettingsAsync on the Module class
• Your custom settings are passed in via OnReadSettingsAsync
- Whenever a project is opened (that contains your custom settings)
• Your custom settings are saved to the project via OnWriteSettingsAsync
- Whenever a project is saved.
Advanced Customization – Project SettingsSubhead Here
• Settings are stored as key-value pairs. Values must be strings.
• Settings are added to the ModuleSettingsWriter during a save
internal class Module1 : Module {private bool hasSettings = false;//Called on “Save” project
protected override Task OnWriteSettingsAsync(ModuleSettingsWriter settings) {settings.Add(“Setting1”, “Custom_setting_val1”);settings.Add(“Setting2”, “Custom_setting_val2”);...return Task.FromResult(0);
}
Advanced Customization – Project SettingsSubhead Here
• Settings are retrieved via OnReadSettingsAsync. Use the ModuleSettingsReader
- OnReadSettingsAsync is called when the project is opened (if you have any settings)
- You are responsible for your own defaults in the case of a project not having custom settings
internal class Module1 : Module {private bool hasSettings = false;//Called on project open
protected override Task OnReadSettingsAsync(ModuleSettingsReader settings) {hasSettings = true; //Set our flag true…
var setting1 = settings[“Setting1”];var setting2 = settings[“Setting2”]; ...return Task.FromResult(0);
}
ProjectOpenedEvent.Subscribe((args)=> {if (!hasSettings) //We did NOT have custom settings…
ProjectClosedEvent.Subscribe((args) => hasSettings = false); //reset our flag
Advanced Customization – Application SettingsSubhead Here
• Application settings get added via .NET property settings
- In Visual Studio, add a new item type of “settings” to your Add-in
- Visual studio will automatically generate a settings class for you
- Settings stored in an app.config in the project
- At runtime, dynamically stored in a user.config in the user’s AppData folder
Advanced Customization – Application SettingsSubhead Here
• Use the property designer to add your application properties
• Visual Studio generates a settings class with the same properties.
- Get and set the properties in code just like a “regular” class
- Call ‘Save()’ on the settings class to persist the settings
Settings1.Default.ShowLastSave = false;Settings1.Default.Save();
Advanced Customization Subhead Here
Demo
Advanced Customization – Project Options Subhead Here
• Pro Options “Property Sheet”
Advanced Customization – Project Options Subhead Here
• Add Project and Application options to the Pro Options “Property Sheet”
- Provide a property page for both project and application options
- Use the Pro SDK Property Sheet item template
- Update property sheet with refID= “esri_core_optionsPropertySheet”
- Config.daml
Advanced Customization – Project Options Subhead Here
• Add Project and Application property pages
- Add New Item -> ArcGIS -> ArcGIS Pro Property Sheet
- Adds an individual property page view + view model
- Adds a custom property sheet container – declared in the Config.daml
- Adds a button to show the custom property sheet container
- For our purposes we only need the page(s) – view and view models
- The rest of the generated content can either be deleted or ignored.
Advanced Customization – Property PageSubhead Here
• Page View: Implement using standard WPF (User Control)
• Page View Model:
- Derives from ArcGIS.Desktop.Framework.Contracts.Page
- You must implement a CommitAsync override for your “Save” logic.
- CommitAsync is called when the user clicks OK on the property sheet
- It is only called if your page is dirty (IsModified = true)
- Set this.IsModified = true whenever a property value is changed
- Marks the page as dirty.
- Override InitializeAsync and Uninitialize as needed:
- Executed when the page is loaded and destroyed respectively.
if (SetProperty(ref _trackChanges, value, () => TrackChanges))this.IsModified = true;
Advanced Customization – Project Options - Config.damlSubhead Here
<modules><insertModule id=“..." ...>
<controls><button id="AdvCustomizationStart_UI_ShowPropertySheet" caption="Show
PropertySheet 1" className=“..." loadOnClick="true" ...></button>
<propertySheets><insertSheet id="AdvCustomizationStart_UI_PropertySheet1" ...><page id="AdvCustomizationStart_UI_ProjectOptions" caption=“Project Tracking"
className="ProjectOptionsViewModel" group="Group 1"><content className="ProjectOptionsView" />
</page></insertSheet>
</propertySheets>
• Delete “extra” content resulting from template generation
Advanced Customization – Project Options - Config.damlSubhead Here
<modules><insertModule id=“..." ...>
<controls><button id="AdvCustomizationStart_UI_ShowPropertySheet" caption="Show
PropertySheet 1" className=“..." loadOnClick="true" ...></button>
<propertySheets><insertSheet id="AdvCustomizationStart_UI_PropertySheet1" ...><page id=" AdvCustomizationStart_UI_ProjectOptions" caption="Project Tracking"
className="ProjectOptionsViewModel" group="Group 1"><content className="ProjectOptionsView" />
</page></insertSheet>
</propertySheets>
• Delete “extra” content resulting from template generation
Advanced Customization – Project Options - Config.damlSubhead Here
<propertySheets>
<updateSheet refID="esri_core_optionsPropertySheet">
</updateSheet></propertySheets>
Advanced Customization – Project Options - Config.damlSubhead Here
<propertySheets>
<updateSheet refID="esri_core_optionsPropertySheet">
<!--Use group=Project for the Project section in the settings--><insertPage id="AdvCustomizationStart_UI_ProjectOptions" caption=“Project Tracking“
className="ProjectOptionsViewModel" group=“Group 1"><content className="ProjectOptionsView" />
</insertPage>
</updateSheet></propertySheets>
Advanced Customization – Project Options - Config.damlSubhead Here
<propertySheets>
<updateSheet refID="esri_core_optionsPropertySheet">
<!--Use group=Project for the Project section in the settings--><insertPage id="AdvCustomizationStart_UI_ProjectOptions" caption=“Project Settings“
className="ProjectOptionsViewModel" group="Project"><content className="ProjectOptionsView" />
</insertPage>
</updateSheet></propertySheets>
Advanced Customization – Project Options - Config.damlSubhead Here
<propertySheets>
<updateSheet refID="esri_core_optionsPropertySheet">
<!--Use group=Project for the Project section in the settings--><insertPage id="AdvCustomizationStart_UI_ProjectOptions" caption=“Project Settings“
className="ProjectOptionsViewModel" group="Project"><content className="ProjectOptionsView" />
</insertPage>
<!--Use group=Application for the Application section in the settings--><insertPage id="AdvCustomizationStart_UI_ApplicationOptions" caption=“Save Tracking“
className="ApplicationOptionsViewModel" group=“Group 1"><content className="ApplicationOptionsView" />
</insertPage></updateSheet>
</propertySheets>
Advanced Customization – Project Options - Config.damlSubhead Here
<propertySheets>
<updateSheet refID="esri_core_optionsPropertySheet">
<!--Use group=Project for the Project section in the settings--><insertPage id="AdvCustomizationStart_UI_ProjectOptions" caption=“Project Settings“
className="ProjectOptionsViewModel" group="Project"><content className="ProjectOptionsView" />
</insertPage>
<!--Use group=Application for the Application section in the settings--><insertPage id="AdvCustomizationStart_UI_ApplicationOptions" caption=“Save Tracking“
className="ApplicationOptionsViewModel" group="Application"><content className="ApplicationOptionsView" />
</insertPage></updateSheet>
</propertySheets>
Advanced Customization Subhead Here
Demo
Advanced Customization – Multi and Versioned Add-ins Subhead Here
• There may be scenarios where you want to deploy:
- Newer versions of the same Add-in
- Augment an existing Add-in with additional capabilities
- …or both
Advanced Customization - Multi and Versioned Add-ins
• Versioning your Add-in(s):
• Deploy bug-fixes and feature enhancements
- Update the version attribute on <AddInInfo> in the Config.daml
- Pro will always load the latest version of an Add-in regardless of its location
- <AddInInfo ... version="1.0“ Earliest
- <AddInInfo ... version="1.0.0.2“
- <AddInInfo ... version="1.0.1“
- <AddInInfo ... version="1.3“ Latest
<ArcGIS defaultAssembly=“Acme_Corp_Addin.dll" ....>
<AddInInfo id="{b705ce83-52aa-453f-8095-f6ae60994ce3}" version="1.0" desktopVersion=“2.1">
<Name>Acme Corp. Addin</Name><Description>Adds standard Acme workflows to Pro for...
<AddInInfo ... version="1.0"
<AddInInfo ... version="1.0.0.2"
<AddInInfo ... version="1.0.1"
<AddInInfo ... version="1.3"
<AddInInfo ... version=“2.0"
Advanced Customization - Multi and Versioned Add-ins Subhead Here
• “Multi” Add-ins
- Deploy functionality incrementally across multiple Add-ins
- “Core” or “Basic” Add-in + other Add-ins as optional packages or extensions
- Eg extra tools or UI components
“core” or basic
“options” or enhanced
Advanced Customization - Multi and Versioned Add-ins Subhead Here
• Use “regular” daml to update the core Add-in module – typically updateTab but you can
update menus, property sheets, groups, etc.
• Option: Add a daml dependency to the “core” Add-in in any Add-in that needs to modify or
enhance it.
- Important if the enhancements Add-ins load before the core Add-in it needs to modify.
<updateModule refID=“AdvCustomizationStart_Module"><tabs><updateTab refID="AdvCustomizationStart_Tab1"><insertGroup refID="ExternalCustomizations_Group1"/>
</updateTab></tabs>
</updateModule>
<dependencies><dependency name="aacc0821-e371-407d-90c6-92fa5eb67b2f"/>
</dependencies>
Advanced Customization Subhead Here
Categories
Advanced Customization – CategoriesSubhead Here
• DAML Categories provides a custom extensibility mechanism
- Allows you, the Add-in developer, to provide your own extensibility beyond what DAML “out of
the box” provides
- Eg optional “filters” or “analysis algorithms” or other capabilities that can be augmented by additional add-ins
- There are two roles involved:
- The Host or “Owner”: defines the category and is responsible for loading any customizations
- “Main” module add-in
- Component providers: responsible for providing any customizations
- “Enhancement/options” add-ins
Advanced Customization – CategoriesSubhead Here
- The Host or Owner:
- Creates the category in the Config.daml
- Defines the requirements to “be in” the category
- Eg Implement a proprietary interface, etc.
- Uses Framework to find and retrieve category components at runtime
- Use Categories.GetComponentElements(“Your_Category_Id")
- It is the Host’s responsibility to document and enforce requirements for a category!
Advanced Customization – Categories – The Host Subhead Here
• Defines the category ID in the Config.daml
• Documents category requirements – these are your requirements
- Eg: custom daml attributes and xml content. An interface that must be implemented, etc.
<categories><insertCategory id="TestCategories"/>
</categories>
public interface IMyCategoryProvider {string Label { get; }void Doit(MapPoint pt);
}
Advanced Customization – Categories - Component Provider(s)Subhead Here
- Component providers are external modules (to the host).
- Implement the component
- Typically just a standard .NET class
- Implements any interfaces required by “host” (eg “IMyCategoryProvider”)
- Register component within the category in the Config.daml
- Note: Components will be instantiated by the Host.
Advanced Customization – Categories – The ProviderSubhead Here
• Add an updateCategory element with refID=“the target category “
<categories><updateCategory refID="TestCategories">
</updateCategory></categories>
Advanced Customization – Categories – The ProviderSubhead Here
• Add an insertComponent child element
- “className” must point to the code-behind file that contains the category implementation
<categories><updateCategory refID="TestCategories"><!-- This element is required --><insertComponent id="AdvCategoriesProvider1" className=“CategoryProvider">
</insertComponent></updateCategory>
</categories>
Advanced Customization – Categories – The ProviderSubhead Here
• Add a child content element to the insertComponent. It has no attributes or children
….unless….!
<categories><updateCategory refID="TestCategories"><!-- This element is required --><insertComponent id="AdvCategoriesProvider1" className=“CategoryProvider"><!– A content element is REQUIRED. All attributes are custom!--><content/>
</insertComponent></updateCategory>
</categories>
Advanced Customization – Categories – The ProviderSubhead Here
• The Category host requires custom content – eg custom attributes or custom xml child
content….in which case it must be added to the Config.daml
<categories><updateCategory refID="TestCategories"><!-- This element is required --><insertComponent id="AdvCategoriesProvider1" className=“CategoryProvider"><!– A content element is REQUIRED. All attributes are custom!--><content version="1.0" name=“custom" label=“etc,etc”>
<!-- add any custom xml content here --><param1 ... />
</content></insertComponent>
</updateCategory></categories>
Advanced Customization – Loading Categories – The Host Subhead Here
• Implement loading the category components (e.g. in Module Initialize, etc)
- Use Categories.GetComponentElements(“Your_Category_DAML_Id")
- Returns an enumeration of ArcGIS.Desktop.Framework.ComponentElement
- Each ComponentElement represents a component record (from add-in Config.damls)
- For each ComponentElement:
- Use GetContent() to return the <content/> element from the Config.daml
- Returns a System.Xml.Ling.XElement.
- Query it for custom attributes and child elements
- Use CreateComponent() to instantiate a custom class instance
Advanced Customization – Loading Categories – The Host Subhead Here
• Implement loading the category components (e.g. in Module Initialize, etc)
_items = new List<IMyCategoryProvider>();XNamespace ns = "http://schemas.esri.com/DADF/Registry";
foreach (var component in Categories.GetComponentElements("TestCategories")) {var content = component.GetContent();// this is the content from Config.damlvar version = content.Attribute("version").Value;var param1 = content.Element(ns + "param1");
//test the componentvar instance = component.CreateComponent() as IMyCategoryProvider;if (instance != null)
_items.Add(instance);}
Advanced Customization – Categories and Embeddable Controls Subhead Here
• Embeddable Controls
Advanced Customization – Categories and Embeddable Controls Subhead Here
• Assume we want to provide an extensibility pattern…..as before…..
• And….it requires a dynamic UI…
Advanced Customization – Categories and Embeddable ControlsSubhead Here
• We need to use something called an “Embeddable Control”
• This is an aspect of the Pro SDK
- “ArcGIS Pro Embeddable Control” item template
- An embeddable control consists of:
- A view component –WPF User Control
- A view model component
- Derives from ArcGIS.Desktop.Framework.Controls.EmbeddableControl
Advanced Customization – Categories and Embeddable Controls Subhead Here
• For dynamic UI:
• As the Host we:
- Define a category same as before
- Define any required custom interfaces, xml content (as before)
- Require providers to register embeddable controls in our category
- Not “vanilla” .NET classes as before
Advanced Customization – Categories and Embeddable ControlsSubhead Here
• For dynamic UI
• As the Provider we:
- Implement the component using the Embeddable Control pattern
- Run the Pro SDK Embeddable Control item template
- Implement any required interfaces on the VewModel
- Register the embeddable control in the category as the category component
Advanced Customization – Categories and Embeddable ControlsSubhead Here
- Registering the EmbeddableControl in the target category in the Config.daml
<!– Note: item template generates this for you!! --><updateCategory refID="TestCategory"> <!–- simply switch out “esri_embeddableControls” -->
<!–- In this case, the component is the EmbeddableControl (ie the “View Model”) --><insertComponent id="Custom_EmbeddableControl" className="EmbeddableControlViewModel">
<!– The content element is the view (i.e. UserControl) <content className="ExtensionEmbeddableControlView" version=“...">
<!-- add any custom xml content here as needed --><param1 ... />
</content></insertComponent>
</updateCategory>
Advanced Customization – Loading Categories and Embeddable
Controls
• As the Host:
- Use Categories.GetComponentElements(“Your_Category_DAML_Id") as before
- Can use GetContent() , etc as needed to check for custom attributes, etc.
- Use EmbeddableControl.Create() in lieu of CreateComponent() to create the
embeddable control.
- EmbeddableControl.Create returns a Tuple<EmbeddableControl, UserControl>
- The EmbeddableControl should be tested for any required interfaces
- Host the UserControl in the UI (per requirements, whatever they are…)
- Standard WPF pattern is to use a <ContentPresenter />
Advanced Customization – Loading Categories and Embeddable
Controls • Host: Implement loading the category components (e.g. in Module Initialize, etc)
_items = new List<Tuple<IMyCategoryProvider, UserControl>>();foreach (var component in Categories.GetComponentElements("TestCategories")) {//get the contentvar version = component.GetContent().Attribute("version").Value;
//test the component - note: we are using EmbeddableControl.Create!
var t = EmbeddableControl.Create(component.ID, "TestCategories", null, false);if (t.Item1 is IMyCategoryProvider)//Item1 is the EmbeddableControl component
items.Add(new Tuple<IMyCategoryProvider, UserControl>((IMyCategoryProvider)t.Item1,t.Item2));
}
Advanced Customization – Loading Categories and Embeddable
Controls • Host: Show the UI (as/when needed)
- In the Host UI View place a ContentPresenter as the placeholder in the xaml
- In the Host UI View Model, set category provider’s UserControl as the ContentPresenter content:
_items = new List<Tuple<IMyCategoryProvider, UserControl>>();...this.ProviderContent = _items[0].Item2;//Item2 is the UserControlNotifyPropertyChanged(“ProviderContent”);
<UserControl x:Class=“..."><DockPanel LastChildFill="True" HorizontalAlignment="Stretch" Height="{Binding ControlHeight}">
<ContentPresenter Content="{Binding ProviderContent}“/></DockPanel>
</UserControl>
Advanced Customization Subhead Here
Demo
Advanced Customization Subhead Here
Summary
• Override Module Read and Write Settings methods to implement custom project settings
• Use .NET for Application properties
- Add Property pages to Project Options for configurable UIs
• Use DAML and AddInInfo version attribute to support multi-addin deployments
• Implement Categories and custom interfaces for runtime extensibility
- Require providers to implement embeddable controls for dynamic UI
ArcGIS Pro SDK for .NET Tech Sessions
Date Time ArcGIS Pro SDK for .NET Tech Sessions Location
Tue, Mar 06
1:00 pm - 2:00 pm An Overview of the Geodatabase API Mojave Learning Center
2:30 pm - 3:30 pm Beginning Pro Customization and Extensibility Primrose A
5:30 pm - 6:30 pm Beginning Editing and Editing UI Patterns Mojave Learning Center
Wed, Mar 07
10:30 am - 11:30 am Mapping and Layout Pasadena/Sierra/Ventura
1:00 pm - 2:00 pm Advanced Pro Customization and Extensibility Santa Rosa
2:30 pm - 3:30 pm Pro Application Architecture Overview & API Patterns Mesquite G-H
4:00 pm - 5:00 pm Advanced Editing and Edit Operations Santa Rosa
Thu, Mar 089:00 am - 3:30 pm Getting Started Hands-On Training Workshop Mojave Learning Center
5:30 pm - 6:30 pm Working with Rasters and Imagery Santa Rosa
Fri, Mar 09
8:30 am - 9:30 am An Overview of the Utility Network Management API Mesquite G-H
10:00 am - 11:00 am Beginning Pro Customization and Extensibility Primrose A
1:00 pm - 2:00 pm Advanced Pro Customization and Extensibility Mesquite G-H
ArcGIS Pro SDK for .NET Demo Theater Sessions
Date TimeArcGIS Pro SDK for .NET Demo Theater
PresentationLocation
Tue, Mar 061:00 pm - 1:30 pm Getting Started Demo Theater 1 - Oasis 1
4:00 pm - 4:30 pm Custom States and Conditions Demo Theater 2 - Oasis 1
Wed, Mar 075:30 pm - 6:00 pm New UI Controls for the SDK Demo Theater 2 - Oasis 1
6:00 pm - 6:30 pm Raster API and Manipulating Pixel Blocks Demo Theater 2 - Oasis 1
ArcGIS Pro Road Ahead Sessions
Date TimeArcGIS Pro SDK for .NET Demo Theater
Presentation Location
Tue, Mar 06 4:00 pm – 5:00 pm ArcGIS Pro: The Road Ahead Oasis 4
Thu, Mar 08 4:00 pm – 5:00 pm ArcGIS Pro: The Road Ahead Primrose B
Advanced Customization Subhead Here
Questions?
Advanced Customization - Multi and Versioned Add-ins Subhead Here
• Inter-Module Access
- Enhancement add-ins [can] access (your) “main” module settings
- Use FrameworkApplication.FindModule with the core module id to retrieve the desired module
instance (in this case the one that has the settings)
- Implement an interface of your own design if needed to avoid referencing the “Module1” class
//Access the "core" module using Pro Framework. Cast it to your proprietary interface//to provide access to underlying settings, properties, as needed...var config = FrameworkApplication.FindModule(“AdvCustomizationStart_Module") as
IModuleProps;var val = config.SomeProperty;...
Advanced Customization – Multi and Versioned Add-ins Subhead Here
• Optional: Export custom events from your Core-Addin module
- Define Event Args class and Event class also in your “separate” assembly – ‘AcmeLib.dll`*
- Reference ArcGIS.Core.dll
- Reference ArcGIS.Desktop.Framework.dll
- Event derives from: ArcGIS.Core.Events.CompositePresentationEvent<CustomEventArgs>
- Implement a static Subscribe, Unsubscribe, Publish.
- Wrap FrameworkApplication.EventAggregator.GetEvent<Your_Custom_Event>()
- Core Module calls Publish to broadcast the event. Listeners call Subscribe and Unsubscribe.
- *Again, this is a preference, not a requirement
Advanced Customization – Event Implementation Subhead Here
• Textpublic class CustomOptionsEventArgs : EventArgs {... public IAcmeConfig AcmeConfigProperties => _configProps;
}
public class CustomOptionsEvent :CompositePresentationEvent<CustomOptionsEventArgs> {public static SubscriptionToken Subscribe(Action<CustomOptionsEventArgs> action, bool
keepSubscriberReferenceAlive = false) {return FrameworkApplication.EventAggregator.GetEvent<CustomOptionsEvent>()
.Register(action, keepSubscriberReferenceAlive);}public static void Unsubscribe(Action<CustomOptionsEventArgs> subscriber) {
FrameworkApplication.EventAggregator.GetEvent<CustomOptionsEvent>().Unregister(subscriber);}public static void Unsubscribe(SubscriptionToken token){
FrameworkApplication.EventAggregator.GetEvent<CustomOptionsEvent>().Unregister(token);}public static void Publish(CustomOptionsEventArgs payload) {FrameworkApplication.EventAggregator.GetEvent<CustomOptionsChangedEvent>().Broadcast(payload);}
}