Please Note: All code surrounded by
[ ]
needs to be replaced with the correct value by you.
The ThemeManager
provides several options to provide themes for your application or control library. For example MahApps.Metro and Fluent.Ribbon use it to provide different themes to the user.
In the following sections the usage will be described by the sample usage at MahApps.Metro.
You can provide built in themes in your application or library by running the XamlColorSchemeGenerator while building or via command line. For detailed information about the usage, please visit the XamlColorSchemeGenerator site.
Please note: It is not recommended to use this approach as you might miss the addition of new required resources when new versions of libraries are released.
You may also provide a custom ResourceDictionary
with all the needed resources.
- Get yourself a copy of the required
Template
- Replace all items surrounded by double curly braces
{{ Content to Replace }}
- Remember to create all needed variants. E.g.: If you want to support light and dark themes you will need to implement both.
First of all you need to add the following namespace in your using section:
using ControlzEx.Theming;
now you can apply the Theme
of your choice to your App
, to a specific Window
or any FrameworkElement
. Just call this line in your code:
ThemeManager.Current.ChangeTheme([Affected item], "[Name of the theme]");
Where [Affected item]
may be:
Application.Current
this
- any
Window
- any
Control
- any
FrameworkElement
and [Name of the theme]
is any valid theme name, e.g. Dark.Blue
in MahApps.Metro.
The ThemeManager
can be called from different locations, for example:
- Inside the constructor of your
App.xaml.cs
- Inside the constructor of your
Window
- In a
Click-Event
of aButton
- In your
ViewModel
Example
If you want, for example, set your application theme to Dark.Red
, when the user clicks a button called "ButtonChangeTheme" you can do this:
private void ButtonChangeTheme_Click(object sender, RoutedEventArgs e)
{
ThemeManager.Current.ChangeTheme(App.Current, "Dark.Red");
}
It is also possible to sync your Theme
with the users Windows theme. The first step is again to add the following namespace in your using section:
using ControlzEx.Theming;
Now you need to define if you want to sync the BaseColor
, the Accent
or both:
ThemeManager.Current.ThemeSyncMode = ThemeSyncMode.[DoNotSync|SyncAll|SyncWithAccent|SyncWithAppMode|SyncWithHighContrast];
The table below explains the available ThemeSyncModes
:
ThemeSyncMode | Description |
---|---|
DoNotSync | No sync at all |
SyncAll | Gets or sets whether changes to the "app mode", accent color and high contrast setting from windows should be detected at runtime and the current Theme be changed accordingly. |
SyncWithAppMode | Gets or sets whether changes to the "app mode" setting from windows should be detected at runtime and the current Theme be changed accordingly. |
SyncWithAccent | Gets or sets whether changes to the accent color settings from windows should be detected at runtime and the current Theme be changed accordingly. |
SyncWithHighContrast | Gets or sets whether changes to the high contrast setting from windows should be detected at runtime and the current Theme be changed accordingly. |
Explanation: "app mode" is the windows terminology for base color like "dark" or "light".
Please note: Once you enable theme sync you should not change the theme manually as manual changes would be overwritten as soon as some event occurs that triggers a theme sync. Such events could be triggered by various things like changes to display settings, connecting/disconnecting via RDP etc..
To actually sync the Theme
call this line:
ThemeManager.Current.SyncTheme();
Note: If you want to sync just a part of the
Theme
and use a customTheme
make sure that theTheme
is correctly added to theThemeManager
.
First of all you need to add the following namespace in your using section:
using ControlzEx.Theming;
-
Optional: If you wish to use
HSL
to create theAccentColors
instead of semi-transparent colors, you can set this in the options:RuntimeThemeGenerator.Current.Options.UseHSL = [true/false];
-
Now you can create a new
Theme
by providing theColor
you wish:Theme newTheme = new Theme("[Name of the generated theme]", "[DisplayName of the generated theme]", [ThemeManager.BaseColorLight or ThemeManager.BaseColorDark], "[AccentName]", [AccentColor], new SolidColorBrush(AccentColor), true, [IsHighContast: false/true]);
Adjust all parameters surrounded by
[]
in the above snipped to your needs. -
Optional: Add or override any
Resources
in the newTheme
if needed, e.g.:newTheme.Resources["MahApps.Colors.Highlight"] = HighlightColor; newTheme.Resources["MahApps.Brushes.Highlight"] = new SolidColorBrush(HighlightColor);
-
Optional: Add the
Theme
to the currentThemeMangers
collection:ThemeManager.Current.AddTheme(newTheme);
-
Apply new
Theme
ThemeManager.Current.ChangeTheme([Affected item], newTheme);
Where
[Affected item]
may be:Application.Current
this
- any
Window
- any
Control
- any
FrameworkElement
If you want to have even more control about the theme generation you can override the LibraryThemeProvider
with your own provider. In the following sample we will show how to do this for MahApps.Metro.
-
Create a new
class
which derives from aLibaryThemeProvider
, in our caseMahAppsLibraryThemeProvider
public class MyLibraryThemeProvider : MahAppsLibraryThemeProvider
-
Override the
static DefaultInstance
public static new readonly MyLibraryThemeProvider DefaultInstance = new MyLibraryThemeProvider();
-
Override the Methods you like (most likely you want to override this):
public override void FillColorSchemeValues(Dictionary<string, string> values, RuntimeThemeColorValues colorValues) { // Check if all needed parameters are not null if (values is null) throw new ArgumentNullException(nameof(values)); if (colorValues is null) throw new ArgumentNullException(nameof(colorValues)); // Add the values you like to override values.Add("MahApps.Colors.AccentBase", "[AccentBaseColor]"); values.Add("MahApps.Colors.Accent", "[AccentColor]"); values.Add("MahApps.Colors.Accent2", "[AccentColor2]"); values.Add("MahApps.Colors.Accent3", "[AccentColor3]"); values.Add("MahApps.Colors.Accent4", "[AccentColor4]"); values.Add("MahApps.Colors.Highlight", "[HighlightColor]"); values.Add("MahApps.Colors.IdealForeground", colorValues.IdealForegroundColor.ToString(CultureInfo.InvariantCulture)); }
-
Register your provider to your
App
or inGeneric.xaml
of your controls librarya. add the following namespace:
xmlns:theming="clr-namespace:BIA_Controls.Theming"
b. add this line in the
Resources
section:<theming:MyLibraryThemeProvider x:Key="{x:Static theming:MyLibraryThemeProvider.DefaultInstance}" />
Hints for resource lookup: The default implementation will look for resources that contain
/themes/
in their resource path. If you want to change that behavior you can overrideIsPotentialThemeResourceDictionary
or if you want to completely change how library themes are resolved for your provider you can overrideGetLibraryTheme
.
- If the user selects the
Light
theme, the accent colors have the same appearance as the transparent ones, but they are solid. - If the user selects the
Dark
theme, the accent colors are a bit brighter than in the original implementation. - The
HighlightColor
is calculated differently - The gray shades are calculated to be equally distributed from black to white
public class MyLibraryThemeProvider : MahAppsLibraryThemeProvider
{
/// <inheritdoc/>
public static new readonly MyLibraryThemeProvider DefaultInstance = new MyLibraryThemeProvider();
public override void FillColorSchemeValues(Dictionary<string, string> values, RuntimeThemeColorValues colorValues)
{
// Check if all needed parameters are not null
if (values is null) throw new ArgumentNullException(nameof(values));
if (colorValues is null) throw new ArgumentNullException(nameof(colorValues));
bool isDarkMode = colorValues.Options.BaseColorScheme.Name == ThemeManager.BaseColorDark;
Color baseColor = (Color)ColorConverter.ConvertFromString(colorValues.Options.BaseColorScheme.Values["MahApps.Colors.ThemeBackground"]);
Color accent = colorValues.AccentBaseColor;
double factor = isDarkMode ? 0.1 : 0.2;
// Add the values you like to override
values.Add("MahApps.Colors.AccentBase", accent.ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.Accent", AddColor(accent, baseColor, factor * 1).ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.Accent2", AddColor(accent, baseColor, factor * 2).ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.Accent3", AddColor(accent, baseColor, factor * 3).ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.Accent4", AddColor(accent, baseColor, factor * 4).ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.Highlight", AddColor(accent, isDarkMode ? Colors.White : Colors.Black, 0.8).ToString(CultureInfo.InvariantCulture));
values.Add("MahApps.Colors.IdealForeground", colorValues.IdealForegroundColor.ToString(CultureInfo.InvariantCulture));
// Gray Colors
for (int i = 1; i <= 10; i++)
{
values.Add($"MahApps.Colors.Gray{i}", GetShadedGray(i / 11d, isDarkMode).ToString(CultureInfo.InvariantCulture));
}
}
private static Color GetShadedGray(double percentage, bool inverse = false)
{
if (inverse)
{
percentage = 1 - percentage;
}
return Color.FromRgb((byte)(percentage * 255), (byte)(percentage * 255), (byte)(percentage * 255));
}
private static Color AddColor(Color baseColor, Color colorToAdd, double? factor)
{
byte firstColorAlpha = baseColor.A;
byte secondColorAlpha = factor.HasValue ? (byte)(factor * 255) : colorToAdd.A;
byte alpha = CompositeAlpha(firstColorAlpha, secondColorAlpha);
byte r = CompositeColorComponent(baseColor.R, firstColorAlpha, colorToAdd.R, secondColorAlpha, alpha);
byte g = CompositeColorComponent(baseColor.G, firstColorAlpha, colorToAdd.G, secondColorAlpha, alpha);
byte b = CompositeColorComponent(baseColor.B, firstColorAlpha, colorToAdd.B, secondColorAlpha, alpha);
return Color.FromArgb(255, r, g, b);
}
/// <summary>
/// For a single R/G/B component. a = precomputed CompositeAlpha(a1, a2)
/// </summary>
private static byte CompositeColorComponent(byte c1, byte a1, byte c2, byte a2, byte a)
{
// Handle the singular case of both layers fully transparent.
if (a == 0)
{
return 0;
}
return System.Convert.ToByte((((255 * c2 * a2) + (c1 * a1 * (255 - a2))) / a) / 255);
}
private static byte CompositeAlpha(byte a1, byte a2)
{
return System.Convert.ToByte(255 - ((255 - a2) * (255 - a1)) / 255);
}
}
You can get the current applied theme of the entire App
, a single Window
or any FrameworkElement
. To do so import this namespace to your usings:
using ControlzEx.Theming;
Now you can call this statement to get the current theme:
Theme currentTheme = ThemeManager.Current.DetectTheme([ThemedItem]);
where ThemedItem
may be:
Application.Current
this
- any
Window
- any
Control
- any
FrameworkElement