From acb7501ac03a4c02e06c13be0a4809cffb90ec2a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 22 Aug 2024 11:33:47 +0300 Subject: [PATCH 01/24] perf: Avoid storing DefaultValue in DPDetails --- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 43 +++++++++--- .../UI/Xaml/DependencyPropertyDetails.cs | 67 +++++-------------- ...dencyPropertyDetailsCollection.Bindings.cs | 30 ++++++++- .../DependencyPropertyDetailsCollection.cs | 27 +------- 4 files changed, 83 insertions(+), 84 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 6dfaf3a2f210..3168ad93aafe 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -293,17 +293,18 @@ private void Dispose(bool disposing) if (precedence == null) { - return propertyDetails.GetValue(); + return propertyDetails.CurrentHighestValuePrecedence == DependencyPropertyValuePrecedences.DefaultValue + ? GetDefaultValue(propertyDetails.Property) + : propertyDetails.GetValue(); } if (isPrecedenceSpecific) { - return propertyDetails.GetValue(precedence.Value); + return GetPrecedenceSpecificValue(propertyDetails, precedence.Value); } var highestPriority = GetCurrentHighestValuePrecedence(propertyDetails); - - return propertyDetails.GetValue((DependencyPropertyValuePrecedences)Math.Max((int)highestPriority, (int)precedence.Value)); + return GetPrecedenceSpecificValue(propertyDetails, (DependencyPropertyValuePrecedences)Math.Max((int)highestPriority, (int)precedence.Value)); } /// @@ -575,7 +576,7 @@ private void TryApplyDataContextOnPrecedenceChange( && !ReferenceEquals(childProviderClearNewValue.Store.Parent, ActualInstance)) { // Sets the DataContext of the new precedence value - childProviderClearNewValue.Store.RestoreInheritedDataContext(_properties.DataContextPropertyDetails.GetValue()); + childProviderClearNewValue.Store.RestoreInheritedDataContext(GetHighestValueFromDetails(_properties.DataContextPropertyDetails)); } } @@ -586,7 +587,7 @@ private void TryApplyDataContextOnPrecedenceChange( && !ReferenceEquals(childProviderSet.Store.Parent, ActualInstance)) { // Sets the DataContext of the new precedence value - childProviderSet.Store.RestoreInheritedDataContext(_properties.DataContextPropertyDetails.GetValue()); + childProviderSet.Store.RestoreInheritedDataContext(GetHighestValueFromDetails(_properties.DataContextPropertyDetails)); } if (previousValue is IDependencyObjectStoreProvider childProviderSetNewValue) @@ -1087,9 +1088,35 @@ internal void RegisterParentChangedCallbackStrong(object key, ParentChangedCallb { var stack = _properties.GetPropertyDetails(property); - return stack.GetValueUnderPrecedence(precedence); + var (value, resultPrecedence) = stack.GetValueUnderPrecedence(precedence); + if (resultPrecedence == DependencyPropertyValuePrecedences.DefaultValue) + { + var actualInstance = ActualInstance; + return (GetDefaultValue(property), DependencyPropertyValuePrecedences.DefaultValue); + } + + return (value, resultPrecedence); + } + + private object GetDefaultValue(DependencyProperty dp) + { + var actualInstance = ActualInstance; + return dp.GetDefaultValue(actualInstance as UIElement, actualInstance?.GetType()); + } + + private object? GetPrecedenceSpecificValue(DependencyPropertyDetails details, DependencyPropertyValuePrecedences precedence) + { + if (precedence == DependencyPropertyValuePrecedences.DefaultValue) + { + return GetDefaultValue(details.Property); + } + + return details.GetValue(precedence); } + private object? GetHighestValueFromDetails(DependencyPropertyDetails details) + => GetPrecedenceSpecificValue(details, details.CurrentHighestValuePrecedence); + internal DependencyPropertyDetails GetPropertyDetails(DependencyProperty property) { return _properties.GetPropertyDetails(property); @@ -2046,7 +2073,7 @@ DependencyPropertyDetails propertyDetails } } - if (AreDifferent(value, propertyDetails.GetValue(precedence))) + if (AreDifferent(value, GetPrecedenceSpecificValue(propertyDetails, precedence))) { propertyDetails.SetValue(value, precedence); } diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs index 76ae9fef2d28..2f9287f49b24 100644 --- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs +++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetails.cs @@ -23,12 +23,13 @@ internal class DependencyPropertyDetails : IEnumerable, IEnumerable, ID private object? _fastLocalValue; private BindingExpression? _binding; private object?[]? _stack; - private object? _defaultValue; private Flags _flags; private DependencyPropertyCallbackManager? _callbackManager; private const int DefaultValueIndex = (int)DependencyPropertyValuePrecedences.DefaultValue; - private const int StackSize = DefaultValueIndex + 1; + + // We are not storing DefaultValue here in DependencyPropertyDetails. So StackSize is exactly DefaultValueIndex + private const int StackSize = DefaultValueIndex; private static readonly LinearArrayPool _pool = LinearArrayPool.CreateAutomaticallyManaged(StackSize, 1); @@ -72,10 +73,6 @@ public void Dispose() public DependencyProperty Property { get; } - /// - /// Constructor - /// - /// The default value of the Dependency Property internal DependencyPropertyDetails(DependencyProperty property, Type dependencyObjectType, bool isTemplatedParentOrDataContext) { Property = property; @@ -124,24 +121,6 @@ private void GetPropertyInheritanceConfiguration( hasInherits = false; } - internal object? GetDefaultValue() - { - if (!HasDefaultValueSet) - { - _defaultValue = Property.GetMetadata(_dependencyObjectType).DefaultValue; - - // Ensures that the default value of non-nullable properties is not null - if (_defaultValue == null && !Property.IsTypeNullable) - { - _defaultValue = Property.GetFallbackDefaultValue(); - } - - _flags |= Flags.DefaultValueSet; - } - - return _defaultValue; - } - /// /// Sets the value at the given level in the stack /// @@ -247,12 +226,6 @@ private bool SetValueFast(object? value, DependencyPropertyValuePrecedences prec return false; } - internal void SetDefaultValue(object? defaultValue) - { - _defaultValue = defaultValue; - _flags |= Flags.DefaultValueSet; - } - internal BindingExpression? GetBinding() => _binding; @@ -293,11 +266,17 @@ internal void ClearBinding() [MethodImpl(MethodImplOptions.AggressiveInlining)] internal object? GetValue(DependencyPropertyValuePrecedences precedence) { + if (precedence == DependencyPropertyValuePrecedences.DefaultValue) + { + // Caller will calculate the default value. + // TODO: Consider refactoring the code such that we don't get to this code path with DefaultValue precedence + return UnsetValue.Instance; + } + if (_stack == null) { return precedence switch { - DependencyPropertyValuePrecedences.DefaultValue => GetDefaultValue(), DependencyPropertyValuePrecedences.Local when _highestPrecedence == DependencyPropertyValuePrecedences.Local => Unwrap(_fastLocalValue), _ => DependencyProperty.UnsetValue }; @@ -337,7 +316,7 @@ internal void ClearBinding() } else { - return (GetDefaultValue(), DependencyPropertyValuePrecedences.DefaultValue); + return (UnsetValue.Instance, DependencyPropertyValuePrecedences.DefaultValue); } } else @@ -355,7 +334,7 @@ internal void ClearBinding() } } - return (stackAlias[(int)DependencyPropertyValuePrecedences.DefaultValue], DependencyPropertyValuePrecedences.DefaultValue); + return (UnsetValue.Instance, DependencyPropertyValuePrecedences.DefaultValue); } } @@ -375,7 +354,7 @@ internal DependencyPropertyValuePrecedences CurrentHighestValuePrecedence private object? Validate(object? value) { return value == null && !Property.IsTypeNullable - ? GetDefaultValue() + ? throw new InvalidOperationException("DP cannot be set to null when its type is not nullable.") : Wrap(value); } @@ -387,7 +366,7 @@ internal DependencyPropertyValuePrecedences CurrentHighestValuePrecedence private object? ValidateNoWrap(object? value) { return value == null && !Property.IsTypeNullable - ? GetDefaultValue() + ? throw new InvalidOperationException("DP cannot be set to null when its type is not nullable.") : value; } @@ -406,9 +385,6 @@ internal DependencyPropertyValuePrecedences CurrentHighestValuePrecedence private bool HasWeakStorage => (_flags & Flags.WeakStorage) != 0; - private bool HasDefaultValueSet - => (_flags & Flags.DefaultValueSet) != 0; - internal bool HasValueInherits => (_flags & Flags.ValueInherits) != 0; @@ -429,8 +405,6 @@ private object?[] Stack MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(_unsetStack), StackSize) .CopyTo(MemoryMarshal.CreateSpan(ref MemoryMarshal.GetArrayDataReference(_stack)!, StackSize)); - _stack[DefaultValueIndex] = GetDefaultValue(); - if (_highestPrecedence == DependencyPropertyValuePrecedences.Local) { _stack[(int)DependencyPropertyValuePrecedences.Local] = _fastLocalValue; @@ -483,32 +457,27 @@ private enum Flags : byte /// WeakStorage = 1 << 0, - /// - /// Determines if the default value has been populated - /// - DefaultValueSet = 1 << 1, - /// /// Determines if the property inherits DataContext from its parent /// - ValueInherits = 1 << 2, + ValueInherits = 1 << 1, /// /// Determines if the property must not inherit DataContext from its parent /// - ValueDoesNotInherit = 1 << 3, + ValueDoesNotInherit = 1 << 2, /// /// Determines if the property inherits Value from its parent /// - Inherits = 1 << 4, + Inherits = 1 << 3, /// /// Normally, Animations has higher precedence than Local. However, /// we want local to take higher precedence if it's newer. /// This flag records this information. /// - LocalValueNewerThanAnimationsValue = 1 << 5, + LocalValueNewerThanAnimationsValue = 1 << 4, } } } diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs index 591762d04911..40b97d5032b9 100644 --- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs +++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.Bindings.cs @@ -111,7 +111,15 @@ internal void ResumeBindings() bindings[i].ResumeBinding(); } - ApplyDataContext(DataContextPropertyDetails.GetValue()); + var value = DataContextPropertyDetails.GetValue(); + if (value == DependencyProperty.UnsetValue) + { + // If we get UnsetValue, it means this is DefaultValue precedence that's not stored in DependencyPropertyDetails. + // In this case, we know for sure that DataContext's default value is null. + value = null; + } + + ApplyDataContext(value); } } @@ -144,7 +152,15 @@ internal void SetBinding(DependencyProperty dependencyProperty, Binding binding, { _templateBindings = _templateBindings.Add(bindingExpression); - ApplyBinding(bindingExpression, TemplatedParentPropertyDetails.GetValue()); + var templatedParent = TemplatedParentPropertyDetails.GetValue(); + if (templatedParent == DependencyProperty.UnsetValue) + { + // If we get UnsetValue, it means this is DefaultValue precedence that's not stored in DependencyPropertyDetails. + // In this case, we know for sure that TemplatedParent's default value is null. + templatedParent = null; + } + + ApplyBinding(bindingExpression, templatedParent); } else { @@ -156,7 +172,15 @@ internal void SetBinding(DependencyProperty dependencyProperty, Binding binding, } else { - ApplyBinding(bindingExpression, DataContextPropertyDetails.GetValue()); + var value = DataContextPropertyDetails.GetValue(); + if (value == DependencyProperty.UnsetValue) + { + // If we get UnsetValue, it means this is DefaultValue precedence that's not stored in DependencyPropertyDetails. + // In this case, we know for sure that DataContext's default value is null. + value = null; + } + + ApplyBinding(bindingExpression, value); } } } diff --git a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs index 320fc63b9a64..72d4689a55d2 100644 --- a/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs +++ b/src/Uno.UI/UI/Xaml/DependencyPropertyDetailsCollection.cs @@ -193,20 +193,11 @@ public DependencyPropertyDetails GetPropertyDetails(DependencyProperty property) _entries = entries = newEntries; } - // Avoid using ref here, as TryResolveDefaultValueFromProviders execution could execute code which - // could cause the entries array to be expanded by bucket size and to be reallocated, which would - // cause the reference to be invalidated. Example of this is a child property which calls child.SetParent(this). - // Even though the _entries size may change, the offset and bucketRemainder will still be valid. - var propertyEntry = entries[offset + bucketRemainder]; - if (propertyEntry is null) + ref var propertyEntry = ref entries[offset + bucketRemainder]; + + if (propertyEntry == null) { propertyEntry = new DependencyPropertyDetails(property, _ownerType, property == _dataContextProperty || property == _templatedParentProperty); - _entries[offset + bucketRemainder] = propertyEntry; - - if (TryResolveDefaultValueFromProviders(property, out var value)) - { - propertyEntry.SetDefaultValue(value); - } } return propertyEntry; @@ -230,18 +221,6 @@ public DependencyPropertyDetails GetPropertyDetails(DependencyProperty property) } } - private bool TryResolveDefaultValueFromProviders(DependencyProperty property, out object? value) - { - // Replicate the WinUI behavior of DependencyObject::GetDefaultValue2 specifically for UIElement. - if (Owner is UIElement uiElement) - { - return uiElement.GetDefaultValue2(property, out value); - } - - value = null; - return false; - } - private void ReturnEntriesAndOffsetsToPools() { if (_entries != _empty) From 6595fb6570d8decc95913257bb25f38563f0a19b Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Thu, 22 Aug 2024 21:18:26 +0300 Subject: [PATCH 02/24] chore: Few adjustments - not yet complete --- .../Helpers/StyleHelper.cs | 14 -------- .../UI/Xaml/Controls/Control/Control.cs | 9 ----- .../Controls/FocusVisual/SystemFocusVisual.cs | 1 - src/Uno.UI/UI/Xaml/DependencyProperty.cs | 35 +++++++++++++++++++ src/Uno.UI/UI/Xaml/Documents/TextElement.cs | 21 +++++------ src/Uno.UI/UI/Xaml/FrameworkElement.cs | 30 ++-------------- src/Uno.UI/UI/Xaml/ResourceResolver.cs | 2 +- 7 files changed, 49 insertions(+), 63 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/Helpers/StyleHelper.cs b/src/Uno.UI.RuntimeTests/Helpers/StyleHelper.cs index 862ace2df302..114686b5451e 100644 --- a/src/Uno.UI.RuntimeTests/Helpers/StyleHelper.cs +++ b/src/Uno.UI.RuntimeTests/Helpers/StyleHelper.cs @@ -93,27 +93,13 @@ public static IDisposable UseFluentStyles() // Force default brushes to be reloaded DefaultBrushes.ResetDefaultThemeBrushes(); - ResetIslandRootForeground(); return new DisposableAction(() => { resources.MergedDictionaries.Remove(xcr); DefaultBrushes.ResetDefaultThemeBrushes(); - ResetIslandRootForeground(); }); #endif } - -#if !WINAPPSDK - private static void ResetIslandRootForeground() - { - if (Uno.UI.Xaml.Core.CoreServices.Instance.InitializationType == Xaml.Core.InitializationType.IslandsOnly && - VisualTreeUtils.FindVisualChildByType(TestServices.WindowHelper.XamlRoot.VisualTree.RootElement) is { } control) - { - // Ensure the root element's Foreground is set correctly - control.SetValue(Control.ForegroundProperty, DefaultBrushes.TextForegroundBrush, DependencyPropertyValuePrecedences.DefaultValue); - } - } -#endif } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs index 863f1bf215e6..2f6700f6043f 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Control/Control.cs @@ -43,7 +43,6 @@ public partial class Control : FrameworkElement private void InitializeControl() { - SetDefaultForeground(ForegroundProperty); SubscribeToOverridenRoutedEvents(); OnIsFocusableChanged(); @@ -59,14 +58,6 @@ private void InitializeControl() internal override bool IsEnabledOverride() => IsEnabled && base.IsEnabledOverride(); - internal override void UpdateThemeBindings(Data.ResourceUpdateReason updateReason) - { - base.UpdateThemeBindings(updateReason); - - //override the default value from dependency property based on application theme - SetDefaultForeground(ForegroundProperty); - } - private protected override Type GetDefaultStyleKey() => DefaultStyleKey as Type; protected override void OnBackgroundChanged(DependencyPropertyChangedEventArgs e) diff --git a/src/Uno.UI/UI/Xaml/Controls/FocusVisual/SystemFocusVisual.cs b/src/Uno.UI/UI/Xaml/Controls/FocusVisual/SystemFocusVisual.cs index caba7ab77897..c1cf7855db06 100644 --- a/src/Uno.UI/UI/Xaml/Controls/FocusVisual/SystemFocusVisual.cs +++ b/src/Uno.UI/UI/Xaml/Controls/FocusVisual/SystemFocusVisual.cs @@ -66,7 +66,6 @@ private static void OnFocusedElementChanged(DependencyObject dependencyObject, D if (args.NewValue is FrameworkElement element) { - element.EnsureFocusVisualBrushDefaults(); element.SizeChanged += focusVisual.FocusedElementSizeChanged; #if !UNO_HAS_ENHANCED_LIFECYCLE element.LayoutUpdated += focusVisual.FocusedElementLayoutUpdated; diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 65eb6c42d969..c698079bf9ad 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -8,12 +8,14 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Controls.Primitives; +using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Shapes; using Uno; using Uno.Extensions; using Uno.UI; using Uno.UI.Dispatching; +using Uno.UI.Xaml.Media; #if __ANDROID__ using _View = Android.Views.View; @@ -477,6 +479,23 @@ private static DependencyProperty[] InternalGetFrameworkPropertiesForType(Type t return output.ToArray(); } + private bool TryGetDefaultInheritedPropertyValue(out object defaultValue) + { + if (this == TextElement.ForegroundProperty || + this == TextBlock.ForegroundProperty || + this == Control.ForegroundProperty || + this == RichTextBlock.ForegroundProperty || + this == ContentPresenter.ForegroundProperty || + this == IconElement.ForegroundProperty) + { + defaultValue = DefaultBrushes.TextForegroundBrush; + return true; + } + + defaultValue = null; + return false; + } + internal object GetDefaultValue(UIElement referenceObject, Type forType) { if (referenceObject?.GetDefaultValue2(this, out var defaultValue) == true) @@ -484,6 +503,22 @@ internal object GetDefaultValue(UIElement referenceObject, Type forType) return defaultValue; } + if (IsInherited && TryGetDefaultInheritedPropertyValue(out var value)) + { + return value; + } + + if (this == FrameworkElement.FocusVisualPrimaryBrushProperty && + ResourceResolver.TryStaticRetrieval("SystemControlFocusVisualPrimaryBrush", null, out var primaryBrush)) + { + return primaryBrush; + } + else if (this == FrameworkElement.FocusVisualSecondaryBrushProperty && + ResourceResolver.TryStaticRetrieval("SystemControlFocusVisualSecondaryBrush", null, out var secondaryBrush)) + { + return secondaryBrush; + } + if (this == Shape.StretchProperty) { if (forType == typeof(Rectangle) || forType == typeof(Ellipse)) diff --git a/src/Uno.UI/UI/Xaml/Documents/TextElement.cs b/src/Uno.UI/UI/Xaml/Documents/TextElement.cs index cf9ee42fdb2c..d99068928f75 100644 --- a/src/Uno.UI/UI/Xaml/Documents/TextElement.cs +++ b/src/Uno.UI/UI/Xaml/Documents/TextElement.cs @@ -167,7 +167,7 @@ protected virtual void OnFontSizeChanged() public Brush Foreground { - get => GetForegroundValue(); + get => (Brush)GetValue(ForegroundProperty); set { if (value != null && !(value is SolidColorBrush)) @@ -175,14 +175,19 @@ public Brush Foreground throw new InvalidOperationException("Specified brush is not a SolidColorBrush"); } - SetForegroundValue(value); + SetValue(ForegroundProperty, value); } } - [GeneratedDependencyProperty(Options = FrameworkPropertyMetadataOptions.Inherits, ChangedCallback = true, ChangedCallbackName = nameof(OnForegroundChanged))] - public static DependencyProperty ForegroundProperty { get; } = CreateForegroundProperty(); - - private static Brush GetForegroundDefaultValue() => SolidColorBrushHelper.Black; + public static DependencyProperty ForegroundProperty { get; } = DependencyProperty.Register( + nameof(Foreground), + typeof(Brush), + typeof(TextElement), + new FrameworkPropertyMetadata( + defaultValue: SolidColorBrushHelper.Black, + options: FrameworkPropertyMetadataOptions.Inherits, + propertyChangedCallback: (instance, args) => ((TextElement)instance).OnForegroundChanged() + )); protected virtual void OnForegroundChanged() { @@ -389,10 +394,6 @@ void SetDefaultForeground(DependencyProperty foregroundProperty) // So, we set this with ImplicitStyle precedence which is a higher precedence than Inheritance. this.SetValue(foregroundProperty, DefaultTextForegroundBrush, DependencyPropertyValuePrecedences.ImplicitStyle); } - else - { - this.SetValue(foregroundProperty, DefaultTextForegroundBrush, DependencyPropertyValuePrecedences.DefaultValue); - } ((IDependencyObjectStoreProvider)this).Store.SetLastUsedTheme(Application.Current?.RequestedThemeForResources); } diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.cs index 8f7a1c728989..1e1ec15d6ed1 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.cs @@ -612,11 +612,7 @@ public Thickness FocusVisualSecondaryThickness public Brush FocusVisualSecondaryBrush { - get - { - EnsureFocusVisualBrushDefaults(); - return GetFocusVisualSecondaryBrushValue(); - } + get => GetFocusVisualSecondaryBrushValue(); set => SetFocusVisualSecondaryBrushValue(value); } @@ -633,11 +629,7 @@ public Thickness FocusVisualPrimaryThickness public Brush FocusVisualPrimaryBrush { - get - { - EnsureFocusVisualBrushDefaults(); - return GetFocusVisualPrimaryBrushValue(); - } + get => GetFocusVisualPrimaryBrushValue(); set => SetFocusVisualPrimaryBrushValue(value); } @@ -655,21 +647,6 @@ public Thickness FocusVisualMargin [GeneratedDependencyProperty] public static DependencyProperty FocusVisualMarginProperty { get; } = CreateFocusVisualMarginProperty(); - private bool _focusVisualBrushesInitialized; - - internal void EnsureFocusVisualBrushDefaults() - { - if (_focusVisualBrushesInitialized) - { - return; - } - - ResourceResolver.ApplyResource(this, FocusVisualPrimaryBrushProperty, new SpecializedResourceDictionary.ResourceKey("SystemControlFocusVisualPrimaryBrush"), ResourceUpdateReason.ThemeResource, null, DependencyPropertyValuePrecedences.DefaultValue); - ResourceResolver.ApplyResource(this, FocusVisualSecondaryBrushProperty, new SpecializedResourceDictionary.ResourceKey("SystemControlFocusVisualSecondaryBrush"), ResourceUpdateReason.ThemeResource, null, DependencyPropertyValuePrecedences.DefaultValue); - - _focusVisualBrushesInitialized = true; - } - /// /// Gets or sets whether a disabled control can receive focus. /// @@ -986,9 +963,6 @@ internal virtual void UpdateThemeBindings(ResourceUpdateReason updateReason) Resources?.UpdateThemeBindings(updateReason); (this as IDependencyObjectStoreProvider).Store.UpdateResourceBindings(updateReason); - // After theme change, the focus visual brushes may not reflect the correct settings - _focusVisualBrushesInitialized = false; - if (updateReason == ResourceUpdateReason.ThemeResource) { // Trigger ActualThemeChanged if relevant diff --git a/src/Uno.UI/UI/Xaml/ResourceResolver.cs b/src/Uno.UI/UI/Xaml/ResourceResolver.cs index 45a51c7c379f..93ee027235ba 100644 --- a/src/Uno.UI/UI/Xaml/ResourceResolver.cs +++ b/src/Uno.UI/UI/Xaml/ResourceResolver.cs @@ -379,7 +379,7 @@ private static bool TryVisualTreeRetrieval(in SpecializedResourceDictionary.Reso /// /// Try to retrieve a resource statically (at parse time). This will check resources in 'xaml scope' first, then top-level resources. /// - private static bool TryStaticRetrieval(in SpecializedResourceDictionary.ResourceKey resourceKey, object context, out object value) + internal static bool TryStaticRetrieval(in SpecializedResourceDictionary.ResourceKey resourceKey, object context, out object value) { // This block is a manual enumeration to avoid the foreach pattern // See https://github.com/dotnet/runtime/issues/56309 for details From 99a460536d8227b233fc18c185b94c676e44045a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 23 Aug 2024 10:09:59 +0300 Subject: [PATCH 03/24] chore: Progress --- src/Uno.UI/UI/Xaml/Controls/Icons/BitmapIcon.cs | 6 +++++- src/Uno.UI/UI/Xaml/Controls/Icons/FontIcon.cs | 12 +++++++++++- src/Uno.UI/UI/Xaml/Controls/Icons/PathIcon.cs | 12 +++++++++++- src/Uno.UI/UI/Xaml/Controls/Icons/SymbolIcon.cs | 12 +++++++++++- src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs | 6 +++++- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 5 ++++- 6 files changed, 47 insertions(+), 6 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/Icons/BitmapIcon.cs b/src/Uno.UI/UI/Xaml/Controls/Icons/BitmapIcon.cs index 4210296a2ed1..15a87d6bcee7 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Icons/BitmapIcon.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Icons/BitmapIcon.cs @@ -8,7 +8,7 @@ namespace Microsoft.UI.Xaml.Controls; /// /// Represents an icon that uses a bitmap as its content. /// -public partial class BitmapIcon : IconElement +public partial class BitmapIcon : IconElement, IThemeChangeAware { private readonly Image _image; @@ -78,4 +78,8 @@ private void UpdateImageMonochromeColor() } #endif } + + // The way this works in WinUI is by the MarkInheritedPropertyDirty call in CFrameworkElement::NotifyThemeChangedForInheritedProperties + // There is a special handling for Foreground specifically there. + void IThemeChangeAware.OnThemeChanged() => UpdateImageMonochromeColor(); } diff --git a/src/Uno.UI/UI/Xaml/Controls/Icons/FontIcon.cs b/src/Uno.UI/UI/Xaml/Controls/Icons/FontIcon.cs index dfcb92c03a68..42701add5e85 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Icons/FontIcon.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Icons/FontIcon.cs @@ -12,7 +12,7 @@ namespace Microsoft.UI.Xaml.Controls; /// /// Represents an icon that uses a glyph from the specified font. /// -public partial class FontIcon : IconElement +public partial class FontIcon : IconElement, IThemeChangeAware { private readonly TextBlock _textBlock; @@ -220,4 +220,14 @@ private protected override void OnForegroundChanged(DependencyPropertyChangedEve _textBlock.Foreground = (Brush)e.NewValue; } } + + // The way this works in WinUI is by the MarkInheritedPropertyDirty call in CFrameworkElement::NotifyThemeChangedForInheritedProperties + // There is a special handling for Foreground specifically there. + void IThemeChangeAware.OnThemeChanged() + { + if (_textBlock is not null) + { + _textBlock.Foreground = Foreground; + } + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Icons/PathIcon.cs b/src/Uno.UI/UI/Xaml/Controls/Icons/PathIcon.cs index 8968a43fee92..8610556cd216 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Icons/PathIcon.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Icons/PathIcon.cs @@ -6,7 +6,7 @@ namespace Microsoft.UI.Xaml.Controls; /// /// Represents an icon that uses a vector path as its content. /// -public partial class PathIcon : IconElement +public partial class PathIcon : IconElement, IThemeChangeAware { private readonly Path _path; @@ -60,4 +60,14 @@ private protected override void OnForegroundChanged(DependencyPropertyChangedEve _path.Fill = (Brush)e.NewValue; } } + + // The way this works in WinUI is by the MarkInheritedPropertyDirty call in CFrameworkElement::NotifyThemeChangedForInheritedProperties + // There is a special handling for Foreground specifically there. + void IThemeChangeAware.OnThemeChanged() + { + if (_path is not null) + { + _path.Fill = Foreground; + } + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/Icons/SymbolIcon.cs b/src/Uno.UI/UI/Xaml/Controls/Icons/SymbolIcon.cs index d0f3fe60ebe8..80ba2f25e3ac 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Icons/SymbolIcon.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Icons/SymbolIcon.cs @@ -10,7 +10,7 @@ namespace Microsoft.UI.Xaml.Controls; /// /// Represents an icon that uses a glyph from the Segoe MDL2 Assets font as its content. /// -public sealed partial class SymbolIcon : IconElement +public sealed partial class SymbolIcon : IconElement, IThemeChangeAware { private double _fontSize = 20.0; @@ -103,4 +103,14 @@ private protected override void OnForegroundChanged(DependencyPropertyChangedEve _textBlock.Foreground = (Brush)e.NewValue; } } + + // The way this works in WinUI is by the MarkInheritedPropertyDirty call in CFrameworkElement::NotifyThemeChangedForInheritedProperties + // There is a special handling for Foreground specifically there. + void IThemeChangeAware.OnThemeChanged() + { + if (_textBlock is not null) + { + _textBlock.Foreground = Foreground; + } + } } diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs index f543ba1c8dca..8e5584ec048c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs @@ -36,7 +36,7 @@ namespace Microsoft.UI.Xaml.Controls { [ContentProperty(Name = nameof(Inlines))] - public partial class TextBlock : DependencyObject + public partial class TextBlock : DependencyObject, IThemeChangeAware { private InlineCollection _inlines; private string _inlinesText; // Text derived from the content of Inlines @@ -1265,5 +1265,9 @@ public Range((int start, int end) tuple) : this(tuple.start, tuple.end) [SuppressMessage("CodeQuality", "IDE0051:Remove unused private members", Justification = "Used only by some platforms")] partial void UpdateIsTextTrimmed(); + + // The way this works in WinUI is by the MarkInheritedPropertyDirty call in CFrameworkElement::NotifyThemeChangedForInheritedProperties + // There is a special handling for Foreground specifically there. + void IThemeChangeAware.OnThemeChanged() => OnForegroundChanged(); } } diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 3168ad93aafe..4592844cda5e 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -1617,7 +1617,10 @@ void Propagate(DependencyObjectStore store) var prop = props[propertyIndex]; if (!IsTemplatedParentFrozen || prop != TemplatedParentProperty) { - store.OnParentPropertyChangedCallback(instanceRef, prop, GetValue(prop)); + if (GetCurrentHighestValuePrecedence(prop) != DependencyPropertyValuePrecedences.DefaultValue) + { + store.OnParentPropertyChangedCallback(instanceRef, prop, GetValue(prop)); + } } } } From f3359127af320aa6d08c0ae07a40c28a5b0fbe1d Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Fri, 23 Aug 2024 11:04:18 +0300 Subject: [PATCH 04/24] chore: One more optimization --- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 4592844cda5e..421b06db73f2 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -279,6 +279,12 @@ private void Dispose(bool disposing) ValidatePropertyOwner(property); + if (propertyDetails is null && (precedence is null || precedence == DependencyPropertyValuePrecedences.DefaultValue) && _properties.FindPropertyDetails(property) is null) + { + // Performance: Avoid force-creating DependencyPropertyDetails when not needed. + return GetDefaultValue(property); + } + propertyDetails ??= _properties.GetPropertyDetails(property); return GetValue(propertyDetails, precedence, isPrecedenceSpecific); From fb7133de4b82bd446013fba6cd2c77ce5af3b9c8 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 24 Aug 2024 17:20:21 +0300 Subject: [PATCH 05/24] chore: Small fix --- .../UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs | 4 ++-- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 8 ++++++++ src/Uno.UI/UI/Xaml/Media/RectangleGeometry.cs | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs index 2461aba5df10..5bcd787941d7 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs @@ -159,7 +159,7 @@ public TextReadingOrder TextReadingOrder } public static DependencyProperty TextReadingOrderProperty { get; } = - DependencyProperty.Register(nameof(TextReadingOrder), typeof(TextReadingOrder), typeof(NumberBox), new FrameworkPropertyMetadata(null)); + DependencyProperty.Register(nameof(TextReadingOrder), typeof(TextReadingOrder), typeof(NumberBox), new FrameworkPropertyMetadata(false)); public bool PreventKeyboardDisplayOnProgrammaticFocus { @@ -168,7 +168,7 @@ public bool PreventKeyboardDisplayOnProgrammaticFocus } public static DependencyProperty PreventKeyboardDisplayOnProgrammaticFocusProperty { get; } = - DependencyProperty.Register(nameof(PreventKeyboardDisplayOnProgrammaticFocus), typeof(bool), typeof(NumberBox), new FrameworkPropertyMetadata(null)); + DependencyProperty.Register(nameof(PreventKeyboardDisplayOnProgrammaticFocus), typeof(bool), typeof(NumberBox), new FrameworkPropertyMetadata(false)); public new object Description { diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 421b06db73f2..99adc8208330 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -2082,6 +2082,14 @@ DependencyPropertyDetails propertyDetails } } + if (value == null && !propertyDetails.Property.IsTypeNullable) + { + // This probably shouldn't exist. We should fix cases that are broken. + // Most (if not all) broken cases appear to be related to TemplatedParent being null incorrectly when applying styles. + // This should be re-validated after https://github.com/unoplatform/uno/issues/1621 is fixed. + value = GetDefaultValue(propertyDetails.Property); + } + if (AreDifferent(value, GetPrecedenceSpecificValue(propertyDetails, precedence))) { propertyDetails.SetValue(value, precedence); diff --git a/src/Uno.UI/UI/Xaml/Media/RectangleGeometry.cs b/src/Uno.UI/UI/Xaml/Media/RectangleGeometry.cs index 8f8e2a1510b4..a11c0ca0931f 100644 --- a/src/Uno.UI/UI/Xaml/Media/RectangleGeometry.cs +++ b/src/Uno.UI/UI/Xaml/Media/RectangleGeometry.cs @@ -29,7 +29,7 @@ public Rect Rect "Rect", typeof(Rect), typeof(RectangleGeometry), new FrameworkPropertyMetadata( - null, + default(Rect), options: FrameworkPropertyMetadataOptions.AffectsMeasure, propertyChangedCallback: OnRectChanged)); From 5061466f8536ccf0643c0739601b4c00bfc71fba Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 24 Aug 2024 17:55:07 +0300 Subject: [PATCH 06/24] chore: temporary --- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index c698079bf9ad..186adab9f28f 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -138,6 +138,11 @@ internal bool IsDependencyObjectCollection /// A property with the same name has already been declared for the ownerType public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata) { + if (!propertyType.IsNullable() && typeMetadata.DefaultValue is null) + { + throw new Exception($"Cannot register property {ownerType}.{name} with non-nullable type {propertyType}"); + } + var newProperty = new DependencyProperty(name, propertyType, ownerType, typeMetadata, attached: false); try From e4616f8e0a4573dfee27c913e319100c5347151a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 24 Aug 2024 18:43:16 +0300 Subject: [PATCH 07/24] chore: Fix --- .../UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs index 5bcd787941d7..72b6b17515a6 100644 --- a/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs +++ b/src/Uno.UI/Microsoft/UI/Xaml/Controls/NumberBox/NumberBox.Properties.cs @@ -159,7 +159,7 @@ public TextReadingOrder TextReadingOrder } public static DependencyProperty TextReadingOrderProperty { get; } = - DependencyProperty.Register(nameof(TextReadingOrder), typeof(TextReadingOrder), typeof(NumberBox), new FrameworkPropertyMetadata(false)); + DependencyProperty.Register(nameof(TextReadingOrder), typeof(TextReadingOrder), typeof(NumberBox), new FrameworkPropertyMetadata(TextReadingOrder.Default)); public bool PreventKeyboardDisplayOnProgrammaticFocus { From 039361fcdd36c464cb9aa3e722cfa00a93dfa6dd Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sat, 24 Aug 2024 19:09:38 +0300 Subject: [PATCH 08/24] chore: Small fix --- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 99adc8208330..01bd569271db 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -1107,7 +1107,15 @@ internal void RegisterParentChangedCallbackStrong(object key, ParentChangedCallb private object GetDefaultValue(DependencyProperty dp) { var actualInstance = ActualInstance; - return dp.GetDefaultValue(actualInstance as UIElement, actualInstance?.GetType()); + var defaultValue = dp.GetDefaultValue(actualInstance as UIElement, actualInstance?.GetType()); + if (GetCurrentHighestValuePrecedence(dp) == DependencyPropertyValuePrecedences.DefaultValue && + // This should be for OnDemand DPs in general which we don't yet support + dp == UIElement.KeyboardAcceleratorsProperty) + { + _properties.GetPropertyDetails(dp).SetValue(defaultValue, DependencyPropertyValuePrecedences.Local); + } + + return defaultValue; } private object? GetPrecedenceSpecificValue(DependencyPropertyDetails details, DependencyPropertyValuePrecedences precedence) From 3bdec66d2379bdcc0112785fd9e6d6d18e87fb67 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 07:56:45 +0300 Subject: [PATCH 09/24] chore: Progress --- src/Uno.UI/DataBinding/BindingExpression.cs | 2 +- .../ContentPresenter/ContentPresenter.cs | 4 +-- .../UI/Xaml/Controls/Icons/IconElement.cs | 4 +-- .../UI/Xaml/Controls/TextBlock/TextBlock.cs | 4 +-- .../Xaml/Controls/TextBlock/TextBlock.skia.cs | 2 +- .../Xaml/Controls/TextBlock/TextBlock.wasm.cs | 2 +- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 2 +- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 33 +++++++++++++++++-- src/Uno.UI/UI/Xaml/Documents/TextElement.cs | 4 +-- .../Media/Animation/FadeInThemeAnimation.cs | 18 ---------- .../Media/Animation/FadeOutThemeAnimation.cs | 18 ---------- src/Uno.UI/UI/Xaml/UIElement.cs | 7 +--- 12 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/Uno.UI/DataBinding/BindingExpression.cs b/src/Uno.UI/DataBinding/BindingExpression.cs index 1b7d4ebd06ad..b73c264e57b8 100644 --- a/src/Uno.UI/DataBinding/BindingExpression.cs +++ b/src/Uno.UI/DataBinding/BindingExpression.cs @@ -370,7 +370,7 @@ private void ApplyFallbackValue(bool useTypeDefaultValue = true) else if (useTypeDefaultValue && TargetPropertyDetails != null) { var viewTarget = _view.Target; - SetTargetValue(TargetPropertyDetails.Property.GetDefaultValue(viewTarget as UIElement, viewTarget?.GetType())); + SetTargetValue(TargetPropertyDetails.Property.GetDefaultValue(viewTarget as DependencyObject, viewTarget?.GetType())); } } diff --git a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs index 1606314f9150..b1c19edcecb3 100644 --- a/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs +++ b/src/Uno.UI/UI/Xaml/Controls/ContentPresenter/ContentPresenter.cs @@ -77,7 +77,7 @@ public ContentPresenter() #if !UNO_HAS_BORDER_VISUAL _borderRenderer = new BorderLayerRenderer(this); #endif - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); InitializePlatform(); } @@ -1234,7 +1234,7 @@ protected override void OnBackgroundChanged(DependencyPropertyChangedEventArgs e internal override void UpdateThemeBindings(ResourceUpdateReason updateReason) { base.UpdateThemeBindings(updateReason); - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); } #if __ANDROID__ diff --git a/src/Uno.UI/UI/Xaml/Controls/Icons/IconElement.cs b/src/Uno.UI/UI/Xaml/Controls/Icons/IconElement.cs index 59174d3234a7..b25bb4f0c22b 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Icons/IconElement.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Icons/IconElement.cs @@ -22,7 +22,7 @@ public partial class IconElement : FrameworkElement public IconElement() { - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); } /// @@ -95,7 +95,7 @@ internal override void UpdateThemeBindings(ResourceUpdateReason updateReason) { base.UpdateThemeBindings(updateReason); - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); } [MemberNotNull(nameof(_rootGrid))] diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs index 8e5584ec048c..d0f21538fcd6 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.cs @@ -74,7 +74,7 @@ private Range Selection public TextBlock() { IFrameworkElementHelper.Initialize(this); - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); _hyperlinks.CollectionChanged += HyperlinksOnCollectionChanged; @@ -1230,7 +1230,7 @@ internal override void UpdateThemeBindings(Data.ResourceUpdateReason updateReaso { base.UpdateThemeBindings(updateReason); - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); if (_inlines is not null) { diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs index 154674a45916..7cf541534d77 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.skia.cs @@ -32,7 +32,7 @@ partial class TextBlock : FrameworkElement, IBlock public TextBlock() { - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); _textVisual = new TextVisual(Visual.Compositor, this); Visual.Children.InsertAtBottom(_textVisual); diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs index 3c8720e16c96..e4ee8dd0aee4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs @@ -39,7 +39,7 @@ partial class TextBlock : FrameworkElement public TextBlock() : base("p") { - SetDefaultForeground(ForegroundProperty); + UpdateLastUsedTheme(); OnFontStyleChangedPartial(); OnFontWeightChangedPartial(); diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 01bd569271db..2f882d211a3c 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -1107,7 +1107,7 @@ internal void RegisterParentChangedCallbackStrong(object key, ParentChangedCallb private object GetDefaultValue(DependencyProperty dp) { var actualInstance = ActualInstance; - var defaultValue = dp.GetDefaultValue(actualInstance as UIElement, actualInstance?.GetType()); + var defaultValue = dp.GetDefaultValue(actualInstance, actualInstance?.GetType()); if (GetCurrentHighestValuePrecedence(dp) == DependencyPropertyValuePrecedences.DefaultValue && // This should be for OnDemand DPs in general which we don't yet support dp == UIElement.KeyboardAcceleratorsProperty) diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 186adab9f28f..325ce56d4e84 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -10,6 +10,7 @@ using Microsoft.UI.Xaml.Controls.Primitives; using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; +using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Shapes; using Uno; using Uno.Extensions; @@ -501,9 +502,9 @@ private bool TryGetDefaultInheritedPropertyValue(out object defaultValue) return false; } - internal object GetDefaultValue(UIElement referenceObject, Type forType) + internal object GetDefaultValue(DependencyObject referenceObject, Type forType) { - if (referenceObject?.GetDefaultValue2(this, out var defaultValue) == true) + if ((referenceObject as UIElement)?.GetDefaultValue2(this, out var defaultValue) == true) { return defaultValue; } @@ -532,6 +533,34 @@ internal object GetDefaultValue(UIElement referenceObject, Type forType) } } + if (this == Timeline.DurationProperty) + { + if (referenceObject is FadeInThemeAnimation or FadeOutThemeAnimation) + { + return FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration; + } + } + + if (this == Storyboard.TargetPropertyProperty) + { + if (referenceObject is FadeInThemeAnimation or FadeOutThemeAnimation) + { + return "Opacity"; + } + } + + if (this == DoubleAnimation.ToProperty) + { + if (referenceObject is FadeInThemeAnimation) + { + return (double?)1.0d; + } + else if (referenceObject is FadeOutThemeAnimation) + { + return (double?)0.0d; + } + } + // TODO: Handle DependencyProperty.CreateDefaultValueCallback when implemented. return _ownerTypeMetadata.DefaultValue; } diff --git a/src/Uno.UI/UI/Xaml/Documents/TextElement.cs b/src/Uno.UI/UI/Xaml/Documents/TextElement.cs index d99068928f75..ac3ed9b111e9 100644 --- a/src/Uno.UI/UI/Xaml/Documents/TextElement.cs +++ b/src/Uno.UI/UI/Xaml/Documents/TextElement.cs @@ -381,8 +381,8 @@ internal FrameworkElement GetContainingFrameworkElement() private protected virtual Brush DefaultTextForegroundBrush => DefaultBrushes.TextForegroundBrush; -#if __WASM__ // On Wasm, we inherit UIElement, and so we need to shadow UIElement.SetDefaultForeground. - private protected new +#if __WASM__ + private protected #else private #endif diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/FadeInThemeAnimation.cs b/src/Uno.UI/UI/Xaml/Media/Animation/FadeInThemeAnimation.cs index ed374deb8cde..bc2d73052176 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/FadeInThemeAnimation.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/FadeInThemeAnimation.cs @@ -7,24 +7,6 @@ namespace Microsoft.UI.Xaml.Media.Animation { public partial class FadeInThemeAnimation : DoubleAnimation, ITimeline { - public FadeInThemeAnimation() - { - this.SetValue( - DurationProperty, - new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration), - DependencyPropertyValuePrecedences.DefaultValue); - - this.SetValue( - Storyboard.TargetPropertyProperty, - "Opacity", - DependencyPropertyValuePrecedences.DefaultValue); - - this.SetValue( - ToProperty, - (double?)1d, - DependencyPropertyValuePrecedences.DefaultValue); - } - public static DependencyProperty TargetNameProperty { get; } = DependencyProperty.Register( "TargetName", typeof(string), typeof(FadeInThemeAnimation), new FrameworkPropertyMetadata(string.Empty)); diff --git a/src/Uno.UI/UI/Xaml/Media/Animation/FadeOutThemeAnimation.cs b/src/Uno.UI/UI/Xaml/Media/Animation/FadeOutThemeAnimation.cs index f09625e1c416..fccc5d22fe01 100644 --- a/src/Uno.UI/UI/Xaml/Media/Animation/FadeOutThemeAnimation.cs +++ b/src/Uno.UI/UI/Xaml/Media/Animation/FadeOutThemeAnimation.cs @@ -7,24 +7,6 @@ namespace Microsoft.UI.Xaml.Media.Animation { public partial class FadeOutThemeAnimation : DoubleAnimation { - public FadeOutThemeAnimation() - { - this.SetValue( - DurationProperty, - new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration), - DependencyPropertyValuePrecedences.DefaultValue); - - this.SetValue( - Storyboard.TargetPropertyProperty, - "Opacity", - DependencyPropertyValuePrecedences.DefaultValue); - - this.SetValue( - ToProperty, - (double?)0d, - DependencyPropertyValuePrecedences.DefaultValue); - } - public static DependencyProperty TargetNameProperty { get; } = DependencyProperty.Register( "TargetName", typeof(string), typeof(FadeOutThemeAnimation), new FrameworkPropertyMetadata(string.Empty)); diff --git a/src/Uno.UI/UI/Xaml/UIElement.cs b/src/Uno.UI/UI/Xaml/UIElement.cs index 1e70244020ba..950cdd53858e 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.cs @@ -522,13 +522,8 @@ protected virtual void OnVisibilityChanged(Visibility oldValue, Visibility newVi partial void OnVisibilityChangedPartial(Visibility oldValue, Visibility newValue); - /// - /// Set correct default foreground for the current theme. - /// - /// The appropriate property for the calling instance. - private protected void SetDefaultForeground(DependencyProperty foregroundProperty) + private protected void UpdateLastUsedTheme() { - this.SetValue(foregroundProperty, DefaultBrushes.TextForegroundBrush, DependencyPropertyValuePrecedences.DefaultValue); ((IDependencyObjectStoreProvider)this).Store.SetLastUsedTheme(Application.Current?.RequestedThemeForResources); } From 8d335416680e7e373eed0b52e3b8158115379ff7 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 09:03:37 +0300 Subject: [PATCH 10/24] chore: Adjust for unit testing --- ...iven_DependencyProperty.DefaultValueProvider.cs | 14 +------------- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 3 ++- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 2 +- 3 files changed, 4 insertions(+), 15 deletions(-) diff --git a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.DefaultValueProvider.cs b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.DefaultValueProvider.cs index 6899a18e6648..2705c608bff8 100644 --- a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.DefaultValueProvider.cs +++ b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.DefaultValueProvider.cs @@ -57,19 +57,7 @@ public void When_GetDefaultValue() var defaultValueTest = new DefaultValueTest(); Assert.AreEqual(expected, defaultValueTest.GetPrecedenceSpecificValue(DefaultValueTest.TestValueProperty, DependencyPropertyValuePrecedences.DefaultValue)); Assert.AreEqual(expected, defaultValueTest.GetValue(DefaultValueTest.TestValueProperty)); - Assert.AreEqual(expected, (defaultValueTest as IDependencyObjectStoreProvider).Store.GetPropertyDetails(DefaultValueTest.TestValueProperty).GetDefaultValue()); - Assert.AreEqual(expected, defaultValueTest.TestValue); - } - - [TestMethod] - public void When_SetDefaultValue() - { - var expected = 24; - var defaultValueTest = new DefaultValueTest(); - ((IDependencyObjectStoreProvider)defaultValueTest).Store.GetPropertyDetails(DefaultValueTest.TestValueProperty).SetDefaultValue(expected); - Assert.AreEqual(expected, defaultValueTest.GetPrecedenceSpecificValue(DefaultValueTest.TestValueProperty, DependencyPropertyValuePrecedences.DefaultValue)); - Assert.AreEqual(expected, defaultValueTest.GetValue(DefaultValueTest.TestValueProperty)); - Assert.AreEqual(expected, ((IDependencyObjectStoreProvider)defaultValueTest).Store.GetPropertyDetails(DefaultValueTest.TestValueProperty).GetDefaultValue()); + Assert.AreEqual(expected, (defaultValueTest as IDependencyObjectStoreProvider).Store.GetDefaultValue(DefaultValueTest.TestValueProperty)); Assert.AreEqual(expected, defaultValueTest.TestValue); } diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 2f882d211a3c..ed1c51c664d2 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -1104,7 +1104,8 @@ internal void RegisterParentChangedCallbackStrong(object key, ParentChangedCallb return (value, resultPrecedence); } - private object GetDefaultValue(DependencyProperty dp) + // Internal for unit testing + internal object GetDefaultValue(DependencyProperty dp) { var actualInstance = ActualInstance; var defaultValue = dp.GetDefaultValue(actualInstance, actualInstance?.GetType()); diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 325ce56d4e84..11ef830f82d1 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -537,7 +537,7 @@ internal object GetDefaultValue(DependencyObject referenceObject, Type forType) { if (referenceObject is FadeInThemeAnimation or FadeOutThemeAnimation) { - return FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration; + return new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration); } } From 48d8b872b56685c5f1e1b9ee59ef0c0d60c764d0 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 10:35:05 +0300 Subject: [PATCH 11/24] chore: Fix failing tests --- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index ed1c51c664d2..00d7973f7ad3 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -17,6 +17,7 @@ using System.Collections; using System.Globalization; using Windows.ApplicationModel.Calls; +using Microsoft.UI.Xaml.Controls; #if __ANDROID__ using View = Android.Views.View; @@ -1636,6 +1637,17 @@ void Propagate(DependencyObjectStore store) { store.OnParentPropertyChangedCallback(instanceRef, prop, GetValue(prop)); } + else + { + if (prop == _properties.DataContextPropertyDetails.Property || prop == _properties.TemplatedParentPropertyDetails.Property) + { + // Historically, the GetValue(prop) above was always called regardless of highest precedence. + // GetValue has a side effect of calling TryRegisterInheritedProperties. + // This tries to keep the original behavior, but it doesn't make sense that we need to do this. + // At least for TemplatedParent, this should be re-evaluated once we align TemplatedParent with WinUI. + TryRegisterInheritedProperties(force: true); + } + } } } } From 2f8557f696964d5d28c5e772e8ddb5020eb8e552 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 11:27:40 +0300 Subject: [PATCH 12/24] chore: Adjust --- .../Xaml/Controls/TextBlock/TextBlock.wasm.cs | 14 +++++------ .../UI/Xaml/FrameworkElement.Android.cs | 24 ++++++------------- 2 files changed, 14 insertions(+), 24 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs index e4ee8dd0aee4..5f6e819b0534 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs @@ -51,18 +51,18 @@ public TextBlock() : base("p") OnTextAlignmentChangedPartial(); OnTextWrappingChangedPartial(); OnIsTextSelectionEnabledChangedPartial(); - InitializeDefaultValues(); _hyperlinks.CollectionChanged += HyperlinksOnCollectionChanged; } - /// - /// Set default properties to vertical top. - /// In wasm, this behavior is closer to the default textblock property than stretch. - /// - private void InitializeDefaultValues() + internal override bool GetDefaultValue2(DependencyProperty property, out object defaultValue) { - this.SetValue(VerticalAlignmentProperty, VerticalAlignment.Top, DependencyPropertyValuePrecedences.DefaultValue); + if (property == VerticalAlignmentProperty) + { + // In wasm, this behavior is closer to the default TextBlock property than stretch. + defaultValue = VerticalAlignment.Top; + return true; + } } private void ConditionalUpdate(ref bool condition, Action action) diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs index 6a05a912791e..68fc152f4aff 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs @@ -20,6 +20,8 @@ public partial class FrameworkElement { private Size? _lastLayoutSize; private bool _constraintsChanged; + private bool? _stretchAffectsMeasure; + private bool _stretchAffectsMeasureDefault; /// /// The parent of the in the visual tree, which may differ from its (ie if it's a child of a native view). @@ -192,40 +194,28 @@ protected override void OnRemovedFromParent() partial void OnLoadedPartial() { // see StretchAffectsMeasure for details. - this.SetValue( - StretchAffectsMeasureProperty, - !(NativeVisualParent is DependencyObject), - DependencyPropertyValuePrecedences.DefaultValue - ); + _stretchAffectsMeasureDefault = NativeVisualParent is not DependencyObject; ReconfigureViewportPropagationPartial(); } private partial void ReconfigureViewportPropagationPartial(); - #region StretchAffectsMeasure DependencyProperty - /// /// Indicates whether stretching (HorizontalAlignment.Stretch and VerticalAlignment.Stretch) should affect the measured size of the FrameworkElement. /// Only set on a FrameworkElement if the parent is a native view whose layouting relies on the values of MeasuredWidth and MeasuredHeight to account for stretching. /// Note that this doesn't take Margins into account. /// /// - /// The is updated at each call, but may - /// be overridden by an external called as . + /// The is updated at each call, but may + /// be overridden by setting this property. /// public bool StretchAffectsMeasure { - get { return (bool)GetValue(StretchAffectsMeasureProperty); } - set { SetValue(StretchAffectsMeasureProperty, value); } + get => _stretchAffectsMeasure ?? _stretchAffectsMeasureDefault; + set => _stretchAffectsMeasure = value; } - // Using a DependencyProperty as the backing store for StretchAffectsMeasure. This enables animation, styling, binding, etc... - public static DependencyProperty StretchAffectsMeasureProperty { get; } = - DependencyProperty.Register("StretchAffectsMeasure", typeof(bool), typeof(FrameworkElement), new FrameworkPropertyMetadata(false)); - - #endregion - protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { ((ILayouterElement)this).OnMeasureInternal(widthMeasureSpec, heightMeasureSpec); From 040a787ca659685f6f926681a85b418a9689a4b7 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 11:29:37 +0300 Subject: [PATCH 13/24] chore: Remove workaround --- .../Controls/UITests/Views/Controls/SampleControl.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs index 5b23c020954d..9b7adaeb5f6b 100644 --- a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs +++ b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs @@ -54,16 +54,6 @@ private static void OnSampleContentChanged(object dependencyObject, DependencyPr { (dependencyObject as SampleControl).ContentTemplate = args.NewValue as DataTemplate; } - -#if XAMARIN - protected override void OnDataContextChanged() - { - base.OnDataContextChanged(); - - // Workaround to #10396: The DataContext of ContentTemplate should be ContentControl.DataContext if ContentControl.Content is not set. - this.SetValue(ContentProperty, DataContext, DependencyPropertyValuePrecedences.DefaultValue); - } -#endif } } #endif From 491bac0986f299ddfa709242ece7c9299277c4e7 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 11:44:05 +0300 Subject: [PATCH 14/24] chore: Fix build error --- src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs index 5f6e819b0534..b5b2dc6d017c 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs @@ -63,6 +63,9 @@ internal override bool GetDefaultValue2(DependencyProperty property, out object defaultValue = VerticalAlignment.Top; return true; } + + defaultValue = null; + return false; } private void ConditionalUpdate(ref bool condition, Action action) From d60d9dcc5524990bcec77caead0fee4049b50add Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 12:36:33 +0300 Subject: [PATCH 15/24] chore: Boxing improvements --- src/Uno.UI/Helpers/Boxes.cs | 58 +++++++++++++++++++ src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs | 2 +- .../Xaml/Controls/TextBlock/TextBlock.wasm.cs | 2 +- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 19 ++++-- src/Uno.UI/UI/Xaml/UIElement.cs | 2 +- 5 files changed, 76 insertions(+), 7 deletions(-) create mode 100644 src/Uno.UI/Helpers/Boxes.cs diff --git a/src/Uno.UI/Helpers/Boxes.cs b/src/Uno.UI/Helpers/Boxes.cs new file mode 100644 index 000000000000..fb69c6c93398 --- /dev/null +++ b/src/Uno.UI/Helpers/Boxes.cs @@ -0,0 +1,58 @@ +using System; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Media; +using Uno.UI.Xaml; + +namespace Uno.UI.Helpers; + + +internal static class Boxes +{ + public static class BooleanBoxes + { + public static readonly object BoxedTrue = true; + public static readonly object BoxedFalse = false; + } + + public static class VerticalAlignmentBoxes + { + public static readonly VerticalAlignment Top = VerticalAlignment.Top; + public static readonly VerticalAlignment Bottom = VerticalAlignment.Bottom; + public static readonly VerticalAlignment Stretch = VerticalAlignment.Stretch; + public static readonly VerticalAlignment Center = VerticalAlignment.Center; + } + + public static class NullableDoubleBoxes + { + public static readonly double? Zero = 0.0d; + public static readonly double? One = 1.0d; + } + + public static class StretchBoxes + { + public static readonly Stretch None = Stretch.None; + public static readonly Stretch Fill = Stretch.Fill; + public static readonly Stretch Uniform = Stretch.Uniform; + public static readonly Stretch UniformToFill = Stretch.UniformToFill; + } + + public static object Box(bool value) => value ? BooleanBoxes.BoxedTrue : BooleanBoxes.BoxedFalse; + + public static object Box(VerticalAlignment value) => value switch + { + VerticalAlignment.Top => VerticalAlignmentBoxes.Top, + VerticalAlignment.Bottom => VerticalAlignmentBoxes.Bottom, + VerticalAlignment.Stretch => VerticalAlignmentBoxes.Stretch, + VerticalAlignment.Center => VerticalAlignmentBoxes.Center, + _ => value, + }; + + public static object Box(Stretch value) => value switch + { + Stretch.None => StretchBoxes.None, + Stretch.Fill => StretchBoxes.Fill, + Stretch.Uniform => StretchBoxes.Uniform, + Stretch.UniformToFill => StretchBoxes.UniformToFill, + _ => value, + }; +} diff --git a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs index 23f3b0ca1a7c..35471f2709e4 100644 --- a/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs +++ b/src/Uno.UI/UI/Xaml/Controls/Popup/Popup.cs @@ -83,7 +83,7 @@ internal override bool GetDefaultValue2(DependencyProperty property, out object { if (property == IsLightDismissEnabledProperty) { - defaultValue = FeatureConfiguration.Popup.EnableLightDismissByDefault; + defaultValue = Uno.UI.Helpers.Boxes.Box(FeatureConfiguration.Popup.EnableLightDismissByDefault); return true; } return base.GetDefaultValue2(property, out defaultValue); diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs index b5b2dc6d017c..dc5aabe82085 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs @@ -60,7 +60,7 @@ internal override bool GetDefaultValue2(DependencyProperty property, out object if (property == VerticalAlignmentProperty) { // In wasm, this behavior is closer to the default TextBlock property than stretch. - defaultValue = VerticalAlignment.Top; + defaultValue = Uno.UI.Helpers.Boxes.VerticalAlignmentBoxes.Top; return true; } diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 11ef830f82d1..9d9b06529b69 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -16,6 +16,7 @@ using Uno.Extensions; using Uno.UI; using Uno.UI.Dispatching; +using Uno.UI.Helpers; using Uno.UI.Xaml.Media; #if __ANDROID__ @@ -39,6 +40,7 @@ public sealed partial class DependencyProperty private readonly static TypeToPropertiesDictionary _getPropertiesForType = new TypeToPropertiesDictionary(); private readonly static NameToPropertyDictionary _getPropertyCache = new NameToPropertyDictionary(); + private static object DefaultThemeAnimationDurationBox = new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration); /// /// A static used for lookups and avoid creating new instances. This assumes that uses are non-reentrant. @@ -529,7 +531,7 @@ internal object GetDefaultValue(DependencyObject referenceObject, Type forType) { if (forType == typeof(Rectangle) || forType == typeof(Ellipse)) { - return Stretch.Fill; + return Boxes.StretchBoxes.Fill; } } @@ -537,7 +539,16 @@ internal object GetDefaultValue(DependencyObject referenceObject, Type forType) { if (referenceObject is FadeInThemeAnimation or FadeOutThemeAnimation) { - return new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration); + if (((Duration)DefaultThemeAnimationDurationBox).TimeSpan == FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration) + { + // Our box is valid, so we can re-use it. + return DefaultThemeAnimationDurationBox; + } + + // Rare code path, it will be hit only once per change in the feature configuration. + // The cached box is not valid. So we create a new box update the cached box. + DefaultThemeAnimationDurationBox = new Duration(FeatureConfiguration.ThemeAnimation.DefaultThemeAnimationDuration); + return DefaultThemeAnimationDurationBox; } } @@ -553,11 +564,11 @@ internal object GetDefaultValue(DependencyObject referenceObject, Type forType) { if (referenceObject is FadeInThemeAnimation) { - return (double?)1.0d; + return Uno.UI.Helpers.Boxes.NullableDoubleBoxes.One; } else if (referenceObject is FadeOutThemeAnimation) { - return (double?)0.0d; + return Uno.UI.Helpers.Boxes.NullableDoubleBoxes.Zero; } } diff --git a/src/Uno.UI/UI/Xaml/UIElement.cs b/src/Uno.UI/UI/Xaml/UIElement.cs index 950cdd53858e..2753f624f8d1 100644 --- a/src/Uno.UI/UI/Xaml/UIElement.cs +++ b/src/Uno.UI/UI/Xaml/UIElement.cs @@ -217,7 +217,7 @@ internal virtual bool GetDefaultValue2(DependencyProperty property, out object d } else if (property == IsTabStopProperty) { - defaultValue = IsTabStopDefaultValue; + defaultValue = Uno.UI.Helpers.Boxes.Box(IsTabStopDefaultValue); return true; } From 049065b34df06ff22666c83b0ac164430b6327b9 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Sun, 25 Aug 2024 12:39:44 +0300 Subject: [PATCH 16/24] chore: Update PackageDiffIgnore --- build/PackageDiffIgnore.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index 192bb9a0910b..5b8c25b6b80d 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -1874,6 +1874,8 @@ + + @@ -1890,6 +1892,9 @@ + + + Date: Mon, 26 Aug 2024 11:51:05 +0300 Subject: [PATCH 17/24] chore: restore workaround --- .../UITests/Views/Controls/SampleControl.cs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs index 9b7adaeb5f6b..6c4ef225be9a 100644 --- a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs +++ b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs @@ -54,6 +54,20 @@ private static void OnSampleContentChanged(object dependencyObject, DependencyPr { (dependencyObject as SampleControl).ContentTemplate = args.NewValue as DataTemplate; } + +#if XAMARIN + internal override bool GetDefaultValue2(DependencyProperty property, out object defaultValue) + { + if (property == ContentProperty) + { + // Workaround to #10396: The DataContext of ContentTemplate should be ContentControl.DataContext if ContentControl.Content is not set. + defaultValue = DataContext; + return true; + } + + return base.GetDefaultValue2(property, out defaultValue); + } +#endif } } #endif From b18713aa390be49a715a37f6c92c3c72f692d664 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 26 Aug 2024 11:57:51 +0300 Subject: [PATCH 18/24] chore: Adjust --- .../UI/Xaml/DependencyObjectExtensions.cs | 3 ++- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 22 ++++++++-------- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 25 +++++++++++++------ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectExtensions.cs b/src/Uno.UI/UI/Xaml/DependencyObjectExtensions.cs index 0f34697bff58..f514834475cf 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectExtensions.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectExtensions.cs @@ -265,10 +265,11 @@ internal static (object value, DependencyPropertyValuePrecedences precedence) Ge internal static (object value, DependencyPropertyValuePrecedences precedence)[] GetValueForEachPrecedences( this DependencyObject instance, DependencyProperty property) { + var store = GetStore(instance); var propertyDetails = GetStore(instance).GetPropertyDetails(property).ToList(); return Enum.GetValues() - .Select(precedence => (propertyDetails[(int)precedence], precedence)) + .Select(precedence => (precedence == DependencyPropertyValuePrecedences.DefaultValue ? store.GetDefaultValue(property) : propertyDetails[(int)precedence], precedence)) .ToArray(); } diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 00d7973f7ad3..4e7061a6b567 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -282,6 +282,14 @@ private void Dispose(bool disposing) if (propertyDetails is null && (precedence is null || precedence == DependencyPropertyValuePrecedences.DefaultValue) && _properties.FindPropertyDetails(property) is null) { + if (_properties.DataContextPropertyDetails.Property == property || _properties.TemplatedParentPropertyDetails.Property == property) + { + // Historically, we didn't have this fast path for default value. + // We add this to maintain the original behavior in GetValue(DependencyPropertyDetails, DependencyPropertyValuePrecedences?, bool) overload. + // This should be revisited in future. + TryRegisterInheritedProperties(force: true); + } + // Performance: Avoid force-creating DependencyPropertyDetails when not needed. return GetDefaultValue(property); } @@ -1633,20 +1641,10 @@ void Propagate(DependencyObjectStore store) var prop = props[propertyIndex]; if (!IsTemplatedParentFrozen || prop != TemplatedParentProperty) { + var value = GetValue(prop); if (GetCurrentHighestValuePrecedence(prop) != DependencyPropertyValuePrecedences.DefaultValue) { - store.OnParentPropertyChangedCallback(instanceRef, prop, GetValue(prop)); - } - else - { - if (prop == _properties.DataContextPropertyDetails.Property || prop == _properties.TemplatedParentPropertyDetails.Property) - { - // Historically, the GetValue(prop) above was always called regardless of highest precedence. - // GetValue has a side effect of calling TryRegisterInheritedProperties. - // This tries to keep the original behavior, but it doesn't make sense that we need to do this. - // At least for TemplatedParent, this should be re-evaluated once we align TemplatedParent with WinUI. - TryRegisterInheritedProperties(force: true); - } + store.OnParentPropertyChangedCallback(instanceRef, prop, value); } } } diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 9d9b06529b69..0724528a949a 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -57,7 +57,6 @@ public sealed partial class DependencyProperty private Type _propertyType; private Type _ownerType; private readonly int _uniqueId; - private object _fallbackDefaultValue; private static int _globalId = -1; @@ -141,10 +140,7 @@ internal bool IsDependencyObjectCollection /// A property with the same name has already been declared for the ownerType public static DependencyProperty Register(string name, Type propertyType, Type ownerType, PropertyMetadata typeMetadata) { - if (!propertyType.IsNullable() && typeMetadata.DefaultValue is null) - { - throw new Exception($"Cannot register property {ownerType}.{name} with non-nullable type {propertyType}"); - } + typeMetadata = FixMetadataIfNeeded(propertyType, typeMetadata); var newProperty = new DependencyProperty(name, propertyType, ownerType, typeMetadata, attached: false); @@ -163,6 +159,20 @@ public static DependencyProperty Register(string name, Type propertyType, Type o return newProperty; } + private static PropertyMetadata FixMetadataIfNeeded(Type propertyType, PropertyMetadata metadata) + { + if (metadata is null) + { + return new PropertyMetadata(defaultValue: RuntimeHelpers.GetUninitializedObject(propertyType)); + } + else if (!propertyType.IsNullable() && metadata.DefaultValue is null) + { + return metadata.CloneWithOverwrittenDefaultValue(RuntimeHelpers.GetUninitializedObject(propertyType)); + } + + return metadata; + } + /// /// Registers a dependency property on the specified . /// @@ -192,6 +202,8 @@ internal static DependencyProperty Register(string name, Type propertyType, Type /// A property with the same name has already been declared for the ownerType public static DependencyProperty RegisterAttached(string name, Type propertyType, Type ownerType, PropertyMetadata defaultMetadata) { + defaultMetadata = FixMetadataIfNeeded(propertyType, defaultMetadata); + var newProperty = new DependencyProperty(name, propertyType, ownerType, defaultMetadata, attached: true); try @@ -284,9 +296,6 @@ internal Type Type internal bool IsTypeNullable => (_flags & Flags.IsTypeNullable) != 0; - internal object GetFallbackDefaultValue() - => _fallbackDefaultValue != null ? _fallbackDefaultValue : _fallbackDefaultValue = Activator.CreateInstance(Type); - internal string Name { get { return _name; } From 3fdae16ee68bc14c644ec478eceac6764be19f1f Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 26 Aug 2024 14:24:22 +0300 Subject: [PATCH 19/24] chore: Fix Metadata fixup logic --- src/Uno.UI/UI/Xaml/DependencyProperty.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Uno.UI/UI/Xaml/DependencyProperty.cs b/src/Uno.UI/UI/Xaml/DependencyProperty.cs index 0724528a949a..9f7b791c585e 100644 --- a/src/Uno.UI/UI/Xaml/DependencyProperty.cs +++ b/src/Uno.UI/UI/Xaml/DependencyProperty.cs @@ -163,7 +163,8 @@ private static PropertyMetadata FixMetadataIfNeeded(Type propertyType, PropertyM { if (metadata is null) { - return new PropertyMetadata(defaultValue: RuntimeHelpers.GetUninitializedObject(propertyType)); + var defaultValue = propertyType.IsNullable() ? null : RuntimeHelpers.GetUninitializedObject(propertyType); + return new PropertyMetadata(defaultValue); } else if (!propertyType.IsNullable() && metadata.DefaultValue is null) { From 4d4fc299e3f125294a0fdd3ae11ab930af4e29ad Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 26 Aug 2024 16:21:48 +0300 Subject: [PATCH 20/24] chore: Remove bad test (brushes shouldn't have DataContext in the first place) --- .../Given_DependencyProperty.Propagation.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.Propagation.cs b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.Propagation.cs index 9dda77a6d337..186585ec36a8 100644 --- a/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.Propagation.cs +++ b/src/Uno.UI.Tests/DependencyProperty/Given_DependencyProperty.Propagation.cs @@ -538,16 +538,10 @@ WeakReference Build() var dc = new object(); SUT.DataContext = dc; - var originalBrush = SUT.Foreground as Brush; - SUT.SetValue(ContentControl.ForegroundProperty, null); - Assert.IsNull(originalBrush.DataContext); - SUT.ClearValue(ContentControl.ForegroundProperty); - Assert.AreEqual(dc, originalBrush.DataContext); - SUT.DataContext = null; return new(dc); From 4441f6cd3cb0af78b7aa2c989de5929c9586548c Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 26 Aug 2024 16:36:16 +0300 Subject: [PATCH 21/24] chore: Fix --- src/Uno.UI/UI/Xaml/DependencyObjectStore.cs | 24 +++++++++++---------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs index 4e7061a6b567..1305066e97cb 100644 --- a/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs +++ b/src/Uno.UI/UI/Xaml/DependencyObjectStore.cs @@ -280,28 +280,30 @@ private void Dispose(bool disposing) ValidatePropertyOwner(property); - if (propertyDetails is null && (precedence is null || precedence == DependencyPropertyValuePrecedences.DefaultValue) && _properties.FindPropertyDetails(property) is null) + var callerRegisteredInheritedProperties = false; + if (_properties.DataContextPropertyDetails.Property == property || _properties.TemplatedParentPropertyDetails.Property == property) { - if (_properties.DataContextPropertyDetails.Property == property || _properties.TemplatedParentPropertyDetails.Property == property) - { - // Historically, we didn't have this fast path for default value. - // We add this to maintain the original behavior in GetValue(DependencyPropertyDetails, DependencyPropertyValuePrecedences?, bool) overload. - // This should be revisited in future. - TryRegisterInheritedProperties(force: true); - } + // Historically, we didn't have this fast path for default value. + // We add this to maintain the original behavior in GetValue(DependencyPropertyDetails, DependencyPropertyValuePrecedences?, bool) overload. + // This should be revisited in future. + TryRegisterInheritedProperties(force: true); + callerRegisteredInheritedProperties = true; + } + if (propertyDetails is null && (precedence is null || precedence == DependencyPropertyValuePrecedences.DefaultValue) && _properties.FindPropertyDetails(property) is null) + { // Performance: Avoid force-creating DependencyPropertyDetails when not needed. return GetDefaultValue(property); } propertyDetails ??= _properties.GetPropertyDetails(property); - return GetValue(propertyDetails, precedence, isPrecedenceSpecific); + return GetValue(propertyDetails, precedence, isPrecedenceSpecific, callerRegisteredInheritedProperties); } - private object? GetValue(DependencyPropertyDetails propertyDetails, DependencyPropertyValuePrecedences? precedence = null, bool isPrecedenceSpecific = false) + private object? GetValue(DependencyPropertyDetails propertyDetails, DependencyPropertyValuePrecedences? precedence = null, bool isPrecedenceSpecific = false, bool callerRegisteredInheritedProperties = false) { - if (propertyDetails == _properties.DataContextPropertyDetails || propertyDetails == _properties.TemplatedParentPropertyDetails) + if (!callerRegisteredInheritedProperties && (propertyDetails == _properties.DataContextPropertyDetails || propertyDetails == _properties.TemplatedParentPropertyDetails)) { TryRegisterInheritedProperties(force: true); } From 71dfb25eda41649795e04eb096cede821145e102 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Mon, 26 Aug 2024 22:08:16 +0300 Subject: [PATCH 22/24] chore: Add DP back --- build/PackageDiffIgnore.xml | 5 ---- .../UI/Xaml/FrameworkElement.Android.cs | 27 ++++++++++++++++--- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index 5b8c25b6b80d..192bb9a0910b 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -1874,8 +1874,6 @@ - - @@ -1892,9 +1890,6 @@ - - - /// Indicates whether stretching (HorizontalAlignment.Stretch and VerticalAlignment.Stretch) should affect the measured size of the FrameworkElement. /// Only set on a FrameworkElement if the parent is a native view whose layouting relies on the values of MeasuredWidth and MeasuredHeight to account for stretching. /// Note that this doesn't take Margins into account. /// /// - /// The is updated at each call, but may - /// be overridden by setting this property. + /// The is updated at each call, but may + /// be overridden by an external called as . /// public bool StretchAffectsMeasure { - get => _stretchAffectsMeasure ?? _stretchAffectsMeasureDefault; - set => _stretchAffectsMeasure = value; + get => (bool)GetValue(StretchAffectsMeasureProperty); + set => SetValue(StretchAffectsMeasureProperty, value); + } + + public static DependencyProperty StretchAffectsMeasureProperty { get; } = + DependencyProperty.Register(nameof(StretchAffectsMeasure), typeof(bool), typeof(FrameworkElement), new FrameworkPropertyMetadata(false)); + + internal override bool GetDefaultValue2(DependencyProperty property, out object defaultValue) + { + if (property == StretchAffectsMeasureProperty) + { + defaultValue = _stretchAffectsMeasureDefault; + return true; + } + + defaultValue = null; + return false; } + #endregion + protected override void OnMeasure(int widthMeasureSpec, int heightMeasureSpec) { ((ILayouterElement)this).OnMeasureInternal(widthMeasureSpec, heightMeasureSpec); From 2944aa055f52feb5ffc93fb0be042a7f0bcc959a Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Tue, 27 Aug 2024 10:34:25 +0300 Subject: [PATCH 23/24] chore: Small fixes --- src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs | 3 +-- src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs index dc5aabe82085..d0d87f3760f5 100644 --- a/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs +++ b/src/Uno.UI/UI/Xaml/Controls/TextBlock/TextBlock.wasm.cs @@ -64,8 +64,7 @@ internal override bool GetDefaultValue2(DependencyProperty property, out object return true; } - defaultValue = null; - return false; + return base.GetDefaultValue2(property, out defaultValue); } private void ConditionalUpdate(ref bool condition, Action action) diff --git a/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs b/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs index cc5b82f51fdd..97c1bd3d056e 100644 --- a/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs +++ b/src/Uno.UI/UI/Xaml/FrameworkElement.Android.cs @@ -229,8 +229,7 @@ internal override bool GetDefaultValue2(DependencyProperty property, out object return true; } - defaultValue = null; - return false; + return base.GetDefaultValue2(property, out defaultValue); } #endregion From af1dd438d391640ffc262c5ed6120a4e2b0660b4 Mon Sep 17 00:00:00 2001 From: Youssef Victor Date: Wed, 28 Aug 2024 12:59:32 +0300 Subject: [PATCH 24/24] chore: Try to fix iOS failure --- .../Controls/UITests/Views/Controls/SampleControl.cs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs index 6c4ef225be9a..88a20ebd4410 100644 --- a/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs +++ b/src/SamplesApp/SamplesApp.UnitTests.Shared/Controls/UITests/Views/Controls/SampleControl.cs @@ -56,16 +56,12 @@ private static void OnSampleContentChanged(object dependencyObject, DependencyPr } #if XAMARIN - internal override bool GetDefaultValue2(DependencyProperty property, out object defaultValue) + protected override void OnDataContextChanged() { - if (property == ContentProperty) - { - // Workaround to #10396: The DataContext of ContentTemplate should be ContentControl.DataContext if ContentControl.Content is not set. - defaultValue = DataContext; - return true; - } + base.OnDataContextChanged(); - return base.GetDefaultValue2(property, out defaultValue); + // Workaround to #10396: The DataContext of ContentTemplate should be ContentControl.DataContext if ContentControl.Content is not set. + this.SetValue(ContentProperty, DataContext, DependencyPropertyValuePrecedences.DefaultStyle); } #endif }