Skip to content

Advanced theming

SKProCH edited this page Nov 26, 2024 · 5 revisions

Setting the initial theme

There are several entry points for getting started with Material themes for your application. They all rely on the fact that you must add an element to your App.axaml file in the Application.Resources section.

  • MaterialTheme - If you don't plan to change the theme and just want to set the colors you want on startup
  • CustomMaterialTheme - If you would prefer to use custom primary and second colors
  • MaterialThemeBase - If you want to control your theme and load it by yourself

MaterialTheme

Actually this is a wrapper for MaterialThemeBase that helps you to set up your theme with BaseTheme, PrimaryColor, SecondaryColor properties.
You can still use the CurrentTheme property, but this is not the recommended use of this element.

CustomMaterialTheme

This is an analogue of MaterialTheme which allows you to setup custom colors for PrimaryColor and SecondaryColor.
If you need different colors for light and dark theme, the CustomMaterialTheme supports individual resources for light and dark theme.

<themes:CustomMaterialTheme>
  <themes:CustomMaterialTheme.Palettes>
    <themes:CustomMaterialThemeResources x:Key="Dark"
                                PrimaryColor="#4BEB59"
                                SecondaryColor="#04C9F0" />
    <themes:CustomMaterialThemeResources x:Key="Light"
                                PrimaryColor="#29964A"
                                SecondaryColor="#0271A4" />
  </themes:CustomMaterialTheme.Palettes>
</themes:CustomMaterialTheme>

MaterialThemeBase

Entry point of new Material.Avalonia theme and styles system. You can control application theme by setting value of CurrentTheme property.
NOTE: Setting value for CurrentTheme property internally convert your new value to struct that implementing interface ITheme. This is done so that the internal state of the theme object is immutable. Therefore, if you get and use the value of the CurrentTheme property (or subscribing CurrentThemeChanged for example), then you will get a structure.


ITheme manipulation

You can create a new empty Theme object using Theme ctor or use Theme.Create helper method to create prefilled theme:

using Avalonia.Media;
using Material.Colors;
using Material.Styles.Themes;
using Material.Styles.Themes.Base;
...
PrimaryColor primary = PrimaryColor.DeepPurple;
Color primaryColor = SwatchHelper.Lookup[(MaterialColor)primary];

SecondaryColor secondary = SecondaryColor.Teal;
Color secondaryColor = SwatchHelper.Lookup[(MaterialColor)secondary];

// For dark theme use  Theme.Dark;
IBaseTheme baseTheme = Theme.Light;

ITheme theme = Theme.Create(baseTheme, primaryColor, secondaryColor);

If you want you can use any arbitrary colors:

Color primaryColor = global::Avalonia.Media.Colors.Purple;
Color secondaryColor = global::Avalonia.Media.Colors.Lime;

IBaseTheme baseTheme = Theme.Light;

ITheme theme = Theme.Create(baseTheme, primaryColor, secondaryColor);

For changing main primary/accent colors or base mode use SetBaseTheme, SetPrimaryColor and SetSecondaryColor on any ITheme instance.


Theming tips and tricks

Locating your MaterialTheme(Base) instance

You can use ThemeExtensions.LocateMaterialTheme extensions method to quickly locating you MaterialTheme (or MaterialThemeBase) instances in your app. Sample usage:

var materialTheme = Application.Current.LocateMaterialTheme<MaterialTheme>();

Applying your theme at startup

If you want to setup theme by hands then application is starting (for example to allow user to change and save themes) you can use the following approach:
Open your App.axaml.cs and in your App class override OnFrameworkInitializationCompleted method.
Locate MaterialTheme (or MaterialThemeBase) and do what you want with the theme.
For example, I will create Dark theme with Cyan as primary color and Orange secondary color and set it for my application:

public override void OnFrameworkInitializationCompleted() {
    base.OnFrameworkInitializationCompleted();
    
    var primary = PrimaryColor.Cyan;
    var primaryColor = SwatchHelper.Lookup[(MaterialColor)primary];

    var secondary = SecondaryColor.Orange;
    var secondaryColor = SwatchHelper.Lookup[(MaterialColor)secondary];
    
    var theme = Theme.Create(Theme.Dark, primaryColor, secondaryColor);
    var themeBootstrap = this.LocateMaterialTheme<MaterialThemeBase>();
    themeBootstrap.CurrentTheme = theme;
}

NOTE: If you always set theme from code use MaterialThemeBase in your Application.Styles. Because MaterialTheme will create an additional overhead to apply the original styles (written in xaml), which you will override anyway.

Also, you can react to a CurrentTheme change (for example, to save a new theme in the configuration) via subscribing to CurrentThemeChanged (or subscribing to CurrentThemeProperty changing via default Avalonia properties system).
Here is the example of saving and loading theme to a file (not a preferred way to save/load theme, but just an example):

public override void OnFrameworkInitializationCompleted() {
    base.OnFrameworkInitializationCompleted();

    var themeBootstrap = this.LocateMaterialTheme<MaterialThemeBase>();
    themeBootstrap.CurrentTheme = LoadOrCreateDefaultTheme();

    themeBootstrap.CurrentThemeChanged.Subscribe(newTheme => {
        var configText = JsonConvert.SerializeObject(newTheme);
        File.WriteAllText("themeconfig.json", configText);
    });
}

