5

I am trying to set up a theme framework in a large WPF application. Currently the solution we have found is to create separate .xaml files for each color palette like so:

<LightColors.xaml>
<Color x:Key="MainPanelColor">Aqua</Color>
<Color x:Key="MainItemColor">Orange</Color>
<SolidColorBrush x:Key="MainPanelBrush" Color="{StaticResource MainPanelColor}" />
<SolidColorBrush x:Key="MainItemBrush"  Color="{StaticResource MainItemColor}" />

The UI then references these items like so:

<Textblock Foreground="{DynamicResource MainItemBrush}"/>

The color palettes are changed during runtime in C#. This theme framework accomplishes the task of allowing themes to be changed during runtime.

Problem: I want to create a layer between the UI and the colors so that the palette colors can be linked to a large list of color definitions used throughout the UI. The only solution I have found that close to works is adding a file like this:

<ColorDefinitions.xaml>
<DynamicResource x:Key="Textblock_SetupPage_Foreground" ResourceKey="MainItemBrush" />
<DynamicResource x:Key="SecondDefinition" ResourceKey="MainItemBrush" />

And referencing this new resource in the UI like this:

<Textblock Foreground="{StaticResource Textblock_SetupPage_Foreground}" />

This solution does not full work, though. It only allows a single UI element to use one of the DynamicResources like "Textblock_SetupPage_Foreground", and changing the Textblock reference to DynamicResource produces an error. How can I accomplish this task?

Z Lang
  • 73
  • 3

1 Answers1

2

Not quite sure if this solves your problem, but I can show you how I implement skinning in our LOBs. The example solution consists of two assemblies and the sample application.

MyCustomControlLibrary defines the colors and brushes and a example custom control. Colors and brushes could be further separated to an extra assembly.

MySkinsLibrary defines the skins and consumes the definitions (resources) from MyControlLibrary.

WpfSkinTestApp consumes the skins and consumes indirectly the MyCostumControlLibrary.

WPFSkinning Test Solution

Colors.xaml:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:MyCustomControlLibrary">
<Color x:Key="MyDarkColor">#FF123456</Color>
<Color x:Key="MyLightColor">#FF456789</Color>
<Color x:Key="MyNeutralColor">#FF666666</Color>

Brushes.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:MyCustomControlLibrary">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/MyCustomControlLibrary;component/ResourceDictionaries/Colors.xaml"/>
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="MyDarkColorBrush" Color="{StaticResource MyDarkColor}"/>
<SolidColorBrush x:Key="MyLightColorBrush" Color="{StaticResource MyLightColor}"/>
<SolidColorBrush x:Key="MyNeutralColorBrush" Color="{StaticResource MyNeutralColor}"/>

Generic.xaml

<ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyCustomControlLibrary">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/MyCustomControlLibrary;component/ResourceDictionaries/Brushes.xaml"/>
</ResourceDictionary.MergedDictionaries>
<Style TargetType="{x:Type local:MyCustomControl}">
    <Setter Property="Background" Value="{StaticResource MyNeutralColorBrush}"/>
    <Setter Property="BorderBrush" Value="White"/>
    <Setter Property="BorderThickness" Value="6"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type local:MyCustomControl}">
                <Border Background="{TemplateBinding Background}"
                        BorderBrush="{TemplateBinding BorderBrush}"
                        BorderThickness="{TemplateBinding BorderThickness}">

                </Border>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

DarkSkin.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:MySkinsLibrary.Skins"
                xmlns:customControls="clr-namespace:MyCustomControlLibrary;assembly=MyCustomControlLibrary">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/MyCustomControlLibrary;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>

<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
    <Setter Property="Background" Value="Gray"/>
    <Setter Property="Foreground" Value="{StaticResource MyDarkColorBrush}"/>
</Style>

