using System.Reflection; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using AntDesignWPF.Extensions; namespace AntDesignWPF.Appearance; /// /// 允许通过交换包含动态资源的资源字典来管理应用程序主题。 /// /// /// ThemeManager.ChangeAppTheme( ThemeMode.Light ); if /// (ThemeManager.GetAppTheme() == ThemeMode.Dark) { ThemeManager.ChangeAppTheme( /// ThemeMode.Light ); } ThemeManager.Changed += (theme, accent) => { /// Debug.WriteLine($"Application theme changed to {theme.ToString()}"); }; /// public class ThemeManager { internal static string LibraryNamespace => Assembly.GetExecutingAssembly().GetName().Name; public static string ThemesDictionaryPath => $"pack://application:,,,/{LibraryNamespace};component/Themes/"; private static ThemeMode _activeThemeMode = ThemeMode.Light; private static ThemePalette _activeThemePalette = ThemePalette.DaybreakBlue; public static event Action? ThemeModeChanged; public static event Action? ThemePaletteChanged; #region Animation private static Task AnimateBrushColorAsync(SolidColorBrush brush, Color toColor, int durationMs = 500) { if (brush.IsFrozen) return Task.CompletedTask; // 冻结的Brush无法动画,直接返回完成的Task var tcs = new TaskCompletionSource(); var anim = new ColorAnimation { To = toColor, Duration = TimeSpan.FromMilliseconds(durationMs), EasingFunction = new CubicEase { EasingMode = EasingMode.EaseInOut }, FillBehavior = FillBehavior.HoldEnd }; // 在动画完成时标记任务完成 anim.Completed += (s, e) => tcs.SetResult(true); brush.BeginAnimation(SolidColorBrush.ColorProperty, anim); return tcs.Task; } /// /// 动画过渡 /// /// public void ChangeTheme(Uri themeUri) { // 1. 加载目标主题的资源字典,但先不合并 ResourceDictionary newTheme = new ResourceDictionary { Source = themeUri }; // 2. 遍历新主题中的画刷资源 foreach (var key in newTheme.Keys) { // 只处理 SolidColorBrush if (newTheme[key] is SolidColorBrush newBrush) { // 3. 在当前的应用程序资源中找到同名的画刷 var currentBrush = Application.Current.Resources[key] as SolidColorBrush; if (currentBrush != null) { // 4. 创建颜色动画 var colorAnimation = new ColorAnimation { From = currentBrush.Color, To = newBrush.Color, Duration = new Duration(TimeSpan.FromMilliseconds(300)), // 动画时长 EasingFunction = new QuarticEase { EasingMode = EasingMode.EaseInOut } // 缓动函数,使动画更自然 }; // 5. 对当前画刷的 Color 属性应用动画 currentBrush.BeginAnimation(SolidColorBrush.ColorProperty, colorAnimation); } } } } #endregion /// /// 将资源应用于 . /// public static void ApplyResourcesToElement(FrameworkElement frameworkElement) { if (frameworkElement.Resources.MergedDictionaries.Count < ThemeManager.Current.Resources.MergedDictionaries.Count) { foreach (var dictionary in ThemeManager.Current.Resources.MergedDictionaries) { frameworkElement.Resources.MergedDictionaries.Add(dictionary); } } } public static void SwitchThemePalette(ThemePalette themePalette) { if (themePalette == ThemePalette.DaybreakBlue) { return; } } /// /// 更改当前应用程序主题。 /// /// 要设置的主题 /// 是否应用自定义背景效果。 /// 是否应更改颜色强调。 public static void SwitchThemeMode(ThemeMode themeMode) { if (themeMode == _activeThemeMode) { return; } ResourceDictionaryManager appDictionaries = new(); var themeDictionaryName = themeMode switch { ThemeMode.Light => "Light", ThemeMode.Dark => "Dark", _ => throw new NotImplementedException(), }; //ApplyThemeWithAnimation(applicationTheme); if (!appDictionaries.UpdateDictionary( "themes", new Uri($"{ThemesDictionaryPath}{themeDictionaryName}.xaml", UriKind.Absolute))) { return; } _activeThemeMode = themeMode; ThemeModeChanged?.Invoke(themeMode); } /// /// 获取当前设置的应用程序主题。 /// /// if something goes wrong. public static ThemeMode GetAppTheme() { if (_activeThemeMode == ThemeMode.Light) { ResourceDictionaryManager appDictionaries = new(); var themeDictionary = appDictionaries.LookupDictionary("themes"); if (themeDictionary == null) { return ThemeMode.Light; } var themeUri = themeDictionary.Source.ToString(); if (themeUri.Contains("light", StringComparison.OrdinalIgnoreCase)) { _activeThemeMode = ThemeMode.Light; } if (themeUri.Contains("dark", StringComparison.OrdinalIgnoreCase)) { _activeThemeMode = ThemeMode.Dark; } } return _activeThemeMode; } #region UiApplication private static ThemeManager? _themeManagerInstance; private readonly Application? application; private Window? mainWindow; private ResourceDictionary? resources; /// /// 初始化 实例. /// private ThemeManager(Application? application) { if (application is null) { return; } if (!ApplicationHasResources(application)) { return; } this.application = application; System.Diagnostics.Debug .WriteLine($"INFO | {typeof(ThemeManager)} application is {this.application}", "AntDesignWPF"); } private static bool ApplicationHasResources(Application? application) { return application! .Resources.MergedDictionaries .Where(e => e.Source is not null) .Any( e => e.Source .ToString() .Contains(ThemeManager.LibraryNamespace, StringComparison.OrdinalIgnoreCase)); } /// /// 将应用程序的 变为 shutdown 模式。 /// public void Shutdown() { application?.Shutdown(); } /// /// 获取或设置应用程序的资源。 /// public object TryFindResource(object resourceKey) { return Resources[resourceKey]; } /// /// 获取当前UI应用程序。 /// public static ThemeManager Current { get { _themeManagerInstance ??= new ThemeManager(Application.Current); return _themeManagerInstance; } } /// /// 获取或设置应用程序的主窗口。 /// public Window? MainWindow { get => application?.MainWindow ?? mainWindow; set { if (application != null) { application.MainWindow = value; } mainWindow = value; } } /// /// 获取或设置应用程序的资源。 /// public ResourceDictionary Resources { get { if (resources is null) { resources = []; try { var themesDictionary = new ThemesDictionary(); var controlsDictionary = new ControlsDictionary(); resources.MergedDictionaries.Add(themesDictionary); resources.MergedDictionaries.Add(controlsDictionary); } catch { } } return application?.Resources ?? resources; } set { if (application is not null) { application.Resources = value; } resources = value; } } #endregion }