From e3484137a637dec46a4fc71026b289ae6c870518 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 19 Sep 2023 17:27:30 -0400 Subject: [PATCH 01/11] fix(binding): Avoir reading source property on UpdateSource for non-DP (cherry picked from commit 203609dd1e31d13099b1e282e0b0329eecd2ed26) # Conflicts: # src/Uno.UI/DataBinding/BindingPath.BindingItem.cs --- .../Given_ComboBox.cs | 81 +++ .../DataBinding/BindingPath.BindingItem.cs | 587 ++++++++++++++++++ src/Uno.UI/DataBinding/BindingPath.cs | 12 +- 3 files changed, 677 insertions(+), 3 deletions(-) create mode 100644 src/Uno.UI/DataBinding/BindingPath.BindingItem.cs diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs index 0f8695dbcfb5..bf5e4f499bda 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Controls/Given_ComboBox.cs @@ -862,6 +862,85 @@ private async Task TakeScreenshot(FrameworkElement SUT) } #endif + [TestMethod] + public async Task When_SelectedItem_TwoWay_Binding_Clear() + { + var root = new Grid(); + + var comboBox = new ComboBox(); + + root.Children.Add(comboBox); + + comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding { Path = new("Items") }); + comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding { Path = new("Item"), Mode = BindingMode.TwoWay }); + + WindowHelper.WindowContent = root; + + var dc = new TwoWayBindingClearViewModel(); + root.DataContext = dc; + + await WindowHelper.WaitForIdle(); + + dc.Dispose(); + root.DataContext = null; + + Assert.AreEqual(1, dc.ItemGetCount); + Assert.AreEqual(1, dc.ItemSetCount); + } + + public sealed class TwoWayBindingClearViewModel : IDisposable + { + public enum Themes + { + Invalid, + Day, + Night + } + + public TwoWayBindingClearViewModel() + { + _item = Items[0]; + } + + public Themes[] Items { get; } = new Themes[] { Themes.Day, Themes.Night }; + private Themes _item; + private bool _isDisposed; + public Themes Item + { + get + { + ItemGetCount++; + + if (_isDisposed) + { + return Themes.Invalid; + } + + return _item; + } + set + { + ItemSetCount++; + + if (_isDisposed) + { + _item = Themes.Invalid; + return; + } + + _item = value; + } + } + + public int ItemGetCount { get; private set; } + public int ItemSetCount { get; private set; } + + public void Dispose() + { + _isDisposed = true; + } + } + public class TwoWayBindingItem : System.ComponentModel.INotifyPropertyChanged { private int _selectedNumber; @@ -932,6 +1011,8 @@ protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) } } + + #if __IOS__ #region "Helper classes for the iOS Modal Page (UIModalPresentationStyle.pageSheet)" public partial class MultiFrame : Grid diff --git a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs new file mode 100644 index 000000000000..cad3862a40ff --- /dev/null +++ b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs @@ -0,0 +1,587 @@ +#nullable enable + +#if !NETFX_CORE +using System; +using Uno.Disposables; +using Uno.Foundation.Logging; +using Uno.UI.Xaml.Input; +using Windows.ApplicationModel.Appointments; +using Windows.UI.Xaml; + +namespace Uno.UI.DataBinding +{ + internal partial class BindingPath + { + private sealed class BindingItem : IBindingItem, IDisposable + { + private delegate void PropertyChangedHandler(object? previousValue, object? newValue, bool shouldRaiseValueChanged); + + private ManagedWeakReference? _dataContextWeakStorage; + private Flags _flags; + + private readonly SerialDisposable _propertyChanged = new SerialDisposable(); + private readonly DependencyPropertyValuePrecedences? _precedence; + private ValueGetterHandler? _valueGetter; + private ValueGetterHandler? _precedenceSpecificGetter; + private ValueGetterHandler? _substituteValueGetter; + private ValueSetterHandler? _valueSetter; + private ValueSetterHandler? _localValueSetter; + private ValueSetterHandler? _animationFillingValueSetter; + private ValueUnsetterHandler? _valueUnsetter; + private ValueUnsetterHandler? _animationFillingValueUnsetter; + + private Type? _dataContextType; + + [Flags] + private enum Flags + { + None = 0, + Disposed = 1 << 0, + AllowPrivateMembers = 1 << 1, + IsDependencyProperty = 1 << 2, + IsDependencyPropertyValueSet = 1 << 3, + } + + public BindingItem(BindingItem next, string property) : + this(next, property, null, false) + { + } + + internal BindingItem(BindingItem? next, string property, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers) + { + Next = next; + PropertyName = property; + _precedence = precedence; + AllowPrivateMembers = allowPrivateMembers; + } + + public object? DataContext + { + get => _dataContextWeakStorage?.Target; + set + { + if (!IsDisposed) + { + // Historically, Uno was processing property changes using INPC. Since the inclusion of DependencyObject + // values changes are now filtered by DependencyProperty updates, making equality updates at this location + // detrimental to the use of INPC events processing. + // In case of an INPC, the bindings engine must reevaluate the path completely from the raising point, regardless + // of the reference being changed. + if (FeatureConfiguration.Binding.IgnoreINPCSameReferences && !DependencyObjectStore.AreDifferent(DataContext, value)) + { + return; + } + + var weakDataContext = WeakReferencePool.RentWeakReference(this, value); + SetWeakDataContext(weakDataContext); + } + } + } + + internal void SetWeakDataContext(ManagedWeakReference? weakDataContext, bool transferReferenceOwnership = false) + { + var previousStorage = _dataContextWeakStorage; + + _dataContextWeakStorage = weakDataContext; + OnDataContextChanged(); + + // Return the reference to the pool after it's been released from the next BindingItem instances. + // Failing to do so makes the reference change without the bindings knowing about it, + // making the reference comparison always equal. + Uno.UI.DataBinding.WeakReferencePool.ReturnWeakReference(this, previousStorage); + } + + public BindingItem? Next { get; } + public string PropertyName { get; } + + public IValueChangedListener? ValueChangedListener { get; set; } + + public object? Value + { + get + { + return GetSourceValue(); + } + set + { + SetValue(value); + } + } + + /// + /// Sets the value using the + /// + /// The value to set + private void SetValue(object? value) + { + BuildValueSetter(); + SetSourceValue(_valueSetter!, value); + } + + /// + /// Sets the value using the + /// + /// The value to set + public void SetLocalValue(object value) + { + BuildLocalValueSetter(); + SetSourceValue(_localValueSetter!, value); + } + + public void SetAnimationFillingValue(object value) + { + BuildAnimationFillingValueSetter(); + SetSourceValue(_animationFillingValueSetter!, value); + } + + public Type? PropertyType + { + get + { + if (DataContext != null) + { + return BindingPropertyHelper.GetPropertyType(_dataContextType!, PropertyName, AllowPrivateMembers); + } + else + { + return null; + } + } + } + + internal object? GetPrecedenceSpecificValue() + { + BuildPrecedenceSpecificValueGetter(); + + return GetSourceValue(_precedenceSpecificGetter!); + } + + internal object? GetSubstituteValue() + { + BuildSubstituteValueGetter(); + + return GetSourceValue(_substituteValueGetter!); + } + + private bool _isDataContextChanging; + + private void OnDataContextChanged() + { + if (DataContext != null) + { + ClearCachedGetters(); + if (_propertyChanged.Disposable != null) + { +#if !HAS_EXPENSIVE_TRYFINALLY // Try/finally incurs a very large performance hit in mono-wasm - https://github.com/dotnet/runtime/issues/50783 + try +#endif + { + _isDataContextChanging = true; + _propertyChanged.Disposable = null; + } +#if !HAS_EXPENSIVE_TRYFINALLY // Try/finally incurs a very large performance hit in mono-wasm - https://github.com/dotnet/runtime/issues/50783 + finally +#endif + { + _isDataContextChanging = false; + } + } + + _propertyChanged.Disposable = SubscribeToPropertyChanged(); + + RaiseValueChanged(Value); + + if (Next != null) + { + Next.DataContext = Value; + } + } + else + { + if (Next != null) + { + Next.DataContext = null; + } + RaiseValueChanged(null); + + _propertyChanged.Disposable = null; + } + } + + private void OnPropertyChanged(object? previousValue, object? newValue, bool shouldRaiseValueChanged) + { + if (_isDataContextChanging && newValue is UnsetValue) + { + // We're in a "resubscribe" scenario when the DataContext is provided a new non-null value, so we don't need to + // pass through the DependencyProperty.UnsetValue. + // We simply discard this update. + return; + } + + if (Next != null) + { + Next.DataContext = newValue; + } + + if (shouldRaiseValueChanged && previousValue != newValue) + { + RaiseValueChanged(newValue); + } + } + + private void ClearCachedGetters() + { + var currentType = DataContext!.GetType(); + + if (_dataContextType != currentType && _dataContextType != null) + { + IsDependencyPropertyValueSet = false; + _valueGetter = null; + _precedenceSpecificGetter = null; + _substituteValueGetter = null; + _localValueSetter = null; + _valueSetter = null; + _valueUnsetter = null; + } + + _dataContextType = currentType; + } + + private void BuildValueSetter() + { + if (_valueSetter == null && _dataContextType != null) + { + _valueSetter = BindingPropertyHelper.GetValueSetter( + _dataContextType, + PropertyName, + convert: true, + precedence: _precedence ?? DependencyPropertyValuePrecedences.Local + ); + if (_precedence == null) + { + _localValueSetter = _valueSetter; + } + } + } + + private void BuildLocalValueSetter() + { + if (_localValueSetter == null && _dataContextType != null) + { + _localValueSetter = BindingPropertyHelper.GetValueSetter( + _dataContextType, + PropertyName, + convert: true, + precedence: DependencyPropertyValuePrecedences.Local + ); + } + } + + private void BuildAnimationFillingValueSetter() + { + if (_animationFillingValueSetter == null && _dataContextType != null) + { + _animationFillingValueSetter = BindingPropertyHelper.GetValueSetter( + _dataContextType, + PropertyName, + convert: true, + precedence: DependencyPropertyValuePrecedences.FillingAnimations + ); + } + } + + private void SetSourceValue(ValueSetterHandler setter, object? value) + { + // Capture the datacontext before the call to avoid a race condition with the GC. + var dataContext = DataContext; + + if (dataContext != null) + { + try + { + setter(dataContext, value); + } + catch (Exception exception) + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Error)) + { + this.Log().Error($"Failed to set the source value for [{PropertyName}]", exception); + } + } + } + else + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) + { + this.Log().DebugFormat("Setting [{0}] failed because the DataContext is null for. It may have already been collected, or explicitly set to null.", PropertyName); + } + } + } + + private void BuildValueGetter() + { + if (_valueGetter == null && _dataContextType != null) + { + _valueGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); + } + } + + private void BuildPrecedenceSpecificValueGetter() + { + if (_precedenceSpecificGetter == null && _dataContextType != null) + { + _precedenceSpecificGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); + } + } + + private void BuildSubstituteValueGetter() + { + if (_substituteValueGetter == null && _dataContextType != null) + { + _substituteValueGetter = + BindingPropertyHelper.GetSubstituteValueGetter(_dataContextType, PropertyName, _precedence ?? DependencyPropertyValuePrecedences.Local); + } + } + + private object? GetSourceValue() + { + BuildValueGetter(); + + return GetSourceValue(_valueGetter!); + } + + private object? GetSourceValue(ValueGetterHandler getter) + { + // Capture the datacontext before the call to avoid a race condition with the GC. + var dataContext = DataContext; + + if (dataContext != null) + { + try + { + return getter(dataContext); + } + catch (Exception exception) + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Error)) + { + this.Log().Error($"Failed to get the source value for [{PropertyName}]", exception); + } + + return DependencyProperty.UnsetValue; + } + } + else + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) + { + this.Log().DebugFormat("Unable to get the source value for [{0}]", PropertyName); + } + + return null; + } + } + + private void BuildValueUnsetter() + { + if (_valueUnsetter == null && _dataContextType != null) + { + _valueUnsetter = _precedence == null ? + BindingPropertyHelper.GetValueUnsetter(_dataContextType, PropertyName) : + BindingPropertyHelper.GetValueUnsetter(_dataContextType, PropertyName, precedence: _precedence.Value); + } + } + + internal void ClearValue() + { + BuildValueUnsetter(); + + // Capture the datacontext before the call to avoid a race condition with the GC. + var dataContext = DataContext; + + if (dataContext != null) + { + _valueUnsetter!(dataContext); + } + else + { + if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) + { + this.Log().DebugFormat("Unsetting [{0}] failed because the DataContext is null for. It may have already been collected, or explicitly set to null.", PropertyName); + } + } + } + + private void BuildAnimationFillingValueUnsetter() + { + if (_animationFillingValueUnsetter == null && _dataContextType != null) + { + _animationFillingValueUnsetter = BindingPropertyHelper.GetValueUnsetter( + _dataContextType, + PropertyName, + precedence: DependencyPropertyValuePrecedences.FillingAnimations + ); + } + } + + internal void ClearAnimationFillingValue() + { + BuildAnimationFillingValueUnsetter(); + + // Capture the datacontext before the call to avoid a race condition with the GC. + var dataContext = DataContext; + if (dataContext != null) + { + _animationFillingValueUnsetter!(dataContext); + } + } + + private void RaiseValueChanged(object? newValue) + { + ValueChangedListener?.OnValueChanged(newValue); + } + + /// + /// Subscribes to property notifications for the current binding + /// + /// The action to execute when new values are raised + /// A disposable to be called when the subscription is disposed. + private IDisposable SubscribeToPropertyChanged() + { + var disposables = new CompositeDisposable(_propertyChangedHandlers.Count + 1); + if (SubscribeToNotifyCollectionChanged(this) is { } notifyCollectionChangedDisposable) + { + disposables.Add(notifyCollectionChangedDisposable); + } + + for (var i = 0; i < _propertyChangedHandlers.Count; i++) + { + var handler = _propertyChangedHandlers[i]; + + var valueHandler = new PropertyChangedValueHandler(this); + + var handlerDisposable = handler.Register(_dataContextWeakStorage!, PropertyName, valueHandler); + + if (handlerDisposable != null) + { + valueHandler.PreviousValue = GetSourceValue(); + + // We need to keep the reference to the updatePropertyHandler + // in this disposable. The reference is attached to the source's + // object lifetime, to the target (bound) object. + // + // All registrations made by _propertyChangedHandlers are + // weak with regards to the delegates that are provided. + disposables.Add(() => + { + var previousValue = valueHandler.PreviousValue; + + valueHandler = null; + handlerDisposable.Dispose(); + OnPropertyChanged(previousValue, DependencyProperty.UnsetValue, shouldRaiseValueChanged: false); + }); + } + } + + return disposables; + } + + public void Dispose() + { + IsDisposed = true; + _propertyChanged.Dispose(); + } + + private bool IsDisposed + { + get => _flags.HasFlag(Flags.Disposed); + set => SetFlag(value, Flags.Disposed); + } + + private bool AllowPrivateMembers + { + get => _flags.HasFlag(Flags.AllowPrivateMembers); + set => SetFlag(value, Flags.AllowPrivateMembers); + } + + private bool IsDependencyPropertyValueSet + { + get => _flags.HasFlag(Flags.IsDependencyPropertyValueSet); + set => SetFlag(value, Flags.IsDependencyPropertyValueSet); + } + + internal bool IsDependencyProperty + { + get + { + if (!IsDependencyPropertyValueSet) + { + var isDP = + _dataContextType is not null + && DependencyProperty.GetProperty(_dataContextType!, PropertyName) is not null; + + SetFlag(isDP, Flags.IsDependencyProperty); + + IsDependencyPropertyValueSet = true; + + return isDP; + } + else + { + return _flags.HasFlag(Flags.IsDependencyProperty); + } + } + } + + private void SetFlag(bool value, Flags flag) + { + if (!value) + { + _flags &= ~flag; + } + else + { + _flags |= flag; + } + } + + /// + /// Property changed value handler, used to avoid creating a delegate for processing + /// + /// + /// This class is primarily used to avoid the costs associated with creating, storing and invoking delegates, + /// particularly on WebAssembly as of .NET 6 where invoking a delegate requires a context switch from AOT + /// to the interpreter. + /// + private class PropertyChangedValueHandler : IPropertyChangedValueHandler, IWeakReferenceProvider + { + private readonly BindingItem _owner; + private readonly ManagedWeakReference _self; + + public PropertyChangedValueHandler(BindingItem owner) + { + _owner = owner; + _self = WeakReferencePool.RentSelfWeakReference(this); + } + + public object? PreviousValue { get; set; } + + public ManagedWeakReference WeakReference + => _self; + + public void NewValue() + { + var newValue = _owner.GetSourceValue(); + + _owner.OnPropertyChanged(PreviousValue, newValue, shouldRaiseValueChanged: true); + + PreviousValue = newValue; + } + + public void NewValue(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) + => NewValue(); + } + } + } +} +#endif diff --git a/src/Uno.UI/DataBinding/BindingPath.cs b/src/Uno.UI/DataBinding/BindingPath.cs index 000bf246d2db..a48da4545044 100644 --- a/src/Uno.UI/DataBinding/BindingPath.cs +++ b/src/Uno.UI/DataBinding/BindingPath.cs @@ -195,8 +195,14 @@ public object? Value { if (!_disposed && _value != null - && DependencyObjectStore.AreDifferent(value, _value.GetPrecedenceSpecificValue()) - ) + && ( + !_value.IsDependencyProperty + + // Don't get the source value if we're not accessing a dependency property. + // WinUI does not read the property value before setting the value for a + // non-dependency property source. + || DependencyObjectStore.AreDifferent(value, _value.GetPrecedenceSpecificValue()) + )) { _value.Value = value; } @@ -428,7 +434,7 @@ private static void TryPrependItem( } var itemPath = path.Substring(start, length); - var item = new BindingItem(head, itemPath, fallbackValue, precedence, allowPrivateMembers); + var item = new BindingItem(head, itemPath, precedence, allowPrivateMembers); head = item; tail ??= item; From 731345f76fad99e5580740bff9e904879db1d054 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Tue, 19 Sep 2023 20:43:45 -0400 Subject: [PATCH 02/11] chore: Adjust for removed dedup on POCO properties (cherry picked from commit 1598e0496727c167bf2d5717ccda91b1d0460ed4) --- .../Given_ObjectAnimationUsingKeyFrames.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Animation/Given_ObjectAnimationUsingKeyFrames.cs b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Animation/Given_ObjectAnimationUsingKeyFrames.cs index 99e0cc914b16..740ca673f77f 100644 --- a/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Animation/Given_ObjectAnimationUsingKeyFrames.cs +++ b/src/Uno.UI.RuntimeTests/Tests/Windows_UI_Xaml_Media_Animation/Given_ObjectAnimationUsingKeyFrames.cs @@ -74,7 +74,9 @@ public async Task When_Animate() await target.GetValue(ct, 3); await Task.Yield(); - target.History.Should().BeEquivalentTo(v1, v2, v3); + // v3 is repeated because the target property is not a DependencyProperty + // and no deduplication happens in the binding engine. + target.History.Should().BeEquivalentTo(v1, v2, v3, v3); sut.State.Should().Be(Timeline.TimelineState.Filling); } @@ -147,7 +149,9 @@ public async Task When_PauseResume() await target.GetValue(ct, 3); await Task.Yield(); - target.History.Should().BeEquivalentTo(v1, v2, v3); + // v3 is repeated because the target property is not a DependencyProperty + // and no deduplication happens in the binding engine. + target.History.Should().BeEquivalentTo(v1, v2, v3, v3); sut.State.Should().Be(Timeline.TimelineState.Filling); } @@ -178,7 +182,9 @@ public async Task When_RepeatCount() await target.GetValue(ct, 9); await Task.Yield(); - target.History.Should().BeEquivalentTo(v1, v2, v3, v1, v2, v3, v1, v2, v3); + // v3 is repeated because the target property is not a DependencyProperty + // and no deduplication happens in the binding engine. + target.History.Should().BeEquivalentTo(v1, v2, v3, v1, v2, v3, v1, v2, v3, v3); sut.State.Should().Be(Timeline.TimelineState.Filling); } From 80c79ee7bc179678f8c2089234048ef84f2b4f07 Mon Sep 17 00:00:00 2001 From: Jerome Laban Date: Wed, 20 Sep 2023 07:25:10 -0400 Subject: [PATCH 03/11] chore: Avoid HasFlags boxing (cherry picked from commit a9199997cc414b8cd1525b2e332b54c2aec85445) --- src/Uno.UI/DataBinding/BindingPath.BindingItem.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs index cad3862a40ff..476a35b6b395 100644 --- a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs +++ b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs @@ -494,19 +494,19 @@ public void Dispose() private bool IsDisposed { - get => _flags.HasFlag(Flags.Disposed); + get => (_flags & Flags.Disposed) != 0; set => SetFlag(value, Flags.Disposed); } private bool AllowPrivateMembers { - get => _flags.HasFlag(Flags.AllowPrivateMembers); + get => (_flags & Flags.AllowPrivateMembers) != 0; set => SetFlag(value, Flags.AllowPrivateMembers); } private bool IsDependencyPropertyValueSet { - get => _flags.HasFlag(Flags.IsDependencyPropertyValueSet); + get => (_flags & Flags.IsDependencyPropertyValueSet) != 0; set => SetFlag(value, Flags.IsDependencyPropertyValueSet); } @@ -528,7 +528,7 @@ _dataContextType is not null } else { - return _flags.HasFlag(Flags.IsDependencyProperty); + return (_flags & Flags.IsDependencyProperty) != 0; } } } From 998ccd04ef8f50d1855fbf98824a32a8817c59bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 20 Sep 2023 19:25:22 -0400 Subject: [PATCH 04/11] chore: Adjust for backport --- .../DataBinding/BindingPath.BindingItem.cs | 587 ------------------ src/Uno.UI/DataBinding/BindingPath.cs | 89 ++- 2 files changed, 75 insertions(+), 601 deletions(-) delete mode 100644 src/Uno.UI/DataBinding/BindingPath.BindingItem.cs diff --git a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs b/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs deleted file mode 100644 index 476a35b6b395..000000000000 --- a/src/Uno.UI/DataBinding/BindingPath.BindingItem.cs +++ /dev/null @@ -1,587 +0,0 @@ -#nullable enable - -#if !NETFX_CORE -using System; -using Uno.Disposables; -using Uno.Foundation.Logging; -using Uno.UI.Xaml.Input; -using Windows.ApplicationModel.Appointments; -using Windows.UI.Xaml; - -namespace Uno.UI.DataBinding -{ - internal partial class BindingPath - { - private sealed class BindingItem : IBindingItem, IDisposable - { - private delegate void PropertyChangedHandler(object? previousValue, object? newValue, bool shouldRaiseValueChanged); - - private ManagedWeakReference? _dataContextWeakStorage; - private Flags _flags; - - private readonly SerialDisposable _propertyChanged = new SerialDisposable(); - private readonly DependencyPropertyValuePrecedences? _precedence; - private ValueGetterHandler? _valueGetter; - private ValueGetterHandler? _precedenceSpecificGetter; - private ValueGetterHandler? _substituteValueGetter; - private ValueSetterHandler? _valueSetter; - private ValueSetterHandler? _localValueSetter; - private ValueSetterHandler? _animationFillingValueSetter; - private ValueUnsetterHandler? _valueUnsetter; - private ValueUnsetterHandler? _animationFillingValueUnsetter; - - private Type? _dataContextType; - - [Flags] - private enum Flags - { - None = 0, - Disposed = 1 << 0, - AllowPrivateMembers = 1 << 1, - IsDependencyProperty = 1 << 2, - IsDependencyPropertyValueSet = 1 << 3, - } - - public BindingItem(BindingItem next, string property) : - this(next, property, null, false) - { - } - - internal BindingItem(BindingItem? next, string property, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers) - { - Next = next; - PropertyName = property; - _precedence = precedence; - AllowPrivateMembers = allowPrivateMembers; - } - - public object? DataContext - { - get => _dataContextWeakStorage?.Target; - set - { - if (!IsDisposed) - { - // Historically, Uno was processing property changes using INPC. Since the inclusion of DependencyObject - // values changes are now filtered by DependencyProperty updates, making equality updates at this location - // detrimental to the use of INPC events processing. - // In case of an INPC, the bindings engine must reevaluate the path completely from the raising point, regardless - // of the reference being changed. - if (FeatureConfiguration.Binding.IgnoreINPCSameReferences && !DependencyObjectStore.AreDifferent(DataContext, value)) - { - return; - } - - var weakDataContext = WeakReferencePool.RentWeakReference(this, value); - SetWeakDataContext(weakDataContext); - } - } - } - - internal void SetWeakDataContext(ManagedWeakReference? weakDataContext, bool transferReferenceOwnership = false) - { - var previousStorage = _dataContextWeakStorage; - - _dataContextWeakStorage = weakDataContext; - OnDataContextChanged(); - - // Return the reference to the pool after it's been released from the next BindingItem instances. - // Failing to do so makes the reference change without the bindings knowing about it, - // making the reference comparison always equal. - Uno.UI.DataBinding.WeakReferencePool.ReturnWeakReference(this, previousStorage); - } - - public BindingItem? Next { get; } - public string PropertyName { get; } - - public IValueChangedListener? ValueChangedListener { get; set; } - - public object? Value - { - get - { - return GetSourceValue(); - } - set - { - SetValue(value); - } - } - - /// - /// Sets the value using the - /// - /// The value to set - private void SetValue(object? value) - { - BuildValueSetter(); - SetSourceValue(_valueSetter!, value); - } - - /// - /// Sets the value using the - /// - /// The value to set - public void SetLocalValue(object value) - { - BuildLocalValueSetter(); - SetSourceValue(_localValueSetter!, value); - } - - public void SetAnimationFillingValue(object value) - { - BuildAnimationFillingValueSetter(); - SetSourceValue(_animationFillingValueSetter!, value); - } - - public Type? PropertyType - { - get - { - if (DataContext != null) - { - return BindingPropertyHelper.GetPropertyType(_dataContextType!, PropertyName, AllowPrivateMembers); - } - else - { - return null; - } - } - } - - internal object? GetPrecedenceSpecificValue() - { - BuildPrecedenceSpecificValueGetter(); - - return GetSourceValue(_precedenceSpecificGetter!); - } - - internal object? GetSubstituteValue() - { - BuildSubstituteValueGetter(); - - return GetSourceValue(_substituteValueGetter!); - } - - private bool _isDataContextChanging; - - private void OnDataContextChanged() - { - if (DataContext != null) - { - ClearCachedGetters(); - if (_propertyChanged.Disposable != null) - { -#if !HAS_EXPENSIVE_TRYFINALLY // Try/finally incurs a very large performance hit in mono-wasm - https://github.com/dotnet/runtime/issues/50783 - try -#endif - { - _isDataContextChanging = true; - _propertyChanged.Disposable = null; - } -#if !HAS_EXPENSIVE_TRYFINALLY // Try/finally incurs a very large performance hit in mono-wasm - https://github.com/dotnet/runtime/issues/50783 - finally -#endif - { - _isDataContextChanging = false; - } - } - - _propertyChanged.Disposable = SubscribeToPropertyChanged(); - - RaiseValueChanged(Value); - - if (Next != null) - { - Next.DataContext = Value; - } - } - else - { - if (Next != null) - { - Next.DataContext = null; - } - RaiseValueChanged(null); - - _propertyChanged.Disposable = null; - } - } - - private void OnPropertyChanged(object? previousValue, object? newValue, bool shouldRaiseValueChanged) - { - if (_isDataContextChanging && newValue is UnsetValue) - { - // We're in a "resubscribe" scenario when the DataContext is provided a new non-null value, so we don't need to - // pass through the DependencyProperty.UnsetValue. - // We simply discard this update. - return; - } - - if (Next != null) - { - Next.DataContext = newValue; - } - - if (shouldRaiseValueChanged && previousValue != newValue) - { - RaiseValueChanged(newValue); - } - } - - private void ClearCachedGetters() - { - var currentType = DataContext!.GetType(); - - if (_dataContextType != currentType && _dataContextType != null) - { - IsDependencyPropertyValueSet = false; - _valueGetter = null; - _precedenceSpecificGetter = null; - _substituteValueGetter = null; - _localValueSetter = null; - _valueSetter = null; - _valueUnsetter = null; - } - - _dataContextType = currentType; - } - - private void BuildValueSetter() - { - if (_valueSetter == null && _dataContextType != null) - { - _valueSetter = BindingPropertyHelper.GetValueSetter( - _dataContextType, - PropertyName, - convert: true, - precedence: _precedence ?? DependencyPropertyValuePrecedences.Local - ); - if (_precedence == null) - { - _localValueSetter = _valueSetter; - } - } - } - - private void BuildLocalValueSetter() - { - if (_localValueSetter == null && _dataContextType != null) - { - _localValueSetter = BindingPropertyHelper.GetValueSetter( - _dataContextType, - PropertyName, - convert: true, - precedence: DependencyPropertyValuePrecedences.Local - ); - } - } - - private void BuildAnimationFillingValueSetter() - { - if (_animationFillingValueSetter == null && _dataContextType != null) - { - _animationFillingValueSetter = BindingPropertyHelper.GetValueSetter( - _dataContextType, - PropertyName, - convert: true, - precedence: DependencyPropertyValuePrecedences.FillingAnimations - ); - } - } - - private void SetSourceValue(ValueSetterHandler setter, object? value) - { - // Capture the datacontext before the call to avoid a race condition with the GC. - var dataContext = DataContext; - - if (dataContext != null) - { - try - { - setter(dataContext, value); - } - catch (Exception exception) - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Error)) - { - this.Log().Error($"Failed to set the source value for [{PropertyName}]", exception); - } - } - } - else - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) - { - this.Log().DebugFormat("Setting [{0}] failed because the DataContext is null for. It may have already been collected, or explicitly set to null.", PropertyName); - } - } - } - - private void BuildValueGetter() - { - if (_valueGetter == null && _dataContextType != null) - { - _valueGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); - } - } - - private void BuildPrecedenceSpecificValueGetter() - { - if (_precedenceSpecificGetter == null && _dataContextType != null) - { - _precedenceSpecificGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); - } - } - - private void BuildSubstituteValueGetter() - { - if (_substituteValueGetter == null && _dataContextType != null) - { - _substituteValueGetter = - BindingPropertyHelper.GetSubstituteValueGetter(_dataContextType, PropertyName, _precedence ?? DependencyPropertyValuePrecedences.Local); - } - } - - private object? GetSourceValue() - { - BuildValueGetter(); - - return GetSourceValue(_valueGetter!); - } - - private object? GetSourceValue(ValueGetterHandler getter) - { - // Capture the datacontext before the call to avoid a race condition with the GC. - var dataContext = DataContext; - - if (dataContext != null) - { - try - { - return getter(dataContext); - } - catch (Exception exception) - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Error)) - { - this.Log().Error($"Failed to get the source value for [{PropertyName}]", exception); - } - - return DependencyProperty.UnsetValue; - } - } - else - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) - { - this.Log().DebugFormat("Unable to get the source value for [{0}]", PropertyName); - } - - return null; - } - } - - private void BuildValueUnsetter() - { - if (_valueUnsetter == null && _dataContextType != null) - { - _valueUnsetter = _precedence == null ? - BindingPropertyHelper.GetValueUnsetter(_dataContextType, PropertyName) : - BindingPropertyHelper.GetValueUnsetter(_dataContextType, PropertyName, precedence: _precedence.Value); - } - } - - internal void ClearValue() - { - BuildValueUnsetter(); - - // Capture the datacontext before the call to avoid a race condition with the GC. - var dataContext = DataContext; - - if (dataContext != null) - { - _valueUnsetter!(dataContext); - } - else - { - if (this.Log().IsEnabled(Uno.Foundation.Logging.LogLevel.Debug)) - { - this.Log().DebugFormat("Unsetting [{0}] failed because the DataContext is null for. It may have already been collected, or explicitly set to null.", PropertyName); - } - } - } - - private void BuildAnimationFillingValueUnsetter() - { - if (_animationFillingValueUnsetter == null && _dataContextType != null) - { - _animationFillingValueUnsetter = BindingPropertyHelper.GetValueUnsetter( - _dataContextType, - PropertyName, - precedence: DependencyPropertyValuePrecedences.FillingAnimations - ); - } - } - - internal void ClearAnimationFillingValue() - { - BuildAnimationFillingValueUnsetter(); - - // Capture the datacontext before the call to avoid a race condition with the GC. - var dataContext = DataContext; - if (dataContext != null) - { - _animationFillingValueUnsetter!(dataContext); - } - } - - private void RaiseValueChanged(object? newValue) - { - ValueChangedListener?.OnValueChanged(newValue); - } - - /// - /// Subscribes to property notifications for the current binding - /// - /// The action to execute when new values are raised - /// A disposable to be called when the subscription is disposed. - private IDisposable SubscribeToPropertyChanged() - { - var disposables = new CompositeDisposable(_propertyChangedHandlers.Count + 1); - if (SubscribeToNotifyCollectionChanged(this) is { } notifyCollectionChangedDisposable) - { - disposables.Add(notifyCollectionChangedDisposable); - } - - for (var i = 0; i < _propertyChangedHandlers.Count; i++) - { - var handler = _propertyChangedHandlers[i]; - - var valueHandler = new PropertyChangedValueHandler(this); - - var handlerDisposable = handler.Register(_dataContextWeakStorage!, PropertyName, valueHandler); - - if (handlerDisposable != null) - { - valueHandler.PreviousValue = GetSourceValue(); - - // We need to keep the reference to the updatePropertyHandler - // in this disposable. The reference is attached to the source's - // object lifetime, to the target (bound) object. - // - // All registrations made by _propertyChangedHandlers are - // weak with regards to the delegates that are provided. - disposables.Add(() => - { - var previousValue = valueHandler.PreviousValue; - - valueHandler = null; - handlerDisposable.Dispose(); - OnPropertyChanged(previousValue, DependencyProperty.UnsetValue, shouldRaiseValueChanged: false); - }); - } - } - - return disposables; - } - - public void Dispose() - { - IsDisposed = true; - _propertyChanged.Dispose(); - } - - private bool IsDisposed - { - get => (_flags & Flags.Disposed) != 0; - set => SetFlag(value, Flags.Disposed); - } - - private bool AllowPrivateMembers - { - get => (_flags & Flags.AllowPrivateMembers) != 0; - set => SetFlag(value, Flags.AllowPrivateMembers); - } - - private bool IsDependencyPropertyValueSet - { - get => (_flags & Flags.IsDependencyPropertyValueSet) != 0; - set => SetFlag(value, Flags.IsDependencyPropertyValueSet); - } - - internal bool IsDependencyProperty - { - get - { - if (!IsDependencyPropertyValueSet) - { - var isDP = - _dataContextType is not null - && DependencyProperty.GetProperty(_dataContextType!, PropertyName) is not null; - - SetFlag(isDP, Flags.IsDependencyProperty); - - IsDependencyPropertyValueSet = true; - - return isDP; - } - else - { - return (_flags & Flags.IsDependencyProperty) != 0; - } - } - } - - private void SetFlag(bool value, Flags flag) - { - if (!value) - { - _flags &= ~flag; - } - else - { - _flags |= flag; - } - } - - /// - /// Property changed value handler, used to avoid creating a delegate for processing - /// - /// - /// This class is primarily used to avoid the costs associated with creating, storing and invoking delegates, - /// particularly on WebAssembly as of .NET 6 where invoking a delegate requires a context switch from AOT - /// to the interpreter. - /// - private class PropertyChangedValueHandler : IPropertyChangedValueHandler, IWeakReferenceProvider - { - private readonly BindingItem _owner; - private readonly ManagedWeakReference _self; - - public PropertyChangedValueHandler(BindingItem owner) - { - _owner = owner; - _self = WeakReferencePool.RentSelfWeakReference(this); - } - - public object? PreviousValue { get; set; } - - public ManagedWeakReference WeakReference - => _self; - - public void NewValue() - { - var newValue = _owner.GetSourceValue(); - - _owner.OnPropertyChanged(PreviousValue, newValue, shouldRaiseValueChanged: true); - - PreviousValue = newValue; - } - - public void NewValue(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args) - => NewValue(); - } - } - } -} -#endif diff --git a/src/Uno.UI/DataBinding/BindingPath.cs b/src/Uno.UI/DataBinding/BindingPath.cs index a48da4545044..dbd5985b72f5 100644 --- a/src/Uno.UI/DataBinding/BindingPath.cs +++ b/src/Uno.UI/DataBinding/BindingPath.cs @@ -532,12 +532,10 @@ private sealed class BindingItem : IBindingItem, IDisposable private delegate void PropertyChangedHandler(object? previousValue, object? newValue, bool shouldRaiseValueChanged); private ManagedWeakReference? _dataContextWeakStorage; + private Flags _flags; private readonly SerialDisposable _propertyChanged = new SerialDisposable(); - private bool _disposed; private readonly DependencyPropertyValuePrecedences? _precedence; - private readonly object? _fallbackValue; - private readonly bool _allowPrivateMembers; private ValueGetterHandler? _valueGetter; private ValueGetterHandler? _precedenceSpecificGetter; private ValueGetterHandler? _substituteValueGetter; @@ -549,18 +547,27 @@ private sealed class BindingItem : IBindingItem, IDisposable private Type? _dataContextType; - public BindingItem(BindingItem next, string property, object fallbackValue) : - this(next, property, fallbackValue, null, false) + [Flags] + private enum Flags { + None = 0, + Disposed = 1 << 0, + AllowPrivateMembers = 1 << 1, + IsDependencyProperty = 1 << 2, + IsDependencyPropertyValueSet = 1 << 3, } - internal BindingItem(BindingItem? next, string property, object? fallbackValue, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers) + public BindingItem(BindingItem next, string property) : + this(next, property, null, false) + { + } + + internal BindingItem(BindingItem? next, string property, DependencyPropertyValuePrecedences? precedence, bool allowPrivateMembers) { Next = next; PropertyName = property; _precedence = precedence; - _fallbackValue = fallbackValue; - _allowPrivateMembers = allowPrivateMembers; + AllowPrivateMembers = allowPrivateMembers; } public object? DataContext @@ -568,7 +575,7 @@ public object? DataContext get => _dataContextWeakStorage?.Target; set { - if (!_disposed) + if (!IsDisposed) { // Historically, Uno was processing property changes using INPC. Since the inclusion of DependencyObject // values changes are now filtered by DependencyProperty updates, making equality updates at this location @@ -648,7 +655,7 @@ public Type? PropertyType { if (DataContext != null) { - return BindingPropertyHelper.GetPropertyType(_dataContextType!, PropertyName, _allowPrivateMembers); + return BindingPropertyHelper.GetPropertyType(_dataContextType!, PropertyName, AllowPrivateMembers); } else { @@ -742,7 +749,8 @@ private void ClearCachedGetters() var currentType = DataContext!.GetType(); if (_dataContextType != currentType && _dataContextType != null) - { + { + IsDependencyPropertyValueSet = false; _valueGetter = null; _precedenceSpecificGetter = null; _substituteValueGetter = null; @@ -829,7 +837,7 @@ private void BuildValueGetter() { if (_valueGetter == null && _dataContextType != null) { - _valueGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, _allowPrivateMembers); + _valueGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); } } @@ -837,7 +845,7 @@ private void BuildPrecedenceSpecificValueGetter() { if (_precedenceSpecificGetter == null && _dataContextType != null) { - _precedenceSpecificGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, _allowPrivateMembers); + _precedenceSpecificGetter = BindingPropertyHelper.GetValueGetter(_dataContextType, PropertyName, _precedence, AllowPrivateMembers); } } @@ -995,10 +1003,63 @@ private IDisposable SubscribeToPropertyChanged() public void Dispose() { - _disposed = true; + IsDisposed = true; _propertyChanged.Dispose(); } + private bool IsDisposed + { + get => (_flags & Flags.Disposed) != 0; + set => SetFlag(value, Flags.Disposed); + } + + private bool AllowPrivateMembers + { + get => (_flags & Flags.AllowPrivateMembers) != 0; + set => SetFlag(value, Flags.AllowPrivateMembers); + } + + private bool IsDependencyPropertyValueSet + { + get => (_flags & Flags.IsDependencyPropertyValueSet) != 0; + set => SetFlag(value, Flags.IsDependencyPropertyValueSet); + } + + internal bool IsDependencyProperty + { + get + { + if (!IsDependencyPropertyValueSet) + { + var isDP = + _dataContextType is not null + && DependencyProperty.GetProperty(_dataContextType!, PropertyName) is not null; + + SetFlag(isDP, Flags.IsDependencyProperty); + + IsDependencyPropertyValueSet = true; + + return isDP; + } + else + { + return (_flags & Flags.IsDependencyProperty) != 0; + } + } + } + + private void SetFlag(bool value, Flags flag) + { + if (!value) + { + _flags &= ~flag; + } + else + { + _flags |= flag; + } + } + /// /// Property changed value handler, used to avoid creating a delegate for processing /// From dce36149d876bb1734b100bf1ade59dc9e4a3acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 20 Sep 2023 20:29:19 -0400 Subject: [PATCH 05/11] chore: Adjust formatting --- src/Uno.UI/DataBinding/BindingPath.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Uno.UI/DataBinding/BindingPath.cs b/src/Uno.UI/DataBinding/BindingPath.cs index dbd5985b72f5..8a61d815479c 100644 --- a/src/Uno.UI/DataBinding/BindingPath.cs +++ b/src/Uno.UI/DataBinding/BindingPath.cs @@ -749,7 +749,7 @@ private void ClearCachedGetters() var currentType = DataContext!.GetType(); if (_dataContextType != currentType && _dataContextType != null) - { + { IsDependencyPropertyValueSet = false; _valueGetter = null; _precedenceSpecificGetter = null; @@ -1059,7 +1059,7 @@ private void SetFlag(bool value, Flags flag) _flags |= flag; } } - + /// /// Property changed value handler, used to avoid creating a delegate for processing /// From 1cd7a3a8071434b2ca78e6a5ca5547775ff53592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Tue, 5 Sep 2023 22:43:11 -0400 Subject: [PATCH 06/11] fix(winappsdk): Adjust 1.4 detection `WindowsAppSDKWinUI` has been removed in 1.4, using `UseWinUITools` until we can find a more stable property. (cherry picked from commit 85a5aae155b9187e7ab86524e9f034b46da2a8cb) --- build/uno.winui.props | 8 +++++++- build/uno.winui.single-project.targets | 4 ++-- build/uno.winui.targets | 8 ++++---- .../Content/Uno.UI.SourceGenerators.props | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/build/uno.winui.props b/build/uno.winui.props index 7dab54f2f5f2..ab48e9800c37 100644 --- a/build/uno.winui.props +++ b/build/uno.winui.props @@ -1,6 +1,12 @@  - + + + <_UnoIsWinAppSDKDefined>false + <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'!='true' and '$(UseWinUITools)'!='true'">false + + + diff --git a/build/uno.winui.single-project.targets b/build/uno.winui.single-project.targets index 5c93dfcf61e2..28c0fcdd07f1 100644 --- a/build/uno.winui.single-project.targets +++ b/build/uno.winui.single-project.targets @@ -100,7 +100,7 @@ - + @@ -148,7 +148,7 @@ - + diff --git a/build/uno.winui.targets b/build/uno.winui.targets index f5dccf8348d2..144ca1bca19a 100644 --- a/build/uno.winui.targets +++ b/build/uno.winui.targets @@ -5,9 +5,9 @@ <_IsUnoWinUIPackage>$(MSBuildThisFile.ToLower().Equals('uno.winui.targets')) - - + + - - + + diff --git a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props index 7e3b42338493..613f7eea4561 100644 --- a/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props +++ b/src/SourceGenerators/Uno.UI.SourceGenerators/Content/Uno.UI.SourceGenerators.props @@ -27,7 +27,7 @@ false true - false + false <_UnoIsWinAppSDKDefined>false - <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'!='true' and '$(UseWinUITools)'!='true'">false + <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'!='true' and '$(UseWinUITools)'!='true'">true - + From b507650113bddbd97503c1b4948f849e1916295e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 6 Sep 2023 09:39:46 -0400 Subject: [PATCH 08/11] chore: Fix condition (cherry picked from commit 1d892277f6b849e053778ab697129d8d6ea52343) --- build/uno.winui.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/uno.winui.props b/build/uno.winui.props index 59b026fb5e6e..680ec3480bad 100644 --- a/build/uno.winui.props +++ b/build/uno.winui.props @@ -4,7 +4,7 @@ <_UnoIsWinAppSDKDefined>false - <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'!='true' and '$(UseWinUITools)'!='true'">true + <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'=='true' or '$(UseWinUITools)'=='true'">true From 2f118c09fa3ba565c80a30d2d46a181e8bcb6484 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Wed, 6 Sep 2023 14:51:21 -0400 Subject: [PATCH 09/11] ci: update diff ignore (cherry picked from commit 3f40f06cad2e8aba00dfce15d0941972edd2b34e) # Conflicts: # build/PackageDiffIgnore.xml --- build/PackageDiffIgnore.xml | 89 +++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index fb6af43feda7..28ed9e747e58 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -9052,6 +9052,95 @@ +<<<<<<< HEAD +======= + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +>>>>>>> 3f40f06cad (ci: update diff ignore) From 1916497a47f3374816a2780b58ad08e84d015740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Fri, 8 Sep 2023 08:31:18 -0400 Subject: [PATCH 10/11] chore: Adjust diff ignore for backport --- build/PackageDiffIgnore.xml | 89 ------------------------------------- 1 file changed, 89 deletions(-) diff --git a/build/PackageDiffIgnore.xml b/build/PackageDiffIgnore.xml index 28ed9e747e58..fb6af43feda7 100644 --- a/build/PackageDiffIgnore.xml +++ b/build/PackageDiffIgnore.xml @@ -9052,95 +9052,6 @@ -<<<<<<< HEAD -======= - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ->>>>>>> 3f40f06cad (ci: update diff ignore) From 5180325e384f1848fe93c26d506b50b70856c604 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Laban?= Date: Tue, 19 Sep 2023 22:52:37 -0400 Subject: [PATCH 11/11] fix(net8): Adjust WinUI property detection .NET 8 changes the ordering of nuget defined props files, This change makes WinUI detection independent of nuget props import and rely on .targets evaluation. --- build/uno.winui.props | 12 +++++------- build/uno.winui.targets | 5 +++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/build/uno.winui.props b/build/uno.winui.props index 680ec3480bad..c7cba7a19f15 100644 --- a/build/uno.winui.props +++ b/build/uno.winui.props @@ -1,12 +1,10 @@  - - - <_UnoIsWinAppSDKDefined>false - <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'=='true' or '$(UseWinUITools)'=='true'">true - - - + + diff --git a/build/uno.winui.targets b/build/uno.winui.targets index 144ca1bca19a..d60b6a5b8319 100644 --- a/build/uno.winui.targets +++ b/build/uno.winui.targets @@ -2,6 +2,11 @@ + + + <_UnoIsWinAppSDKDefined>false + <_UnoIsWinAppSDKDefined Condition="'$(WindowsAppSDKWinUI)'=='true' or '$(UseWinUITools)'=='true'">true + <_IsUnoWinUIPackage>$(MSBuildThisFile.ToLower().Equals('uno.winui.targets'))