<Style x:Key="MyCustomControlStyle" TargetType="{x:Type customControls:MyCustomControl}" BasedOn="{StaticResource {x:Type customControls:MyCustomControl}}">
    <Setter Property="Background" Value="{StaticResource MyDarkColorBrush}"/>
</Style>

LightSkin.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:local="clr-namespace:MySkinsLibrary.Skins"
                xmlns:customControls="clr-namespace:MyCustomControlLibrary;assembly=MyCustomControlLibrary">
<ResourceDictionary.MergedDictionaries>
    <ResourceDictionary Source="/MyCustomControlLibrary;component/Themes/Generic.xaml"/>
</ResourceDictionary.MergedDictionaries>

<Style x:Key="MyTextBlockStyle" TargetType="TextBlock">
    <Setter Property="Background" Value="Gray"/>
    <Setter Property="Foreground" Value="{StaticResource MyLightColorBrush}"/>
</Style>

<Style x:Key="MyCustomControlStyle" TargetType="{x:Type customControls:MyCustomControl}" BasedOn="{StaticResource {x:Type customControls:MyCustomControl}}">
    <Setter Property="Background" Value="{StaticResource MyLightColorBrush}"/>
</Style>

In your application you can use the different skins in the app.xaml, like this: (for design purpose)

<Application x:Class="WpfSkinTestApp.App"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:local="clr-namespace:WpfSkinTestApp"
         StartupUri="MainWindow.xaml">
<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>
            <ResourceDictionary Source="pack://application:,,,/MySkinsLibrary;component/Skins/LightSkin.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

and changing it during runtime e.g. like this:

private void RadioButton_Checked(object sender, RoutedEventArgs e)
    {
        string skinName = "LightSkin";

        if (((RadioButton)sender).Name == "DarkSkin")
        {
            skinName = "DarkSkin";
        }

        ResourceDictionary resources = new ResourceDictionary();
        resources.Source = new Uri($"pack://application:,,,/MySkinsLibrary;component/Skins/{skinName}.xaml");
        Application.Current.Resources = resources;
    }

For completness, this is the main window of the test application:

<Window x:Class="WpfSkinTestApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:customControls="clr-namespace:MyCustomControlLibrary;assembly=MyCustomControlLibrary"
    xmlns:local="clr-namespace:WpfSkinTestApp"
    mc:Ignorable="d"
    Title="MainWindow" Height="350" Width="525"
    Background="{StaticResource MyNeutralColorBrush}">
<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition/>
        <ColumnDefinition/>
        <ColumnDefinition/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition/>
        <RowDefinition Height="Auto"/>
        <RowDefinition/>
        <RowDefinition/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Column="1"
               Grid.Row="1"
               Text="Example Colors"
               Style="{DynamicResource MyTextBlockStyle}"/>
    <customControls:MyCustomControl Grid.Column="1"
                                    Grid.Row="2"
                                    Style="{DynamicResource MyCustomControlStyle}"/>
    <StackPanel Grid.Column="2"
                Grid.Row="2"
                Margin="24"                    >
        <RadioButton x:Name="LightSkin" GroupName="1" Content="Light Skin" IsChecked="True" Checked="RadioButton_Checked"/>
        <RadioButton x:Name="DarkSkin" GroupName="1" Content="Dark Skin" Checked="RadioButton_Checked"/>
    </StackPanel>
</Grid>

Let me know, if this is what you´re looking for.

Todd Nedd
  • 51
  • 1
  • Thank you for the effort put into this answer. Your solution is similar to what I ended up using: redefining resources in each skin resourcedictionary. I redefine resources like Button_Base_BorderDisabled for colors and brushes, and you redefine styles like MyTextBlockStyle. What I'm hoping still exists... would be a way to link colors into one alias. For example, create an alias for MyDarkColor, MyLightColor, and MyNeutralColor like MainColor, and only use the MainColor reference throughout the UI, but have it dynamically changeable. – Z Lang Oct 29 '18 at 22:23