This guide explains when and how to use CustomThemeApplicator to integrate Flowery.NET's theme controls into apps with existing theming architectures. For general theme management, see DaisyThemeManager.
The default DaisyThemeManager.ApplyTheme() works by adding/removing palette resources from Application.Resources.MergedDictionaries. This works great for simple apps, but you may need CustomThemeApplicator if:
| Scenario | Why Default Doesn't Work |
|---|---|
Custom MergedDictionaries in Application.Resources |
Resource resolution conflicts; DynamicResource bindings don't refresh |
| In-place ThemeDictionary updates required | Default adds to MergedDictionaries, not ThemeDictionaries |
| Theme persistence needed | Default doesn't save to settings |
| Additional actions on theme change | Logging, analytics, UI updates, etc. |
| Complex resource hierarchies | Multiple ResourceDictionaries that need coordinated updates |
If your app works fine with the default behavior, you don't need CustomThemeApplicator.
You have an Avalonia app with custom resources in Application.Resources:
<!-- App.axaml (before Flowery.NET) -->
<Application ...>
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://MyApp/Themes/CustomResources.axaml" />
<ResourceInclude Source="avares://MyApp/Themes/Icons.axaml" />
</ResourceDictionary.MergedDictionaries>
<SolidColorBrush x:Key="MyCustomBrush" Color="#808080"/>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<StyleInclude Source="avares://MyApp/Themes/AppStyles.axaml" />
</Application.Styles>
</Application>
You add DaisyUITheme and start using Daisy resources.
> Namespace Note: Flowery.NET uses two namespaces:
> - Flowery - For DaisyUITheme only (in App.axaml)
> - Flowery.Controls - For all controls (in views like MainWindow.axaml)
<!-- App.axaml (with Flowery.NET added) -->
<Application ...
xmlns:daisy="clr-namespace:Flowery;assembly=Flowery.NET"
RequestedThemeVariant="Dark">
<Application.Resources>
<!-- Your existing resources remain -->
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source="avares://MyApp/Themes/CustomResources.axaml" />
<ResourceInclude Source="avares://MyApp/Themes/Icons.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
<Application.Styles>
<FluentTheme />
<daisy:DaisyUITheme /> <!-- Add this -->
<StyleInclude Source="avares://MyApp/Themes/AppStyles.axaml" />
</Application.Styles>
</Application>
Your views now use Daisy resources:
<Border Background="{DynamicResource DaisyBase200Brush}"
BorderBrush="{DynamicResource DaisyBase300Brush}">
<TextBlock Foreground="{DynamicResource DaisyBaseContentBrush}"
Text="Hello Flowery!"/>
</Border>
You add DaisyThemeDropdown for runtime theme switching:
<controls:DaisyThemeDropdown Width="220"/>
What happens:
The colors don't update. If you restart the app, it's still using the old theme.
DaisyThemeDropdown internally calls DaisyThemeManager.ApplyTheme(), which does this:
// Removes old palette from MergedDictionaries
app.Resources.MergedDictionaries.Remove(_currentPalette);
// Adds new palette to MergedDictionaries
app.Resources.MergedDictionaries.Add(newPalette);
If your app has custom MergedDictionaries in Application.Resources, the new palette gets added but Avalonia's DynamicResource bindings may not refresh properly because:
1. Resources are added to MergedDictionaries, not updated in-place within the active ThemeDictionary
2. Avalonia's resource resolution doesn't always trigger a full refresh for this type of change
3. Views may still see cached brush values
Note: This issue depends on your specific resource setup. Some apps work fine with the default; others need in-place updates.Write a method that applies themes the way your app needs:
// App.axaml.cs
public static bool ApplyThemeInPlace(string themeName)
{
var themeInfo = DaisyThemeManager.GetThemeInfo(themeName);
if (themeInfo == null) return false;
var app = Application.Current;
if (app?.Resources == null) return false;
// Load the palette
var paletteUri = new Uri($"avares://Flowery.NET/Themes/Palettes/Daisy{themeInfo.Name}.axaml");
var palette = (ResourceDictionary)AvaloniaXamlLoader.Load(paletteUri);
// Determine target variant
var targetVariant = themeInfo.IsDark ? ThemeVariant.Dark : ThemeVariant.Light;
// Update resources IN-PLACE within the ThemeDictionary
if (app.Resources.ThemeDictionaries.TryGetValue(targetVariant, out var themeDict)
&& themeDict is IResourceDictionary dict)
{
foreach (var kvp in palette)
dict[kvp.Key] = kvp.Value;
}
// Switch variant to trigger Avalonia's refresh
app.RequestedThemeVariant = targetVariant;
// Optional: if your app has a settings object for persistence, save here
// MySettings.Current.Theme = themeName;
// MySettings.Save();
return true;
}
// App.axaml.cs
public override void OnFrameworkInitializationCompleted()
{
// One line: tell Flowery.NET to use your applicator
DaisyThemeManager.CustomThemeApplicator = ApplyThemeInPlace;
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
desktop.MainWindow = new MainWindow();
}
base.OnFrameworkInitializationCompleted();
}
DaisyThemeDropdown works ✓DaisyThemeController works ✓ DaisyThemeRadio works ✓DaisyThemeSwap works ✓DynamicResource bindings refresh properly ✓If the default theme application works but you want to save the selected theme to your app's settings:
DaisyThemeManager.CustomThemeApplicator = themeName =>
{
// Apply theme using default-style logic
var result = ApplyThemeDefault(themeName); // Your copy of default logic
if (result)
{
// If your app has a settings object, persist the theme here (example)
// AppSettings.Current.Theme = themeName;
// AppSettings.Save();
}
return result;
};
DaisyThemeManager.CustomThemeApplicator = themeName =>
{
// Use your logging framework (Serilog, NLog, Microsoft.Extensions.Logging, etc.)
Logger.Info($"Theme changing to: {themeName}");
// Optional: track with your analytics service
Analytics.Track("theme_changed", new { theme = themeName });
// Do the actual theme application
return ApplyThemeInPlace(themeName);
};
This is just a code example to demonstrate what might be used.
DaisyThemeManager.CustomThemeApplicator = themeName =>
{
var result = ApplyThemeInPlace(themeName);
if (result)
{
// Update other UI elements
MainViewModel.Instance.RefreshColors();
// Notify plugins
PluginManager.OnThemeChanged(themeName);
}
return result;
};
| Before CustomThemeApplicator | After |
|---|---|
| Built-in theme controls don't work with custom resource setups | All controls work seamlessly |
| Had to build custom theme UI | Use DaisyThemeDropdown etc. directly |
| Theme persistence required separate handling | Persistence built into your applicator |
| Complex workarounds or library modifications | Single line of setup |
The CustomThemeApplicator pattern gives you full control over how themes are applied while still benefiting from Flowery.NET's theme UI controls.