Clone our Wealth-Lab 8 Extension Demo project on GitHub to get a head start in developing your own Extensions!

Theming in WealthLab Extensions

WealthLab 9 supports application-wide theming, including Light and Dark modes. Because WealthLab's desktop interface is built with WPF, extensions can participate in the active Theme by using the Theme resources and controls supplied by WealthLab.WPF. The WealthLab theming framework is designed so that additional Themes can be supported in the future.

Accessing the Host (WL9) Environment

The IHost interface provides access to the current WealthLab environment. Extensions can use it to retrieve application-level information and services, such as the location of the user's WealthLab data folder or the DataSets defined by the user.

You can access the current IHost instance from anywhere in your extension through the WLHost singleton and its Instance property. For example, the following code retrieves the path to the user's WealthLab data folder:

string folder = WLHost.Instance.DataFolder;

Use WLHost.Instance whenever your extension needs access to functionality exposed by the IHost interface.

Theme Resources

WealthLab defines Theme-specific resources in XAML resource dictionaries. When building WPF extensions, especially custom Windows and UserControls, use these Theme resources instead of hard-coding colors wherever practical. Reference Theme resources using WPF DynamicResource bindings so the UI automatically reflects the currently selected Theme. For example:

Background="{DynamicResource BrownBackground}"

When the user changes Themes, the resource resolves to the value defined by the newly selected Theme.

Common Theme Resources

The following resources are available for use by WealthLab extensions.

Window Colors

<SolidColorBrush x:Key="BackgroundColour" />
<SolidColorBrush x:Key="WindowBorderColour" />
<SolidColorBrush x:Key="WindowTitleColour" />

WealthLab-Specific Colors

<SolidColorBrush x:Key="MDITitleBackground" />
<SolidColorBrush x:Key="SelectorBackground" />

<SolidColorBrush x:Key="BuildingBlock" />
<SolidColorBrush x:Key="BuildingBlockBuy" />
<SolidColorBrush x:Key="BuildingBlockSell" />
<SolidColorBrush x:Key="BuildingBlockShort" />
<SolidColorBrush x:Key="BuildingBlockCover" />

<SolidColorBrush x:Key="GreenButtonBackground" />
<SolidColorBrush x:Key="GreenButtonBorder" />
<SolidColorBrush x:Key="RedButtonBackground" />

Panel Background Colors

<SolidColorBrush x:Key="GreenBackground" />
<SolidColorBrush x:Key="RedBackground" />
<SolidColorBrush x:Key="YellowBackground" />
<SolidColorBrush x:Key="BlueBackground" />
<SolidColorBrush x:Key="ConstructorBackground" />
<SolidColorBrush x:Key="OrangeBackground" />
<SolidColorBrush x:Key="VioletBackground" />
<SolidColorBrush x:Key="BrownBackground" />
<SolidColorBrush x:Key="BlueGrayBackground" />
<SolidColorBrush x:Key="WealthSignalsBackground" />

Text Colors

<SolidColorBrush x:Key="RedText" />
<SolidColorBrush x:Key="GreenText" />
<SolidColorBrush x:Key="BlueText" />

Building Block Colors

<SolidColorBrush x:Key="BBBackground" />
<SolidColorBrush x:Key="BBLongEntry" />
<SolidColorBrush x:Key="BBLongExit" />
<SolidColorBrush x:Key="BBShortEntry" />
<SolidColorBrush x:Key="BBShortExit" />
<SolidColorBrush x:Key="BBCondition" />
<SolidColorBrush x:Key="BBQualifier" />

Standard UI Colors

<SolidColorBrush x:Key="TransparentBrush" />
<SolidColorBrush x:Key="ContainerBackground" />
<SolidColorBrush x:Key="ContainerBorder" />

<SolidColorBrush x:Key="ControlDefaultForeground" />
<SolidColorBrush x:Key="ControlMOSelectForeground" />

<SolidColorBrush x:Key="ControlDarkerBackground" />
<SolidColorBrush x:Key="ControlDarkerBorderBrush" />

<SolidColorBrush x:Key="ControlDefaultBackground" />
<SolidColorBrush x:Key="ControlDefaultBorderBrush" />

<SolidColorBrush x:Key="ControlBrightDefaultBackground" />
<SolidColorBrush x:Key="ControlBrightDefaultBorderBrush" />

<SolidColorBrush x:Key="ControlDisabledBackground" />
<SolidColorBrush x:Key="ControlDisabledBorderBrush" />

<SolidColorBrush x:Key="ControlMouseOverBackground" />
<SolidColorBrush x:Key="ControlMouseOverBorderBrush" />

<SolidColorBrush x:Key="ControlSelectedBackground" />
<SolidColorBrush x:Key="ControlSelectedBorderBrush" />

<SolidColorBrush x:Key="ControlSelectedMouseOverBackground" />
<SolidColorBrush x:Key="ControlSelectedMouseOverBorderBrush" />

<SolidColorBrush x:Key="ControlGlythColour" />
<SolidColorBrush x:Key="ControlMouseOverGlythColour" />
<SolidColorBrush x:Key="ControlSelectedGlythColour" />
<SolidColorBrush x:Key="ControlDisabledGlythColour" />

