MegaMark
MegaMark

Reputation: 610

Dynamically changing theme in wpf with nested resource dictionaries

What I want to accomplish is to allow my users to change the overall color theme of my application by selecting a master color from a list. I have got this working but there was something that I noticed that could possibly make my life easier if I can figure out how to do it. Right now I have a Theme folder that looks like this. Theme folder that holds other folders and generic resource dictionaries used accross the board(i.e. brushes used for backgrounds). The next set of folders down are the RedTheme, BlueTheme folders each of which contain resource dictionaries for individual controls; for example BlueTheme has a resource dictionary for buttons and so does RedTheme. The interesting thing I've noticed is that the resource dictionaries are almost identical with the exception of the color of just one SolidColorBrush. So I'm trying to bind that brush somehow so that I don't wind up having copies upon copies of the same resource dictionaries with just one difference. I will do my best to put in my code, but the nature of it is complicated enough without actually being able to see the different dictionaries. Also I should add that I am nesting dictionaries within nested dictionaries. App.xaml contains whatever dictionary, that contains multiple dictionaries itself, is swapped in or out for theme change AND some generic dictionaries.

ThemeBlue.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="BlueTheme/BlueBrushes.xaml"/>
        <ResourceDictionary Source="BlueTheme/ButtonStyleAndTemplate(normal_blue).xaml"/>
        <ResourceDictionary Source="BlueTheme/LabelStyleAndTemplate(normal_blue).xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

ThemeRed.xaml

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="RedTheme/RedBrushes.xaml"/>
        <ResourceDictionary Source="RedTheme/ButtonStyleAndTemplate(normal_red).xaml"/>
        <ResourceDictionary Source="RedTheme/LabelStyleAndTemplate(normal_red).xaml"/>
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

THIS IS WHERE I WANT THE DYNAMIC CHANGE TO HAPPEN I just want to bind the brush somewhere in the code-behind when a new theme is selected, and pop that brush into the spots I've tried to highlight, so that I only need to generate ONE of these dictionaries instead of one for each color.

Style TargetType="{x:Type mycon:MyButton}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type mycon:MyButton}">
                    <Grid Margin="2">
                        <Ellipse Name="MainCircle" Fill="{DynamicResource ResourceKey=TranslucentBrush}" Stroke="{DynamicResource ResourceKey=***RedBorderBrush***}" />
                        <Ellipse Name="RefractionCircle" Fill="{DynamicResource ResourceKey=ButtonRefractionLayer}"/>
                        <mycon:ButtonTextBlock Margin="0,0,0,8" FontWeight="Black" FontSize="20" Foreground="{DynamicResource ResourceKey=***RedBorderBrush***}" Text="{TemplateBinding Content}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                        <Path x:Name="ReflectionLayer" VerticalAlignment="Top" Stretch="Fill">
                            <Path.RenderTransform>
                                <ScaleTransform ScaleY="0.5" />
                            </Path.RenderTransform>
                            <Path.Data>
                                <PathGeometry>
                                    <PathFigure IsClosed="True" StartPoint="98.999,45.499">
                                        <BezierSegment Point1="98.999,54.170" Point2="89.046,52.258" Point3="85.502,51.029"/>
                                        <BezierSegment IsSmoothJoin="True" Point1="75.860,47.685" Point2="69.111,45.196" Point3="50.167,45.196"/>
                                        <BezierSegment Point1="30.805,45.196" Point2="20.173,47.741" Point3="10.665,51.363"/>
                                        <BezierSegment IsSmoothJoin="True" Point1="7.469,52.580" Point2="1.000,53.252" Point3="1.000,44.999"/>
                                        <BezierSegment Point1="1.000,39.510" Point2="0.884,39.227" Point3="2.519,34.286"/>
                                        <BezierSegment IsSmoothJoin="True" Point1="9.106,14.370" Point2="27.875,0" Point3="50,0"/>
                                        <BezierSegment Point1="72.198,0" Point2="91.018,14.466" Point3="97.546,34.485"/>
                                        <BezierSegment IsSmoothJoin="True" Point1="99.139,39.369" Point2="98.999,40.084" Point3="98.999,45.499"/>
                                    </PathFigure>
                                </PathGeometry>
                            </Path.Data>
                            <Path.Fill>
                                <RadialGradientBrush GradientOrigin="0.498,0.526">
                                    <RadialGradientBrush.RelativeTransform>
                                        <TransformGroup>
                                            <ScaleTransform CenterX="0.5" CenterY="0.5" ScaleX="1" ScaleY="1.997"/>
                                            <TranslateTransform X="0" Y="0.5"/>
                                        </TransformGroup>
                                    </RadialGradientBrush.RelativeTransform>
                                    <GradientStop Offset="1" Color="#99FFFFFF"/>
                                    <GradientStop Offset="0.85" Color="#72FFFFFF"/>
                                    <GradientStop Offset="0" Color="#00000000"/>
                                </RadialGradientBrush>
                            </Path.Fill>
                        </Path>

                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter TargetName="MainCircle" Property="Fill" Value="{DynamicResource ResourceKey=TransparentBrush}"/>
                        </Trigger>
                        <Trigger Property="IsPressed" Value="True">
                            <Setter TargetName="MainCircle" Property="Fill" Value="{DynamicResource ResourceKey=HighlitTranslucentBrush}"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <!-- EndRegion -->
</ResourceDictionary>

-------CODE BEHIND MAIN WINDOW----------

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        SetDefaultStyle();
        populateCboTheme();
    }

    private void populateCboTheme()
    {
        cboTheme.Items.Add("Red");
        cboTheme.Items.Add("Blue");
        cboTheme.Items.Add("Green");
        cboTheme.Items.Add("Yellow");
    }

    private void SetDefaultStyle()
    {
        SetNewStyle("Red");
    }

    private void SetNewStyle(string _color)
    {
        Uri themeUri = new Uri(string.Format("Themes/Theme{0}.xaml",_color),UriKind.Relative);
        ResourceDictionary theme = (ResourceDictionary)Application.LoadComponent(themeUri);
        Resources.MergedDictionaries.Add(theme);
    }

    private void TitleBar_MouseDown(object sender, MouseButtonEventArgs e)
    {
        DragMove();
    }

    private void cboTheme_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        switch (cboTheme.SelectedIndex)
        {
        case 0:
            SetNewStyle("Red");
            break;
        case 1:
            SetNewStyle("Blue");
            break;
        case 2:
            SetNewStyle("Green");
            break;
        case 3:
            SetNewStyle("Yellow");
            break;
        }
    }
}

Upvotes: 2

Views: 3137

Answers (1)

MegaMark
MegaMark

Reputation: 610

Ok so DHN was right... I showed alot of code but did not give alot of explanation of what I was looking for. Since this post I have stumbled across the answer I was looking for. Basically instead of wrapping brushes and controls together in one resource dictionary and swapping it in and out I let the control dictionaries point to a generic named key brush and made different brush dictionaries for each color. Even though this solution still poses the problem of duplicating code... it duplicates FAR less than before.

<solidcolorbrush x:key="RedBorderBrush".../>
is now : <solidcolorbrush x:key="BorderBrush".../>
and the same for all the different colors.
Then I point <... Fill="{dynamicresource resourcekey="BorderBrush"}".
Then when I swap the colors (since all brush dictionaries name their border brush "BorderBrush") the colors change properly.

Upvotes: 1

Related Questions