Search code examples
wpfmahapps.metrotheming

Switch XAML resources at runtime based on light/dark theme?


I have a WPF application that uses MahApps ("light" mode) and I've now provided a way for the user to switch modes (light/dark). A lot of the app's colours obviously change depending on the mode, as can be seen by comparing the \Styles\Themes\Light.colour_name.xaml & Dark.colour_name.xaml files, but I would like to "extend" these in some way by implementing my own xaml <Color> resources that also change depending on the selected mode. Can this be done?

One example is charts that currently use blue to plot the points - these look fine in "light" mode but are a bit dark in "dark" mode. I realise I could just use a more suitable shade of blue that looks okay in both modes, but it would be more flexible if I could define a Color resource that is (say) 'Blue' in light mode but 'Lime' in dark mode. There are likely to be other uses for such a feature too, e.g. coloured notification messages.


Solution

  • You could create custom themes based on the built-in themes. Create a resource dictionary that merges the resource dictionary of the base theme and override existing brushes and add new ones.

    Light theme dictionary (Light.Blue.Custom.xaml):

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" >
       <ResourceDictionary.MergedDictionaries>
          <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Light.Blue.xaml" />
       </ResourceDictionary.MergedDictionaries>
    
       <!-- Overriding default brushes -->
       <SolidColorBrush x:Key="MahApps.Brushes.Button.Border.MouseOver" Color="Blue" options:Freeze="True" />
    
       <!-- Adding your own brushes -->
       <SolidColorBrush x:Key="My.Brushes.Notification.Alert" Color="Red" options:Freeze="True" />
    
    </ResourceDictionary>
    

    Dark theme dictionary (Dark.Blue.Custom.xaml):

    <ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                        xmlns:options="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" >
       <ResourceDictionary.MergedDictionaries>
          <ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/Dark.Blue.xaml" />
       </ResourceDictionary.MergedDictionaries>
    
       <!-- Overriding default brushes -->
       <SolidColorBrush x:Key="MahApps.Brushes.Button.Border.MouseOver" Color="DarkBlue" options:Freeze="True" />
    
       <!-- Adding your own brushes -->
       <SolidColorBrush x:Key="My.Brushes.Notification.Alert" Color="DarkRed" options:Freeze="True" />
    
    </ResourceDictionary>
    

    Register the theme dictionary in your application. You must replace the dummy name YourApp with the real name of your application in both the class and the resource dictionary URI.

    public partial class YourApp : Application
    {
        protected override void OnStartup(StartupEventArgs e)
        {
            base.OnStartup(e);
    
            var lightBlueCustomTheme = ThemeManager.Current.AddLibraryTheme(new LibraryTheme(
                new Uri("pack://application:,,,/YourApp;component/Light.Blue.Custom.xaml"),
                MahAppsLibraryThemeProvider.DefaultInstance));
    
            var darkBlueCustomTheme = ThemeManager.Current.AddLibraryTheme(new LibraryTheme(
                new Uri("pack://application:,,,/YourApp;component/Dark.Blue.Custom.xaml"),
                MahAppsLibraryThemeProvider.DefaultInstance));
    
            ThemeManager.Current.ChangeTheme(this, lightBlueCustomTheme);
            
            // Optionally enable App Mode theme switching
            // ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.SyncWithAppMode;
            // ThemeManager.Current.SyncTheme();
        }
    }
    

    Note that you need to provide themes for both Light and Dark, if you want to switch modes. For more information about themes and customization, you can refer to the documentation.