The standard foreground text color is:

{DynamicResource ControlDefaultForeground}

Toolbars generally use:

{DynamicResource ContainerBackground}

for their background.

Determining the Current Theme

In C# Code

The IWLClientHost interface in WealthLab.WPF exposes information about the Theme associated with a WealthLab window. Use:

WLClientHost.Instance.IsDarkTheme

to determine whether the active Theme is Dark. For example:

bool isDark =
    WLClientHost.Instance.IsDarkTheme;

Detecting Theme Changes

You can subscribe to Theme changes through the EventRouter class in WealthLab.Core. Subscribe to the event:

ThemeChanged

The event parameter is a bool indicating whether the newly selected Theme is Dark. Use this when your extension performs Theme-sensitive work that cannot be handled entirely through DynamicResource bindings.

In XAML

The Theme resources also expose a Boolean resource named:

IsDark

You can bind to it using DynamicResource. For example:

IsReversed="{DynamicResource IsDark}"

This is useful for controls such as WLImage that can alter their appearance for the Dark Theme.

Example: Theme-Aware Image

The following example uses WLImage in a DataTemplate and automatically reverses the image when Dark Theme is active:

<GridViewColumn.CellTemplate>
    <DataTemplate>
        <StackPanel Orientation="Horizontal">

            <wpf:WLImage
                Width="14"
                Height="14"
                Margin="0,1,2,0"
                BaseSource="../Images/NSF.png"
                IsReversed="{DynamicResource IsDark}"
                Visibility="{Binding NSF,
                    Converter={StaticResource boolVis}}"
                ToolTip="NSF Position"/>

            <Image
                Width="14"
                Height="14"
                Margin="0,0,4,0"
                Source="{Binding PositionType,
                    Converter={StaticResource PosTypeToImage}}"/>

            <TextBlock
                Text="{Binding PositionType}"
                Foreground="{Binding Profit,
                    Converter={StaticResource ProfitBrush}}"/>

        </StackPanel>
    </DataTemplate>
</GridViewColumn.CellTemplate>

Reversible Glyphs in Configurable Components

Many WealthLab extension types ultimately derive from Configurable. Configurable exposes the GlyphResource property used to represent the component in the WealthLab user interface. By default, WealthLab can reverse the glyph when the Dark Theme is active. If your glyph is designed to work correctly in both Light and Dark Themes without modification, override:

public virtual bool DisableGlyphReverse

and return true. For example:

public override bool DisableGlyphReverse =>
    true;

WLImage

WLImage, defined in WealthLab.WPF, derives from the WPF Image control and adds Theme-aware image handling.

BaseSource

public ImageSource BaseSource

Gets or sets the source image. WLImage can automatically display a reversed version when required by the current Theme.

DisableReverse

public bool DisableReverse

Set this property to true to disable automatic image reversal.

IsReversed

public bool IsReversed

Gets or sets whether the image is currently displayed in its reversed state. This property can also be bound to the Theme's IsDark DynamicResource.

WLButton

WLButton, defined in WealthLab.WPF, derives from WPF's Button class and provides additional WealthLab functionality, including Theme-aware image handling.

DisplayState

public WLButtonDisplayState DisplayState

Controls what the button displays. Supported values include:

  • Text
  • Image
  • ImageAndText

Text

public string Text

Contains the text displayed by the button when DisplayState includes Text.

Image

public ImageSource Image

Contains the image displayed when DisplayState includes Image. WLButton uses WLImage internally to provide Theme-aware image handling.

DisableReverse

public bool DisableReverse

Set this property to true if the button's image should not be automatically reversed when using a Dark Theme.

Theming Custom Windows

When creating a custom WealthLab window, derive from the DialogWindow class in WealthLab.WPF instead of directly from WPF's Window class. DialogWindow contains the integration required to participate in WealthLab theming. Apply the standard WealthLab window style using:

Style="{DynamicResource CustomToolWindowStyle}"

Use DynamicResource bindings for the Window's Theme-sensitive colors. For example:

Background="{DynamicResource ContainerBackground}"

Sizing DialogWindow

WPF normally uses the Window SizeToContent property to automatically size a Window around its contents. For a DialogWindow, use:

DesignSizeToContent

instead. This avoids WPF sizing problems that can occur when a custom Window style and SizeToContent are used together.

Context Menus

Use WLContextMenu from WealthLab.WPF instead of the standard WPF ContextMenu when creating context menus in a WealthLab extension.

<wpf:WLContextMenu>
    ...
</wpf:WLContextMenu>

WLContextMenu includes the integration required to behave consistently with WealthLab Themes.

Dynamically Created UI

UI elements created dynamically at runtime may occasionally need the current Theme explicitly reapplied. The IWLClientHost interface provides:

void RefreshTheme(
    DependencyObject dobj)

Call RefreshTheme to apply the current Theme to the supplied DependencyObject and its descendants. For example:

MyClientHost.RefreshTheme(this);

This can be useful after significantly changing or rebuilding the visual tree of a dynamically generated interface. In many cases, normal DynamicResource bindings update automatically when the Theme changes, so call RefreshTheme only when dynamically created or replaced elements do not pick up the current Theme correctly.