diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs
index c37156540f4b..b122c41cee85 100644
--- a/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs
+++ b/src/SourceGenerators/Uno.UI.SourceGenerators/XamlGenerator/XamlFileGenerator.cs
@@ -4048,6 +4048,22 @@ void WriteBinding(bool isTemplateBindingAttachedProperty, string? prefix = null)
}
}
+ foreach (var option in bindingOptions)
+ {
+ var themeResourceCandidate = option.Objects.FirstOrDefault();
+ if (themeResourceCandidate?.Type is { PreferredXamlNamespace: XamlConstants.PresentationXamlXmlNamespace, Name: "ThemeResource" })
+ {
+ if (option.Member.Name == "TargetNullValue" && themeResourceCandidate.Members.FirstOrDefault().Value is string targetNullValueKey)
+ {
+ writer.AppendLineIndented($".ApplyTargetNullValueThemeResource(@\"{targetNullValueKey}\", {ParseContextPropertyAccess})");
+ }
+ else if (option.Member.Name == "FallbackValue" && themeResourceCandidate.Members.FirstOrDefault().Value is string fallbackValueKey)
+ {
+ writer.AppendLineIndented($".ApplyFallbackValueThemeResource(@\"{fallbackValueKey}\", {ParseContextPropertyAccess})");
+ }
+ }
+ }
+
// xbind initialization
if (bindNode != null && !isBindingType)
{
diff --git a/src/Uno.UI/Helpers/BindingExtensions.cs b/src/Uno.UI/Helpers/BindingExtensions.cs
new file mode 100644
index 000000000000..59834e2261ec
--- /dev/null
+++ b/src/Uno.UI/Helpers/BindingExtensions.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using Microsoft.UI.Xaml.Data;
+
+namespace Uno.UI.Helpers.Xaml;
+
+public static class BindingExtensions
+{
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Binding ApplyTargetNullValueThemeResource(this Binding binding, string themeResourceName, object parseContext)
+ {
+ binding.TargetNullValueThemeResource = themeResourceName;
+ binding.ParseContext = parseContext;
+ return binding;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Binding ApplyFallbackValueThemeResource(this Binding binding, string themeResourceName, object parseContext)
+ {
+ binding.FallbackValueThemeResource = themeResourceName;
+ binding.ParseContext = parseContext;
+ return binding;
+ }
+}
diff --git a/src/Uno.UI/UI/Xaml/Data/Binding.cs b/src/Uno.UI/UI/Xaml/Data/Binding.cs
index 829e67750fd8..4e35e8ecc3bc 100644
--- a/src/Uno.UI/UI/Xaml/Data/Binding.cs
+++ b/src/Uno.UI/UI/Xaml/Data/Binding.cs
@@ -123,6 +123,8 @@ public object FallbackValue
}
}
+ internal string FallbackValueThemeResource { get; set; }
+
///
/// Gets or sets a value that indicates the direction of the data flow in the binding.
///
@@ -180,6 +182,10 @@ public object Source
/// The target null value.
public object TargetNullValue { get; set; }
+ internal string TargetNullValueThemeResource { get; set; }
+
+ internal object ParseContext { get; set; }
+
///
/// Gets or sets a value that determines the timing of binding source updates for two-way bindings.
///
diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs
index 3315baa2d4a4..2f82a455333c 100644
--- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs
+++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs
@@ -1289,13 +1289,31 @@ internal void UpdateResourceBindings(ResourceUpdateReason updateReason, Resource
throw new ArgumentException();
}
+ ResourceDictionary[]? dictionariesInScope = null;
+
+ if (updateReason == ResourceUpdateReason.ThemeResource)
+ {
+ dictionariesInScope = GetResourceDictionaries(includeAppResources: false, containingDictionary).ToArray();
+ for (var i = dictionariesInScope.Length - 1; i >= 0; i--)
+ {
+ ResourceResolver.PushSourceToScope(dictionariesInScope[i]);
+ }
+
+ _properties.UpdateBindingExpressions();
+
+ foreach (var dict in dictionariesInScope)
+ {
+ ResourceResolver.PopSourceFromScope();
+ }
+ }
+
if (_resourceBindings == null || !_resourceBindings.HasBindings)
{
UpdateChildResourceBindings(updateReason);
return;
}
- var dictionariesInScope = GetResourceDictionaries(includeAppResources: false, containingDictionary).ToArray();
+ dictionariesInScope ??= GetResourceDictionaries(includeAppResources: false, containingDictionary).ToArray();
var bindings = _resourceBindings.GetAllBindings();
@@ -1428,6 +1446,8 @@ private void UpdateChildResourceBindings(ResourceUpdateReason updateReason)
// Call OnThemeChanged after bindings of descendants have been updated
themeChangeAware.OnThemeChanged();
}
+
+ _properties.OnThemeChanged();
}
}
diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs
index 69db7e087528..5ebb8d4dda78 100644
--- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs
+++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs
@@ -1,11 +1,12 @@
using System;
using System.Collections.Generic;
using System.Text;
+using Microsoft.UI.Xaml.Data;
using Uno.Collections;
using Uno.Extensions;
using Uno.Foundation.Logging;
+using Uno.UI;
using Uno.UI.DataBinding;
-using Microsoft.UI.Xaml.Data;
namespace Microsoft.UI.Xaml
{
@@ -219,5 +220,58 @@ internal BindingExpression GetBindingExpression(DependencyProperty dependencyPro
return null;
}
+
+ internal void UpdateBindingExpressions()
+ {
+ foreach (var binding in _bindings)
+ {
+ UpdateBindingPropertiesFromThemeResources(binding.ParentBinding);
+ }
+
+ foreach (var binding in _templateBindings)
+ {
+ UpdateBindingPropertiesFromThemeResources(binding.ParentBinding);
+ }
+ }
+
+ private static void UpdateBindingPropertiesFromThemeResources(Binding binding)
+ {
+ if (binding.TargetNullValueThemeResource is { } targetNullValueThemeResourceKey)
+ {
+ binding.TargetNullValue = (object)ResourceResolverSingleton.Instance.ResolveResourceStatic(targetNullValueThemeResourceKey, typeof(object), context: binding.ParseContext);
+ }
+
+ if (binding.FallbackValueThemeResource is { } fallbackValueThemeResourceKey)
+ {
+ binding.FallbackValue = (object)ResourceResolverSingleton.Instance.ResolveResourceStatic(fallbackValueThemeResourceKey, typeof(object), context: binding.ParseContext);
+ }
+ }
+
+ internal void OnThemeChanged()
+ {
+ foreach (var binding in _bindings)
+ {
+ RefreshBindingValueIfNecessary(binding);
+ }
+
+ foreach (var binding in _templateBindings)
+ {
+ RefreshBindingValueIfNecessary(binding);
+ }
+ }
+
+ private void RefreshBindingValueIfNecessary(BindingExpression binding)
+ {
+ if (binding.ParentBinding.TargetNullValueThemeResource is not null ||
+ binding.ParentBinding.FallbackValueThemeResource is not null)
+ {
+ // Note: This may refresh the binding more than really necessary.
+ // For example, if TargetNullValue is set to a theme resource, but the binding is not null
+ // In this case, a change to TargetNullValue should probably not refresh the binding.
+ // Another case is when the ThemeResource evaluates the same between light/dark themes.
+ // For now, it's not necessary.
+ binding.RefreshTarget();
+ }
+ }
}
}