c# – How do I allow the user to set the form + control styles in WPF applications?

I’m just starting to make the transition from vb.net and winforms to c# and WPF and starting on my first app. I’ve done a lot of tutorials and now it’s time to try and put some things into practice.

My first challenge is theming of my application. I want to present my user with a choice of themes which will have preset color schemes for each control on my forms. For example a Light and Dark mode. I’ve managed some form theming by putting styling in App.xaml (the app will have multiple forms, so I understand it will need to be here?). Some of it had to be complicated for the drop down main menu (I lifted this code – waaay over my WPF knowldege at the moment):

    <Application.Resources>
        
        <Style TargetType="Window">
            <Setter Property="Background" Value="#303030"/>
        </Style> 

        <Style TargetType="Menu">
            <Setter Property="Background" Value="#202020"/>
            <Setter Property="Foreground" Value="Gainsboro"></Setter>
            <Setter Property="Padding" Value="0,4,0,4"></Setter>
        </Style>

        <Style TargetType="{x:Type MenuItem}">
            <Style.Triggers>
                <Trigger Property="MenuItem.Role" Value="TopLevelHeader">
                    <Setter Property="Control.Template">
                        <Setter.Value>
                            <ControlTemplate TargetType="{x:Type MenuItem}">
                                <Border x:Name="templateRoot" SnapsToDevicePixels="true"
                                        BorderThickness="{TemplateBinding Control.BorderThickness}"
                                        Background="{TemplateBinding Control.Background}"
                                        BorderBrush="{TemplateBinding Control.BorderBrush}">
                                    <Grid VerticalAlignment="Center">
                                        <Grid.ColumnDefinitions>
                                            <ColumnDefinition Width="Auto"/>
                                            <ColumnDefinition Width="Auto"/>
                                        </Grid.ColumnDefinitions>
                                        <ContentPresenter x:Name="Icon" ContentSource="Icon" 
                                                          SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" VerticalAlignment="Center"
                                                          HorizontalAlignment="Center" Width="16" Height="16" Margin="3"/>
                                        <Path x:Name="GlyphPanel" Data="F1 M 10.0,1.2 L 4.7,9.1 L 4.5,9.1 L 0,5.2 L 1.3,3.5 L 4.3,6.1L 8.3,0 L 10.0,1.2 Z" FlowDirection="LeftToRight" Margin="3"
                                              Visibility="Collapsed" VerticalAlignment="Center" Fill="{TemplateBinding Control.Foreground}"/>
                                        <ContentPresenter Grid.Column="1" ContentSource="Header" RecognizesAccessKey="true"
                                                          Margin="{TemplateBinding Control.Padding}"
                                                          SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
                                        <Popup x:Name="PART_Popup" AllowsTransparency="true" Focusable="false"
                                               PopupAnimation="{DynamicResource {x:Static SystemParameters.MenuPopupAnimationKey}}"
                                               Placement="Bottom"
                                               IsOpen="{Binding IsSubmenuOpen, RelativeSource={RelativeSource TemplatedParent}}"
                                               PlacementTarget="{Binding ElementName=templateRoot}">
                                            <Border x:Name="SubMenuBorder" Background="#202020" BorderBrush="#202020" BorderThickness="1" Padding="2">
                                                <ScrollViewer x:Name="SubMenuScrollViewer"
                                                             Style="{DynamicResource {ComponentResourceKey ResourceId=MenuScrollViewer, TypeInTargetAssembly={x:Type FrameworkElement}}}">
                                                    <Grid RenderOptions.ClearTypeHint="Enabled">
                                                        <Canvas Height="0" Width="0" HorizontalAlignment="Left" VerticalAlignment="Top">
                                                            <Rectangle Name="OpaqueRect" Height="{Binding ElementName=SubMenuBorder, Path=ActualHeight}"
                                                                         Width="{Binding ElementName=SubMenuBorder, Path=ActualWidth}"
                                                                         Fill="{Binding ElementName=SubMenuBorder, Path=Background}"/>
                                                        </Canvas>
                                                        <!-- Icon separator color: -->
                                                        <Rectangle HorizontalAlignment="Left" Width="1" Margin="29,2,0,2" Fill="#202020"/>
                                                        <ItemsPresenter x:Name="ItemsPresenter" KeyboardNavigation.DirectionalNavigation="Cycle"
                                                                        KeyboardNavigation.TabNavigation="Cycle" Grid.IsSharedSizeScope="true"
                                                                        SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}"/>
                                                    </Grid>
                                                </ScrollViewer>
                                            </Border>
                                        </Popup>
                                    </Grid>
                                </Border>
                                <ControlTemplate.Triggers>
                                    <Trigger Property="MenuItem.IsSuspendingPopupAnimation" Value="true">
                                        <Setter TargetName="PART_Popup" Property="Popup.PopupAnimation" Value="None"/>
                                    </Trigger>
                                    <Trigger Value="{x:Null}" Property="MenuItem.Icon">
                                        <Setter TargetName="Icon" Property="UIElement.Visibility" Value="Collapsed"/>
                                    </Trigger>
                                    <Trigger Property="MenuItem.IsChecked" Value="true">
                                        <Setter TargetName="GlyphPanel" Property="UIElement.Visibility" Value="Visible"/>
                                        <Setter TargetName="Icon" Property="UIElement.Visibility" Value="Collapsed"/>
                                    </Trigger>
                                    <Trigger Property="MenuItem.IsHighlighted" Value="true">
                                        <Setter TargetName="templateRoot" Value="#3D0767B8" Property="Border.Background"/>
                                        <Setter TargetName="templateRoot" Value="#FF0767B8" Property="Border.BorderBrush"/>
                                    </Trigger>
                                    <Trigger Property="UIElement.IsEnabled" Value="false">
                                        <Setter TargetName="templateRoot" Value="#FF707070" Property="TextElement.Foreground"/>
                                        <Setter TargetName="GlyphPanel" Value="#FF707070" Property="Shape.Fill"/>
                                    </Trigger>
                                    <Trigger SourceName="SubMenuScrollViewer" Property="ScrollViewer.CanContentScroll" Value="false">
                                        <Setter TargetName="OpaqueRect" Value="{Binding ElementName=SubMenuScrollViewer, Path=VerticalOffset}"
                                                Property="Canvas.Top"/>
                                        <Setter TargetName="OpaqueRect" Value="{Binding ElementName=SubMenuScrollViewer, Path=HorizontalOffset}"
                                                Property="Canvas.Left"/>
                                    </Trigger>
                                </ControlTemplate.Triggers>
                            </ControlTemplate>
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </Style.Triggers>
        </Style>
   
    
    
    </Application.Resources>

This, of course, sets Styles at compile time, but I need Styles set dynamically and repeatedly at runtime.

However, I’d like to have it so that the user selects a theme from a drop-down in a settings menu and all forms are displayed with the new theme. Preferably in real time, but “restart required” would be permissible.

What would be the best way to achieve this? Conceptually, it would almost be like swapping in different CSS style sheets.

Leave a Comment