ProGuide Applying Custom Styles - kataya/arcgis-pro-sdk GitHub Wiki
This guide steps you through the process of applying custom styles to Add-in UIs to support Pro's Light and Dark themes. For example, a user control with special styling requirements or a control from a 3rd party library that cannot use the ESRI_XXXXX styles. The code used to illustrate this add-in can be found in the Custom Styling Sample.
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
ArcGIS Pro currently supports 2 themes: Light and Dark. In order for your Add-ins to blend seamlessly with the UI of Pro, they will need to likewise support a Light and Dark mode. In general, ensuring that all UI content either inherits the correct ESRI style (which is the case for Checkbox, Radio, Listbox) or applies the correct ESRI style (eg TextBlock, TextBox, Label, Button, Tooltips) is sufficient. Consult ProGuide Style Guide for general guidelines on styling your Add-ins within Pro.
However, there may be situations or scenarios where you cannot use the available ESRI styles and colors - perhaps because your content requires custom styling (eg you roll your own brushes, colors, highlights, drop shadows, etc.) or you are consuming a 3rd party control which requires custom styling. There may be other situations. In this case, you take full responsibility for providing the styling and ensuring it "flips" to the correct Light or Dark mode depending on the current theme in use by Pro.
This guide walks you through the process of authoring a custom UserControl with both Light and Dark styling. The control flips to the appropriate custom light or dark style to match the current theme in use within Pro.
In addition to the Light and Dark themes within Pro, Pro also supports the Windows High Contrast mode. High Contrast mode is an OS setting for users who need a high degree of color contrast to read text on their screen. High Contrast mode can be turned on via the Control Panel or the key combination: left Alt+left Shift+Print Screen.
Pro bases its High Contrast mode theme off Dark Mode and for the purpose of this ProGuide it is assumed that if you put your OS in High Contrast mode then your custom dark theme will be applied.
ArcGIS Pro light or dark theme is selected off the pro Backstage, Options, Application settings, General. Scroll down to the Personalize section and select the Light or Dark theme.
To toggle themes, the application must be restarted. Note: the selected theme setting is always overridden by the OS level High Contrast mode.
Prerequisite
Create a new add-in. You can follow the steps in the ProGuide Build Your First Add-in if need be. Call your Add-in project 'CustomStyling'. Add the following project folders (within visual studio):
- Resources
- Themes
- UI
(The initial project will already contain both a DarkImages and Images folder).
Right-click on the UI project folder. Add an ArcGIS Pro Dockpane (just leave the name as the default 'Dockpane1') from the ArcGIS Pro SDK item templates. Next, add a User Control (WPF) to the UI folder also. Change the name of the UserControl to 'MyCustomControl'.
Your visual studio solution should look like this:
Step 1
Edit the Config.daml. Change the caption of the <button
DAML element to read "Show Themed Dockpane".
<controls>
<button id="CustomStyling_UI_Dockpane1_ShowButton" caption="Show Themed Dockpane" ...>
Step 2
We are going to add a series of Resource Dictionaries that will contain our custom styles. We will be adding 4 dictionaries. Right click on the Resources folder. From the context menu, select Add New Item..., WPF, Resource Dictionary (WPF). Name the 4 Resource Dictionaries as follows:
- DarkColors.xaml
- DarkResources.xaml
- LightColors.xaml
- LightResources.xaml
Step 3
Next, define the dark and light styles and colors for our UI.
Edit LightResources.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Resources">
<SolidColorBrush x:Key="App_White" Color="#FFFFFF"/>
<SolidColorBrush x:Key="App_Black" Color="#000000"/>
<SolidColorBrush x:Key="App_Gray" Color="#F7F8F8"/>
<SolidColorBrush x:Key="App_Gray2" Color="#4C4C4C"/>
<Style x:Key="AppTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource App_Gray2}" />
</Style>
</ResourceDictionary>
Edit LightColors.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Resources">
<Color x:Key="App_White_color">#FFFFFF</Color>
<Color x:Key="App_Black_color">#000000</Color>
</ResourceDictionary>
Edit DarkResources.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Resources">
<SolidColorBrush x:Key="App_White" Color="#000000"/>
<SolidColorBrush x:Key="App_Black" Color="#FFFFFF"/>
<SolidColorBrush x:Key="App_Gray" Color="#333333"/>
<SolidColorBrush x:Key="App_Gray2" Color="#D1D1D1"/>
<Style x:Key="AppTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="Foreground" Value="{DynamicResource App_Gray2}" />
</Style>
</ResourceDictionary>
Edit DarkColors.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Resources">
<Color x:Key="App_White_color">#000000</Color>
<Color x:Key="App_Black_color">#FFFFFF</Color>
</ResourceDictionary>
Save your work.
Notice how the naming between the light and dark resource dictionary items are the same. This allows us to reference one set of named resources within our code allowing us to swap out their actual definition (light or dark colors, brushes, etc.) at runtime to match the corresponding theme of Pro.
Step 4
Consolidate the light and dark resource dictionaries into a single light and dark "theme" resource dictionary respectively. We will add 2 resource dictionaries to the Theme folder. Right-click on the Theme folder and from the context menu, select Add New Item..., WPF, Resource Dictionary (WPF). Name the 2 Resource Dictionaries as follows:
- DarkTheme.xaml
- LightTheme.xaml
Edit DarkTheme.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Themes">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CustomStyling;component/Resources/DarkColors.xaml"></ResourceDictionary>
<ResourceDictionary Source="pack://application:,,,/CustomStyling;component/Resources/DarkResources.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Edit LightTheme.xaml
. Add the following entries into the resource dictionary:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:CustomStyling.Themes">
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/CustomStyling;component/Resources/LightColors.xaml"></ResourceDictionary>
<ResourceDictionary Source="pack://application:,,,/CustomStyling;component/Resources/LightResources.xaml"></ResourceDictionary>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
Save your work.
Step 5
Edit the custom User Control MyCustomControl.xaml
. Add in the following content to the XAML:
<UserControl x:Class="CustomStyling.UI.MyCustomControl"
...
...>
<Grid>
<StackPanel Orientation="Vertical" VerticalAlignment="Center" HorizontalAlignment="Center">
<Border BorderBrush="{DynamicResource App_White}"
Background="{DynamicResource App_White}" >
<TextBlock Text="Custom Text" Foreground="{DynamicResource App_Black}"
FontSize="32"
Margin="50">
<TextBlock.ToolTip>
<ToolTip Background="{DynamicResource App_Gray}">
<TextBlock Text="Tooltip Text" TextWrapping="Wrap" MaxWidth="120"
Style="{DynamicResource AppTextStyle}" VerticalAlignment="Top"/>
</ToolTip>
</TextBlock.ToolTip>
</TextBlock>
</Border>
</StackPanel>
</Grid>
</UserControl>
The UserControl should look something like this in the Visual Studio designer. You may have to compile the project first.
The XAML editor will complain about the DynamicResources in the control and not knowing how to resolve them. To resolve these references at design time (optional) add a reference to either the dark or light theme resource dictionary into the UserControl resources.
(Optional) Add the following xaml to the UserControl.Resources
.
<UserControl x:Class="CustomStyling.UI.MyCustomControl"
...
...>
<UserControl.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<extensions:DesignOnlyResourceDictionary Source="pack://application:,,,/CustomStyling;component/Themes/LightTheme.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</UserControl.Resources>
Note the <extensions:
prefix on the DesignOnlyResourceDictionary
element. If intellisense (or Resharper if you have it) does not automatically resolve the extensions namespace prefix then add the following namespace attribute to the UserControl element:
<UserControl x:Class="CustomStyling.UI.MyCustomControl"
...
xmlns:extensions="clr-namespace:ArcGIS.Desktop.Extensions;assembly=ArcGIS.Desktop.Extensions"
...
... >
In the designer, your custom control will always be styled with LightTheme.xaml
. Note: You can read more about design time only resource dictionaries here.
Step 6
Add a reference to the MyCustomControl
UserControl in the DockPane1.xaml
(i.e. our Add-in DockPane View class). You can either delete out all the existing content (added by the Dockpane SDK template) or simply add the MyCustomControl
reference at the bottom of the xaml file:
...
...
<ui:MyCustomControl Grid.Row="1"></ui:MyCustomControl>
</Grid>
</UserControl>
If the <ui:
namespace prefix does not get resolved, add a namespace declaration attribute to the DockPane1View
element:
<UserControl x:Class="CustomStyling.UI.Dockpane1View"
...
...
xmlns:ui="clr-namespace:CustomStyling.UI"
...>
Save your work. Compile and fix any errors.
Step 7
When the Pro theme changes, we are responsible for changing our own Add-in styling to match (if we want to blend with the Pro UI). To do that, we have to tell our UserControl which of our custom themes to use to resolve the DynamicReferences added into the control in step 5. Pro provides the FrameworkApplication.ApplicationTheme
property that tells us what the active theme currently is.
Whenever we are required to load our custom styling for our UI, we check this property on the Pro application and load our corresponding custom light or dark theme accordingly. The Pro theme is set at startup and can only be changed by restarting Pro. Therefore, we only need to check the theme setting once per session. We will perform our check in the constructor of our UserControl.
Add the following method into the code-behind file MyCustomControl.xaml.cs
:
private void InitResources() {
var resources = this.Resources;
resources.BeginInit();
if (FrameworkApplication.ApplicationTheme == ApplicationTheme.Dark ||
FrameworkApplication.ApplicationTheme == ApplicationTheme.HighContrast) {
resources.MergedDictionaries.Add(
new ResourceDictionary() {
Source = new Uri("pack://application:,,,/CustomStyling;component/Themes/DarkTheme.xaml")
});
}
else {
resources.MergedDictionaries.Add(
new ResourceDictionary() {
Source = new Uri("pack://application:,,,/CustomStyling;component/Themes/LightTheme.xaml")
});
}
resources.EndInit();
}
Add the following IsInDesignMode
property to the UserControl. (When we are in design mode we want to avoid calling our InitResources()
code. The code will fail because of the FrameworkApplication.ApplicationTheme
property and the user control will not load into the designer).
internal bool IsInDesignMode {
get {
return (bool) DependencyPropertyDescriptor.FromProperty(
DesignerProperties.IsInDesignModeProperty, typeof (DependencyObject)).Metadata.DefaultValue;
}
}
Call the InitResources()
method from within the MyCustomControl
constructor when we are not in the designer:
public MyCustomControl() {
InitializeComponent();
if (!IsInDesignMode)
InitResources();
}
Within InitResources()
, we check the FrameworkApplication.ApplicationTheme
property and load our corresponding custom theme into our UserControl resources. Our App_
DynamicResources will resolve to either light or dark themed colors depending on the current theme of Pro.
Save your work. Compile and fix any errors. Run the Add-in in the debugger.
Step 8
Open any project. Click on the ADD-IN tab. Find the "Show Themed Dockpane" button. Click it to show your Dockpane.
If Pro is in Light mode, the corresponding style of the custom UserControl should be light. If Pro is in Dark mode (or you are running the OS in High Contrast), the style of the custom UserControl should be dark.
Go to the backstage and change the theme of Pro in the Application options, General settings. Restart Pro. Now when you display the dockpane verify the style of the UserControl has also changed.