rfmodulator
rfmodulator

Reputation: 3738

How to change WPF Material Design Palette at runtime?

I've recently "upgraded" a solution to a VS2017 and .NET Framework 4.6.1.

I've also updated all of the NuGet packages to their latest versions.

This question likely concerns one or more of the following packages (previous versions are in parenthesizes):

MahApps.Metro by Jan Karger et al. 1.6.5 (1.3.0-ALPHA016)

MaterialDesignColors by James Willock 1.1.3 (1.1.2)

MaterialDesignThemes by James Willock 2.5.0.1205 (1.1.0.234)

MaterialDesignThemes.MahApps by James Willock 0.0.12 (0.0.3)

WPF related packages also being referenced include:

Extended.Wpf.Toolkit by Xceed 3.4.0 (2.6.0)

ControlzEx by Jan Karger et al. 3.0.2.4 (none, new dependency of MahApps.Metro)

Prior to the updates, the following would change the UI color palette at runtime:

private void primaryPaletteComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
   paletteHelper.ReplacePrimaryColor(this.primaryPaletteComboBox.Text);
}

/* Where: */

var paletteHelper = new MaterialDesignThemes.Wpf.PaletteHelper();

this.primaryPaletteComboBox.Items.AddRange(new object[] {
    "Amber",
    "Blue",
    "BlueGrey",
    "Brown",
    "Cyan",
    "DeepOrange",
    "DeepPurple",
    "Green",
    "Grey",
    "Indigo",
    "LightBlue",
    "LightGreen",
    "Lime",
    "Orange",
    "Pink",
    "Purple",
    "Red",
    "Teal",
    "Yellow"});

...Super simple, super easy.

After the updates, ReplacePrimaryColor throws the following exception:

System.InvalidOperationException: 'Unable to safely determine a single resource definition for SecondaryAccentBrush.'

Downgrading these packages to their previous versions cause other problems.

How can I change the color palette at runtime in the most recent version of these packages?

I would like to do this by simply using the color palette name that the user selects from a ComboBox.

Obviously SecondaryAccentBrush is a problem.

Does anyone know what changed in the last few years?

This should be easy, but Google doesn't give me anything useful. Maybe I'm not asking the right question.

PaletteHelper now provides a ReplacePalette(Palette palette) method that looks promising, perhaps there is a way to instantiate a Palette object by using the name of a predefined resource?

I would rather go to the dentist and proctologist at the same time, than to screw around with XAML resource definitions.

Upvotes: 1

Views: 15412

Answers (4)

tinmac
tinmac

Reputation: 2580

You can achieve this using MDIX PaletteHelper.

For Dark/Light

Drop a ToggleButton on your Ui & bind its isChecked bool to the isDark bool used below (Binding or Code behind, however you are wiring your ui up)

    private readonly PaletteHelper _paletteHelper = new PaletteHelper();
    private void ToggleBaseColour(bool isDark)
    {
        ITheme theme = _paletteHelper.GetTheme();
        IBaseTheme baseTheme = isDark ? new MaterialDesignDarkTheme() : (IBaseTheme)new MaterialDesignLightTheme();
        theme.SetBaseTheme(baseTheme);
        _paletteHelper.SetTheme(theme);
    }

For Primary & Accent colours

the MDIX GitHub demo apps PaletteSelector VM has well written & easy to read code here

Upvotes: 6

Paul Pellekoorne
Paul Pellekoorne

Reputation: 1

ITheme has a SetPrimaryColor and SetSecondaryColor method, maybe it wasn't there before. You can also address properties (like primaryLight, secondaryMid, ...) of a theme directly and individually.

private readonly PaletteHelper _paletteHelper = new PaletteHelper();

ITheme theme = _paletteHelper.GetTheme();
theme.SetPrimaryColor(System.Windows.Media.Color.FromRgb(200, 0, 0)); //red
_paletteHelper.SetTheme(theme);

Upvotes: 0

Michael280
Michael280

Reputation: 58

Question is quite old but maybe it will help someone else. I think you are missing some entries in your xaml dictionary (related to Accent colors). Minimum should be similar to following

<Application x:Class="MaterialTest.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Light.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignThemes.Wpf;component/Themes/MaterialDesignTheme.Defaults.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.DeepPurple.xaml" />
                <ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Accent/MaterialDesignColor.Lime.xaml" />

            </ResourceDictionary.MergedDictionaries>            
        </ResourceDictionary>
    </Application.Resources>
</Application>

But this is not enough in your case. You are integrating with MahApp.Metro Please follow this link to find more about integration: https://github.com/MaterialDesignInXAML/MaterialDesignInXamlToolkit/wiki/MahApps.Metro-integration

Seems like you have your themes hardcoded. It is better to get list of available themes directly from library. Then you can use this like that:

SwatchesProvider swatchesProvider = new SwatchesProvider();
List<string> PrimaryColorsList = swatchesProvider.Swatches.Select(a => a.Name).ToList();
this.primaryPaletteComboBox.Items.AddRange(PrimaryColorsList);

private void primaryPaletteComboBox_SelectedIndexChanged(object sender, EventArgs e)
{
    SwatchesProvider swatchesProvider = new SwatchesProvider();
    Swatch color= swatchesProvider.Swatches.FirstOrDefault(a => a.Name == this.primaryPaletteComboBox.Text);
    paletteHelper.ReplacePrimaryColor(color);
}

Upvotes: 1

rfmodulator
rfmodulator

Reputation: 3738

This is my work around, it's not the answer.

So at startup, the default palette is set in App.xaml:

<ResourceDictionary Source="pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.SWATCHNAME.xaml" />

What I'm doing to change it at runtime is:

Uri uri = new Uri($"pack://application:,,,/MaterialDesignColors;component/Themes/Recommended/Primary/MaterialDesignColor.{SWATCHNAME}.xaml");
System.Windows.Application.Current.Resources.MergedDictionaries.RemoveAt(4);
System.Windows.Application.Current.Resources.MergedDictionaries.Insert(4, new ResourceDictionary() { Source = uri });

This does what I want in the sense that I can specify a single color swatch, and not have to specify an accent, or set the Hue indices, in the Palette constructor.

The resource in question is always at index 4, so for now I'm going with it as no keys are defined in App.xaml.

The only other caveat is only some elements are changed at runtime. The app has to be restarted before all of the elements get the new colors, by using the same lines of code in AppStart.cs.

I hope this helps someone else. (note the SWATCHNAME placeholder, and your specific resource index)

If anyone has a better idea, lay it on me.

Upvotes: 2

Related Questions