Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
WPF Theme Manager
using System;
using System.Reflection;
using System.Windows;
public static class ThemeManager
{
#region Fields
private const BindingFlags DefaultStaticFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
private const BindingFlags DefaultInstanceFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
private static readonly Assembly PresentationFramework = typeof(FrameworkElement).Assembly;
private static readonly Type UxThemeWrapper = PresentationFramework.GetType("MS.Win32.UxThemeWrapper");
private static readonly Type SystemResources = PresentationFramework.GetType("System.Windows.SystemResources");
private static readonly MethodInfo SystemResources_InvalidateResources = SystemResources.GetMethod("InvalidateResources", DefaultStaticFlags);
private static readonly MethodInfo SystemResources_OnThemeChanged = SystemResources.GetMethod("OnThemeChanged", DefaultStaticFlags);
private static readonly MethodInfo SystemParameters_InvalidateCache = typeof(SystemParameters).GetMethod("InvalidateCache", DefaultStaticFlags, null, Type.EmptyTypes, null);
private static readonly MethodInfo SystemColors_InvalidateCache = typeof(SystemColors).GetMethod("InvalidateCache", DefaultStaticFlags);
private static readonly FieldInfo UxThemeWrapper_isActive = UxThemeWrapper.GetField("_isActive", DefaultStaticFlags);
private static readonly FieldInfo UxThemeWrapper_themeColor = UxThemeWrapper.GetField("_themeColor", DefaultStaticFlags);
private static readonly FieldInfo UxThemeWrapper_themeName = UxThemeWrapper.GetField("_themeName", DefaultStaticFlags);
#endregion
#region Intercept Theme Change
static ThemeManager()
{
SystemResources.GetMethod("EnsureResourceChangeListener", DefaultStaticFlags).Invoke(null, null);
var hook = Delegate.CreateDelegate(typeof(DependencyObject).Assembly.GetType("MS.Win32.HwndWrapperHook"),
typeof(ThemeManager).GetMethod(nameof(FilterThemeMessage), DefaultStaticFlags));
var notify = SystemResources.GetField("_hwndNotify", DefaultStaticFlags).GetValue(null);
notify = notify.GetType().GetProperty("Value", DefaultInstanceFlags).GetValue(notify, null);
notify.GetType().GetMethod("AddHook", DefaultInstanceFlags).Invoke(notify, new object[] { hook });
}
private static IntPtr FilterThemeMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
if (msg == 0x31a) // WM_THEMECHANGED
{
handled = true;
}
return IntPtr.Zero;
}
#endregion
#region Change Theme
/// <summary>
/// Changes the theme using a compound theme name (theme-name[.theme-color]).
/// </summary>
/// <param name="compoundThemeName">Compound theme name.</param>
public static void ChangeTheme(string compoundThemeName)
{
if (string.IsNullOrEmpty(compoundThemeName))
{
ChangeTheme(string.Empty, string.Empty);
}
else
{
var themeData = compoundThemeName.Split('.');
ChangeTheme(themeData[0], themeData.Length == 1 ? string.Empty : themeData[1]);
}
}
/// <summary>
/// Changes the theme.
/// </summary>
/// <param name="themeName">Name of the theme.</param>
/// <param name="themeColor">Color of the theme.</param>
public static void ChangeTheme(string themeName, string themeColor)
{
SystemColors_InvalidateCache.Invoke(null, null);
SystemParameters_InvalidateCache.Invoke(null, null);
SystemResources_OnThemeChanged.Invoke(null, null);
if (!string.IsNullOrEmpty(themeName))
{
UxThemeWrapper_isActive.SetValue(null, true);
UxThemeWrapper_themeName.SetValue(null, themeName);
UxThemeWrapper_themeColor.SetValue(null, themeColor);
}
SystemResources_InvalidateResources.Invoke(null, new object[] { false });
}
#endregion
}
@aelij

This comment has been minimized.

Copy link
Owner Author

@aelij aelij commented Jul 4, 2016

This overrides the default OS theme. Although it is quite a hack (uses private Reflection) it has several advantages. See this post.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.