private ITheme LoadOrCreateDefaultTheme() {
    try {
        var text = File.ReadAllText("themeconfig.json");
        return JsonConvert.DeserializeObject<Theme>(text);
    }
    catch (Exception) {
        // In case of any exception or file missing, etc
        // Fallback to creating default theme
        var primary = PrimaryColor.Cyan;
        var primaryColor = SwatchHelper.Lookup[(MaterialColor)primary];

        var secondary = SecondaryColor.Orange;
        var secondaryColor = SwatchHelper.Lookup[(MaterialColor)secondary];

        return Theme.Create(Theme.Dark, primaryColor, secondaryColor);
    }
}

Overriding colors

You can set the colors you want directly on the ITheme object, which you then use to set the value of CurrentTheme in MaterialThemeBase. But often this is an overhead, and there is an easier way to override the desired color.
You can add a new resource to end of Application.Resources in your App.axaml like this:

<Application.Styles>
  ...
  <themes:MaterialTheme BaseTheme="Light" PrimaryColor="Purple" SecondaryColor="Lime" />
<Application.Resources>
  <SolidColorBrush x:Key="PrimaryHueLightBrush" Color="Red"></SolidColorBrush>
</Application.Resources>

Or you can create and use a new style and put in after MaterialTheme in Application.Styles section:

<Application.Styles>
  ...
  <themes:MaterialTheme BaseTheme="Light" PrimaryColor="Purple" SecondaryColor="Lime" />
  <Style>
    <Style.Resources>
      <SolidColorBrush x:Key="PrimaryHueLightBrush" Color="Red"></SolidColorBrush>
    </Style.Resources>
  </Style>
</Application.Styles>

List of brushes which Material.Avalonia uses located here.


Migrating 2.5 -> 3.0

Actually there is no breaking changes in this update. We make many things obsoleted, but it should work fine with the new version. Except:

  1. If you use anything related to MaterialDesignColor.*.xaml files, Swatch and Hue classes you need to rework your system, cuz these things removed.
  2. If you use SecondaryAccentBrush, SecondaryAccentForegroundBrush, ValidationErrorColor you need to switch to proper brushed defined at wiki.

If no there is migration tips:

  1. If you use only BundledTheme before (and do not manipulate theme by yourself) then this has backward compability 100%.
  2. If you use IThemeManager, ThemeManager, PaletteHelper, ResourceDictionaryExtensions.SetTheme, ResourceDictionaryExtensions.GetTheme, ResourceDictionaryExtensions.GetThemeManager, these things should work fine but consider switching to MaterialThemeBase and proper theme manipulating.

Old versions

v2.5 and before

The first step is to set the initial theme. There are two recommended ways to accomplish this:

  1. Use built-in markup extension (BundledTheme). These return a new ResourceDictionary instance with all of the brushes setup for you.
  2. Create a new ITheme object and use ResourceDictionaryExtensions.SetTheme(Application.Current.Resources). You can create a new Theme object using:
using Avalonia.Media;
using Material.Colors;
using Material.Styles.Themes;
using Material.Styles.Themes.Base;
...
PrimaryColor primary = PrimaryColor.DeepPurple;
Color primaryColor = SwatchHelper.Lookup[(MaterialColor)primary];

SecondaryColor secondary = SecondaryColor.Teal;
Color secondaryColor = SwatchHelper.Lookup[(MaterialColor)secondary];

IBaseTheme baseTheme = Theme.Light;
//If you want a dark theme you can use IBaseTheme baseTheme = Theme.Dark;

ITheme theme = Theme.Create(baseTheme, primaryColor, secondaryColor);

If you want you can use any arbitrary colors.

using Avalonia.Media;
using Material.Styles.Themes;
using Material.Styles.Themes.Base;
...
Color primaryColor = global::Avalonia.Media.Colors.Purple;
Color secondaryColor = global::Avalonia.Media.Colors.Lime;

IBaseTheme baseTheme = Theme.Light;
//If you want a dark theme you can use IBaseTheme baseTheme = Theme.Dark;

ITheme theme = Theme.Create(baseTheme, primaryColor, secondaryColor);

Changing the theme

Prior to version 2.6.0 the PaletteHelper class provided methods for modifying the theme. These methods are now obsolete in favor of the new GetTheme and SetTheme methods on PaletteHelper. To change the theme simply get the existing ITheme, modify this theme object, and then simply set it with PaletteHelper.SetTheme. Similar to this:

var paletteHelper = new PaletteHelper();
//Retrieve the app's existing theme
ITheme theme = paletteHelper.GetTheme();

//Change the base theme to Dark
theme.SetBaseTheme(BaseThemeMode.Dark.GetBaseTheme());
//or theme.SetBaseTheme(BaseThemeMode.Light.GetBaseTheme());

//Change all of the primary colors to Red
theme.SetPrimaryColor(Colors.Red);

//Change all of the secondary colors to Blue
theme.SetSecondaryColor(Colors.Blue);

//You can also change a single color on the theme, and optionally set the corresponding foreground color
theme.PrimaryMid = new ColorPair(Colors.Brown, Colors.White);

//Change the app's current theme
paletteHelper.SetTheme(theme);

Material.Avalonia Wiki pages is still WIP (work in progress), Pages could not ready for show if they unclickable.

Main section

Widgets / Controls

Theming

Builders

Assist for widgets

Clone this wiki locally