From cf4383a9045549c439062cc09ed6a6dd2bd40346 Mon Sep 17 00:00:00 2001 From: aldelaro5 Date: Fri, 31 Mar 2023 01:15:48 -0400 Subject: [PATCH 01/11] InternalSelectionModel: Prevent unwanted source collection changes --- .../Selection/InternalSelectionModel.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs index c8ad9bd88b..455700af1f 100644 --- a/src/Avalonia.Controls/Selection/InternalSelectionModel.cs +++ b/src/Avalonia.Controls/Selection/InternalSelectionModel.cs @@ -67,6 +67,8 @@ namespace Avalonia.Controls.Selection var previousSource = Source; var previousWritableSelectedItems = _writableSelectedItems; + base.OnSourceCollectionChangeStarted(); + try { _skipSyncFromSelectedItems = true; @@ -81,8 +83,20 @@ namespace Avalonia.Controls.Selection // We skipped the sync from WritableSelectedItems before; do it now that both // the source and WritableSelectedItems are updated. - if (previousSource != Source || previousWritableSelectedItems != _writableSelectedItems) + if (previousWritableSelectedItems != _writableSelectedItems) + { + base.OnSourceCollectionChangeFinished(); + SyncFromSelectedItems(); + } + else if (previousSource != Source) + { SyncFromSelectedItems(); + base.OnSourceCollectionChangeFinished(); + } + else + { + base.OnSourceCollectionChangeFinished(); + } } private protected override void SetSource(IEnumerable? value) From e7c3e0c673303e3ff047d08ce4c6d1676432e992 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 May 2023 12:31:35 +0200 Subject: [PATCH 02/11] Added failing test for #11212. --- .../AvaloniaObjectTests_Coercion.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs index fe4262331f..0d0456dbda 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Coercion.cs @@ -31,6 +31,16 @@ namespace Avalonia.Base.UnitTests Assert.Equal(100, target.GetValue(Class1.AttachedProperty)); } + [Fact] + public void Coerces_Set_Value_Attached_On_Class_Not_Derived_From_Owner() + { + var target = new Class2(); + + target.SetValue(Class1.AttachedProperty, 150); + + Assert.Equal(100, target.GetValue(Class1.AttachedProperty)); + } + [Fact] public void Coerces_Bound_Value() { @@ -301,7 +311,7 @@ namespace Avalonia.Base.UnitTests coerce: CoerceFoo); public static readonly AttachedProperty AttachedProperty = - AvaloniaProperty.RegisterAttached( + AvaloniaProperty.RegisterAttached( "Attached", defaultValue: 11, coerce: CoerceFoo); @@ -332,8 +342,9 @@ namespace Avalonia.Base.UnitTests public static int CoerceFoo(AvaloniaObject instance, int value) { - var o = (Class1)instance; - return Math.Clamp(value, o.MinFoo, o.MaxFoo); + return instance is Class1 o ? + Math.Clamp(value, o.MinFoo, o.MaxFoo) : + Math.Clamp(value, 0, 100); } protected override void OnPropertyChangedCore(AvaloniaPropertyChangedEventArgs change) From 559640fb1ad9bbe6c31285b2aede497979491a30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Fri, 12 May 2023 12:31:49 +0200 Subject: [PATCH 03/11] Register attached metadata on host not owner. --- src/Avalonia.Base/AvaloniaProperty.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index c57131f7b5..63fb83bbc6 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -338,7 +338,7 @@ namespace Avalonia defaultBindingMode: defaultBindingMode, coerce: coerce); - var result = new AttachedProperty(name, typeof(TOwner), metadata, inherits, validate); + var result = new AttachedProperty(name, typeof(THost), metadata, inherits, validate); var registry = AvaloniaPropertyRegistry.Instance; registry.Register(typeof(TOwner), result); registry.RegisterAttached(typeof(THost), result); From fe0edf557ce46d2aee8a97d4f5092c2802d6ef79 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 14 May 2023 23:29:19 -0400 Subject: [PATCH 04/11] Update Cecil --- nukebuild/_build.csproj | 2 +- src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index d03746766e..8999b7ca76 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj index b654c66157..4f9c7416db 100644 --- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj +++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj @@ -118,7 +118,7 @@ - + From 3bf8327dd418d85f1d19085d3b53b7fb11916b66 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Sun, 14 May 2023 23:57:11 -0400 Subject: [PATCH 05/11] Update XAMLX --- src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github index e5254eb1b2..c5d5d8b78d 160000 --- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github +++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github @@ -1 +1 @@ -Subproject commit e5254eb1b2017f78a92acd466c8fa1e47401056b +Subproject commit c5d5d8b78dce42dfb7b7f320a5c345da36a58579 From 00d077a6342d227495ef1b0808debd96dbd3da4e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 May 2023 09:53:45 +0200 Subject: [PATCH 06/11] Pass both owner and host type to AvaloniaProperty. For non-attached properties these will be the same. For attached properties the metadata needs to be registered on the host, but the `Owner` property needs to be set to the owner, not the host. --- src/Avalonia.Base/AttachedProperty.cs | 4 +++- src/Avalonia.Base/AvaloniaProperty.cs | 12 ++++++++---- src/Avalonia.Base/AvaloniaProperty`1.cs | 4 +++- src/Avalonia.Base/DirectPropertyBase.cs | 2 +- src/Avalonia.Base/StyledProperty.cs | 4 +++- .../Avalonia.Base.UnitTests/AttachedPropertyTests.cs | 2 ++ .../AvaloniaObjectTests_Validation.cs | 1 + .../AvaloniaPropertyRegistryTests.cs | 2 +- .../Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs | 2 +- tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs | 4 +++- .../Utilities/AvaloniaPropertyDictionaryTests.cs | 1 + 11 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 4a09f2a80a..27844023ae 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -13,16 +13,18 @@ namespace Avalonia /// /// The name of the property. /// The class that is registering the property. + /// The class that the property being is registered on. /// The property metadata. /// Whether the property inherits its value. /// A value validation callback. public AttachedProperty( string name, Type ownerType, + Type hostType, StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null) - : base(name, ownerType, metadata, inherits, validate) + : base(name, ownerType, hostType, metadata, inherits, validate) { IsAttached = true; } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 63fb83bbc6..332ca1802c 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -39,12 +39,14 @@ namespace Avalonia /// The name of the property. /// The type of the property's value. /// The type of the class that registers the property. + /// The class that the property being is registered on. /// The property metadata. /// A callback. protected AvaloniaProperty( string name, Type valueType, Type ownerType, + Type hostType, AvaloniaPropertyMetadata metadata, Action? notifying = null) { @@ -63,9 +65,9 @@ namespace Avalonia Notifying = notifying; Id = s_nextId++; - _metadata.Add(ownerType, metadata ?? throw new ArgumentNullException(nameof(metadata))); + _metadata.Add(hostType, metadata ?? throw new ArgumentNullException(nameof(metadata))); _defaultMetadata = metadata.GenerateTypeSafeMetadata(); - _singleMetadata = new(ownerType, metadata); + _singleMetadata = new(hostType, metadata); } /// @@ -255,6 +257,7 @@ namespace Avalonia var result = new StyledProperty( name, typeof(TOwner), + typeof(TOwner), metadata, inherits, validate); @@ -301,6 +304,7 @@ namespace Avalonia var result = new StyledProperty( name, typeof(TOwner), + typeof(TOwner), metadata, inherits, validate, @@ -338,7 +342,7 @@ namespace Avalonia defaultBindingMode: defaultBindingMode, coerce: coerce); - var result = new AttachedProperty(name, typeof(THost), metadata, inherits, validate); + var result = new AttachedProperty(name, typeof(TOwner), typeof(THost), metadata, inherits, validate); var registry = AvaloniaPropertyRegistry.Instance; registry.Register(typeof(TOwner), result); registry.RegisterAttached(typeof(THost), result); @@ -375,7 +379,7 @@ namespace Avalonia defaultBindingMode: defaultBindingMode, coerce: coerce); - var result = new AttachedProperty(name, ownerType, metadata, inherits, validate); + var result = new AttachedProperty(name, ownerType, typeof(THost), metadata, inherits, validate); var registry = AvaloniaPropertyRegistry.Instance; registry.Register(ownerType, result); registry.RegisterAttached(typeof(THost), result); diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index f8c062a176..f08c0e0a02 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -19,14 +19,16 @@ namespace Avalonia /// /// The name of the property. /// The type of the class that registers the property. + /// The class that the property being is registered on. /// The property metadata. /// A callback. protected AvaloniaProperty( string name, Type ownerType, + Type hostType, AvaloniaPropertyMetadata metadata, Action? notifying = null) - : base(name, typeof(TValue), ownerType, metadata, notifying) + : base(name, typeof(TValue), ownerType, hostType, metadata, notifying) { _changed = new LightweightSubject>(); } diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 7e5a962157..94694e6209 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -24,7 +24,7 @@ namespace Avalonia string name, Type ownerType, AvaloniaPropertyMetadata metadata) - : base(name, ownerType, metadata) + : base(name, ownerType, ownerType, metadata) { Owner = ownerType; } diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 5cb330eda9..76ba83b88d 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -16,6 +16,7 @@ namespace Avalonia /// /// The name of the property. /// The type of the class that registers the property. + /// The class that the property being is registered on. /// The property metadata. /// Whether the property inherits its value. /// @@ -26,11 +27,12 @@ namespace Avalonia public StyledProperty( string name, Type ownerType, + Type hostType, StyledPropertyMetadata metadata, bool inherits = false, Func? validate = null, Action? notifying = null) - : base(name, ownerType, metadata, notifying) + : base(name, ownerType, hostType, metadata, notifying) { Inherits = inherits; ValidateValue = validate; diff --git a/tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs index f461240d68..8a6e7b8679 100644 --- a/tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AttachedPropertyTests.cs @@ -1,3 +1,4 @@ +using Avalonia.Controls; using Xunit; namespace Avalonia.Base.UnitTests @@ -10,6 +11,7 @@ namespace Avalonia.Base.UnitTests var property = new AttachedProperty( "Foo", typeof(Class1), + typeof(Control), new StyledPropertyMetadata()); Assert.True(property.IsAttached); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs index 513aeb65ab..2b3bd07fed 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Validation.cs @@ -15,6 +15,7 @@ namespace Avalonia.Base.UnitTests new StyledProperty( "BadDefault", typeof(Class1), + typeof(Class1), new StyledPropertyMetadata(101), validate: Class1.ValidateFoo)); } diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs index 0d62f20f7e..23e21c1645 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyRegistryTests.cs @@ -22,7 +22,7 @@ namespace Avalonia.Base.UnitTests { var registry = new AvaloniaPropertyRegistry(); var metadata = new StyledPropertyMetadata(); - var property = new AttachedProperty("test", typeof(object), metadata, true); + var property = new AttachedProperty("test", typeof(object), typeof(object), metadata, true); registry.Register(typeof(object), property); registry.RegisterAttached(typeof(AvaloniaPropertyRegistryTests), property); property.AddOwner(); diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs index e44c15d962..d974351b1e 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaPropertyTests.cs @@ -157,7 +157,7 @@ namespace Avalonia.Base.UnitTests private class TestProperty : AvaloniaProperty { public TestProperty(string name, Type ownerType, TestMetadata metadata = null) - : base(name, ownerType, metadata ?? new TestMetadata()) + : base(name, ownerType, ownerType, metadata ?? new TestMetadata()) { } diff --git a/tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs b/tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs index f0aa47784d..5304c74c39 100644 --- a/tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/StyledPropertyTests.cs @@ -9,7 +9,8 @@ namespace Avalonia.Base.UnitTests { var p1 = new StyledProperty( "p1", - typeof(Class1), + typeof(Class1), + typeof(Class1), new StyledPropertyMetadata()); var p2 = p1.AddOwner(); @@ -24,6 +25,7 @@ namespace Avalonia.Base.UnitTests var p1 = new StyledProperty( "p1", typeof(Class1), + typeof(Class1), new StyledPropertyMetadata()); var p2 = p1.AddOwner(); diff --git a/tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs b/tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs index 362eee8035..4b4aeb4344 100644 --- a/tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs +++ b/tests/Avalonia.Base.UnitTests/Utilities/AvaloniaPropertyDictionaryTests.cs @@ -18,6 +18,7 @@ namespace Avalonia.Base.UnitTests.Utilities TestProperties[i] = new StyledProperty( $"Test{i}", typeof(AvaloniaPropertyDictionaryTests), + typeof(AvaloniaPropertyDictionaryTests), new StyledPropertyMetadata()); } From bab99456ad842b759f2cf5c174a10b51186f07e9 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 15 May 2023 09:18:22 +0000 Subject: [PATCH 07/11] fix safe area demo project build --- samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj b/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj index 619209892d..a3b020d531 100644 --- a/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj +++ b/samples/SafeAreaDemo.Desktop/SafeAreaDemo.Desktop.csproj @@ -14,7 +14,7 @@ - + From 474d78b33519dcb98f577f4cd9268cf2b57f7879 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 May 2023 12:13:34 +0200 Subject: [PATCH 08/11] Deprecate IStyleable . This was signalled for removal in #9553, but I was hesitant to do it because it will be in use by a lot of code in order to override `StyleKey`. Instead of removing it, deprecate it and provide a virtual `StyledElement.StyleKeyOverride` property as the supported way of overriding a control's style key. --- src/Avalonia.Base/StyledElement.cs | 60 ++++++++++++++----- src/Avalonia.Base/Styling/ControlTheme.cs | 2 +- .../Styling/DescendentSelector.cs | 4 +- src/Avalonia.Base/Styling/IStyleable.cs | 4 +- src/Avalonia.Base/Styling/NestingSelector.cs | 2 +- src/Avalonia.Base/Styling/Selectors.cs | 4 +- .../Styling/TypeNameAndClassSelector.cs | 2 +- .../Embedding/EmbeddableControlRoot.cs | 4 +- .../Embedding/Offscreen/OffscreenTopLevel.cs | 4 +- src/Avalonia.Controls/ItemsControl.cs | 2 +- src/Avalonia.Controls/MaskedTextBox.cs | 4 +- .../SplitButton/ToggleSplitButton.cs | 4 +- src/Avalonia.Controls/UserControl.cs | 2 +- src/Avalonia.Controls/Window.cs | 4 +- .../Diagnostics/Controls/CommitTextBox.cs | 4 +- .../Diagnostics/Controls/FilterTextBox.cs | 4 +- .../ViewModels/TreePageViewModel.cs | 2 +- .../Diagnostics/ViewModels/VisualTreeNode.cs | 2 +- src/Avalonia.ReactiveUI/RoutedViewHost.cs | 4 +- src/Avalonia.ReactiveUI/ViewModelViewHost.cs | 4 +- src/Windows/Avalonia.Win32/TrayIconImpl.cs | 4 +- .../ItemsControlTests.cs | 8 +-- .../SelectingItemsControlTests_Multiple.cs | 4 +- .../TabControlTests.cs | 4 +- .../AvaloniaPropertyConverterTest.cs | 56 ++--------------- 25 files changed, 91 insertions(+), 107 deletions(-) diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 3270fe4614..196fa850d6 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -30,10 +30,12 @@ namespace Avalonia ILogical, IThemeVariantHost, IStyleHost, - IStyleable, ISetLogicalParent, ISetInheritanceParent, - ISupportInitialize + ISupportInitialize, +#pragma warning disable CS0618 // Type or member is obsolete + IStyleable +#pragma warning restore CS0618 // Type or member is obsolete { /// /// Defines the property. @@ -217,6 +219,18 @@ namespace Avalonia /// public Styles Styles => _styles ??= new Styles(this); + /// + /// Gets the type by which the element is styled. + /// + /// + /// Usually controls are styled by their own type, but there are instances where you want + /// an element to be styled by its base type, e.g. creating SpecialButton that + /// derives from Button and adds extra functionality but is still styled as a regular + /// Button. To change the style for a control class, override the + /// property + /// + public Type StyleKey => StyleKeyOverride; + /// /// Gets or sets the styled element's resource dictionary. /// @@ -278,6 +292,18 @@ namespace Avalonia /// protected IPseudoClasses PseudoClasses => Classes; + /// + /// Gets the type by which the element is styled. + /// + /// + /// Usually controls are styled by their own type, but there are instances where you want + /// an element to be styled by its base type, e.g. creating SpecialButton that + /// derives from Button and adds extra functionality but is still styled as a regular + /// Button. Override this property to change the style for a control class, returning the + /// type that you wish the elements to be styled as. + /// + protected virtual Type StyleKeyOverride => GetType(); + /// /// Gets a value indicating whether the element is attached to a rooted logical tree. /// @@ -309,24 +335,12 @@ namespace Avalonia /// IAvaloniaReadOnlyList IStyleable.Classes => Classes; - /// - /// Gets the type by which the styled element is styled. - /// - /// - /// Usually controls are styled by their own type, but there are instances where you want - /// a styled element to be styled by its base type, e.g. creating SpecialButton that - /// derives from Button and adds extra functionality but is still styled as a regular - /// Button. - /// - Type IStyleable.StyleKey => GetType(); - /// bool IStyleHost.IsStylesInitialized => _styles != null; /// IStyleHost? IStyleHost.StylingParent => (IStyleHost?)InheritanceParent; - /// public virtual void BeginInit() { @@ -669,7 +683,7 @@ namespace Avalonia // If the Theme property is not set, try to find a ControlTheme resource with our StyleKey. if (_implicitTheme is null) { - var key = ((IStyleable)this).StyleKey; + var key = GetStyleKey(this); if (this.TryFindResource(key, out var value) && value is ControlTheme t) _implicitTheme = t; @@ -700,6 +714,22 @@ namespace Avalonia } } + /// + /// Internal getter for so that we only need to suppress the obsolete + /// warning in one place. + /// + /// The element + /// + /// is obsolete and will be removed in a future version, but for backwards + /// compatibility we need to support code which overrides . + /// + internal static Type GetStyleKey(StyledElement e) + { +#pragma warning disable CS0618 // Type or member is obsolete + return ((IStyleable)e).StyleKey; +#pragma warning restore CS0618 // Type or member is obsolete + } + private static void DataContextNotifying(AvaloniaObject o, bool updateStarted) { if (o is StyledElement element) diff --git a/src/Avalonia.Base/Styling/ControlTheme.cs b/src/Avalonia.Base/Styling/ControlTheme.cs index 22c8f61577..75a3beb907 100644 --- a/src/Avalonia.Base/Styling/ControlTheme.cs +++ b/src/Avalonia.Base/Styling/ControlTheme.cs @@ -46,7 +46,7 @@ namespace Avalonia.Styling if (TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - if (HasSettersOrAnimations && TargetType.IsAssignableFrom(((IStyleable)target).StyleKey)) + if (HasSettersOrAnimations && TargetType.IsAssignableFrom(StyledElement.GetStyleKey(target))) { Attach(target, null, type); return SelectorMatchResult.AlwaysThisType; diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 77ae0f2877..20874a6877 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -44,9 +44,9 @@ namespace Avalonia.Styling { c = c.LogicalParent; - if (c is IStyleable) + if (c is StyledElement s) { - var match = _parent.Match((StyledElement)c, parent, subscribe); + var match = _parent.Match(s, parent, subscribe); if (match.Result == SelectorMatchResult.Sometimes) { diff --git a/src/Avalonia.Base/Styling/IStyleable.cs b/src/Avalonia.Base/Styling/IStyleable.cs index a6cc2c0f76..0768669905 100644 --- a/src/Avalonia.Base/Styling/IStyleable.cs +++ b/src/Avalonia.Base/Styling/IStyleable.cs @@ -1,13 +1,12 @@ using System; using Avalonia.Collections; -using Avalonia.Metadata; namespace Avalonia.Styling { /// /// Interface for styleable elements. /// - [NotClientImplementable] + [Obsolete("This interface may be removed in 12.0. Use StyledElement, or override StyledElement.StyleKeyOverride to override the StyleKey for a class.")] public interface IStyleable : INamed { /// @@ -18,6 +17,7 @@ namespace Avalonia.Styling /// /// Gets the type by which the control is styled. /// + [Obsolete("Override StyledElement.StyleKeyOverride instead.")] Type StyleKey { get; } /// diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index 980bf12907..deb688ca4d 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -23,7 +23,7 @@ namespace Avalonia.Styling { if (theme.TargetType is null) throw new InvalidOperationException("ControlTheme has no TargetType."); - return theme.TargetType.IsAssignableFrom(((IStyleable)control).StyleKey) ? + return theme.TargetType.IsAssignableFrom(StyledElement.GetStyleKey(control)) ? SelectorMatch.AlwaysThisType : SelectorMatch.NeverThisType; } diff --git a/src/Avalonia.Base/Styling/Selectors.cs b/src/Avalonia.Base/Styling/Selectors.cs index 476d86cd11..d7406f2164 100644 --- a/src/Avalonia.Base/Styling/Selectors.cs +++ b/src/Avalonia.Base/Styling/Selectors.cs @@ -76,7 +76,7 @@ namespace Avalonia.Styling /// The type. /// The previous selector. /// The selector. - public static Selector Is(this Selector? previous) where T : IStyleable + public static Selector Is(this Selector? previous) where T : StyledElement { return previous.Is(typeof(T)); } @@ -171,7 +171,7 @@ namespace Avalonia.Styling /// The type. /// The previous selector. /// The selector. - public static Selector OfType(this Selector? previous) where T : IStyleable + public static Selector OfType(this Selector? previous) where T : StyledElement { return previous.OfType(typeof(T)); } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index a10b3eb3ea..2bd05242f5 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -93,7 +93,7 @@ namespace Avalonia.Styling { if (TargetType != null) { - var controlType = ((IStyleable)control).StyleKey ?? control.GetType(); + var controlType = StyledElement.GetStyleKey(control) ?? control.GetType(); if (IsConcreteType) { diff --git a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs index b79fef55b9..7a0ab36050 100644 --- a/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs +++ b/src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs @@ -7,7 +7,7 @@ using Avalonia.Styling; namespace Avalonia.Controls.Embedding { - public class EmbeddableControlRoot : TopLevel, IStyleable, IFocusScope, IDisposable + public class EmbeddableControlRoot : TopLevel, IFocusScope, IDisposable { public EmbeddableControlRoot(ITopLevelImpl impl) : base(impl) { @@ -46,7 +46,7 @@ namespace Avalonia.Controls.Embedding return rv; } - Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); + protected override Type StyleKeyOverride => typeof(EmbeddableControlRoot); public void Dispose() => PlatformImpl?.Dispose(); } } diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs index b037dd9901..a87e36d00d 100644 --- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs +++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs @@ -4,7 +4,7 @@ using Avalonia.Styling; namespace Avalonia.Controls.Embedding.Offscreen { - class OffscreenTopLevel : TopLevel, IStyleable + class OffscreenTopLevel : TopLevel { public OffscreenTopLevelImplBase Impl { get; } @@ -31,7 +31,7 @@ namespace Avalonia.Controls.Embedding.Offscreen } } - Type IStyleable.StyleKey => typeof(EmbeddableControlRoot); + protected override Type StyleKeyOverride => typeof(EmbeddableControlRoot); public void Dispose() { PlatformImpl?.Dispose(); diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs index 064716fa9b..1613bda45b 100644 --- a/src/Avalonia.Controls/ItemsControl.cs +++ b/src/Avalonia.Controls/ItemsControl.cs @@ -712,7 +712,7 @@ namespace Avalonia.Controls if (itemContainerTheme is not null && !container.IsSet(ThemeProperty) && - ((IStyleable)container).StyleKey == itemContainerTheme.TargetType) + StyledElement.GetStyleKey(container) == itemContainerTheme.TargetType) { container.Theme = itemContainerTheme; } diff --git a/src/Avalonia.Controls/MaskedTextBox.cs b/src/Avalonia.Controls/MaskedTextBox.cs index f54e8b19db..4800b7b1e4 100644 --- a/src/Avalonia.Controls/MaskedTextBox.cs +++ b/src/Avalonia.Controls/MaskedTextBox.cs @@ -10,7 +10,7 @@ using Avalonia.Styling; namespace Avalonia.Controls { - public class MaskedTextBox : TextBox, IStyleable + public class MaskedTextBox : TextBox { public static readonly StyledProperty AsciiOnlyProperty = AvaloniaProperty.Register(nameof(AsciiOnly)); @@ -183,7 +183,7 @@ namespace Avalonia.Controls set => SetValue(ResetOnSpaceProperty, value); } - Type IStyleable.StyleKey => typeof(TextBox); + protected override Type StyleKeyOverride => typeof(TextBox); /// protected override void OnGotFocus(GotFocusEventArgs e) diff --git a/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs b/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs index 509b58833f..ff8bd5dc0b 100644 --- a/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs +++ b/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs @@ -13,7 +13,7 @@ namespace Avalonia.Controls /// the secondary part opens a flyout. /// [PseudoClasses(pcChecked)] - public class ToggleSplitButton : SplitButton, IStyleable + public class ToggleSplitButton : SplitButton { /// /// Raised when the property value changes. @@ -63,7 +63,7 @@ namespace Avalonia.Controls /// Both and share /// the same exact default style. /// - Type IStyleable.StyleKey => typeof(SplitButton); + protected override Type StyleKeyOverride => typeof(SplitButton); /// /// Toggles the property between true and false. diff --git a/src/Avalonia.Controls/UserControl.cs b/src/Avalonia.Controls/UserControl.cs index e9339d5f4b..40c312be42 100644 --- a/src/Avalonia.Controls/UserControl.cs +++ b/src/Avalonia.Controls/UserControl.cs @@ -5,7 +5,7 @@ namespace Avalonia.Controls /// /// Provides the base class for defining a new control that encapsulates related existing controls and provides its own logic. /// - public class UserControl : ContentControl, IStyleable + public class UserControl : ContentControl { } diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs index 66cce89b9d..3f04350995 100644 --- a/src/Avalonia.Controls/Window.cs +++ b/src/Avalonia.Controls/Window.cs @@ -65,7 +65,7 @@ namespace Avalonia.Controls /// /// A top-level window. /// - public class Window : WindowBase, IStyleable, IFocusScope, ILayoutRoot + public class Window : WindowBase, IFocusScope, ILayoutRoot { private readonly List<(Window child, bool isDialog)> _children = new List<(Window, bool)>(); private bool _isExtendedIntoWindowDecorations; @@ -420,7 +420,7 @@ namespace Avalonia.Controls public void BeginResizeDrag(WindowEdge edge, PointerPressedEventArgs e) => PlatformImpl?.BeginResizeDrag(edge, e); /// - Type IStyleable.StyleKey => typeof(Window); + protected override Type StyleKeyOverride => typeof(Window); /// /// Fired before a window is closed. diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs index 7870febd0a..d724318641 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/CommitTextBox.cs @@ -7,9 +7,9 @@ using Avalonia.Styling; namespace Avalonia.Diagnostics.Controls { //TODO: UpdateSourceTrigger & Binding.ValidationRules could help removing the need for this control. - internal sealed class CommitTextBox : TextBox, IStyleable + internal sealed class CommitTextBox : TextBox { - Type IStyleable.StyleKey => typeof(TextBox); + protected override Type StyleKeyOverride => typeof(TextBox); /// /// Defines the property. diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs index 1e5674cc21..c703c73549 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.cs @@ -5,7 +5,7 @@ using Avalonia.Styling; namespace Avalonia.Diagnostics.Controls { - internal class FilterTextBox : TextBox, IStyleable + internal class FilterTextBox : TextBox { public static readonly StyledProperty UseRegexFilterProperty = AvaloniaProperty.Register(nameof(UseRegexFilter), @@ -42,6 +42,6 @@ namespace Avalonia.Diagnostics.Controls set => SetValue(UseWholeWordFilterProperty, value); } - Type IStyleable.StyleKey => typeof(TextBox); + protected override Type StyleKeyOverride => typeof(TextBox); } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs index 4e8b4c66a2..67dbfed92b 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs @@ -208,7 +208,7 @@ namespace Avalonia.Diagnostics.ViewModels var classes = string.Concat(visual.Classes .Where(c => !c.StartsWith(":")) .Select(c => '.' + c)); - var typeName = ((IStyleable)visual).StyleKey.Name; + var typeName = StyledElement.GetStyleKey(visual); return $"{typeName}{name}{classes}"; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs index c3ebb1beaf..8dbcd606b0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/VisualTreeNode.cs @@ -23,7 +23,7 @@ namespace Avalonia.Diagnostics.ViewModels _ => TreeNodeCollection.Empty }; - if (Visual is IStyleable styleable) + if (Visual is StyledElement styleable) IsInTemplate = styleable.TemplatedParent != null; } diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs index 2d848d4cd7..701b0d0f46 100644 --- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs +++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs @@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI /// ReactiveUI routing documentation website for more info. /// /// - public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable + public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger { /// /// for the property. @@ -126,7 +126,7 @@ namespace Avalonia.ReactiveUI /// public IViewLocator? ViewLocator { get; set; } - Type IStyleable.StyleKey => typeof(TransitioningContentControl); + protected override Type StyleKeyOverride => typeof(TransitioningContentControl); /// /// Invoked when ReactiveUI router navigates to a view model. diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs index dc45758046..3e7ed42662 100644 --- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs +++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs @@ -13,7 +13,7 @@ namespace Avalonia.ReactiveUI /// the ViewModel property and display it. This control is very useful /// inside a DataTemplate to display the View associated with a ViewModel. /// - public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable + public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger { /// /// for the property. @@ -78,7 +78,7 @@ namespace Avalonia.ReactiveUI /// public IViewLocator? ViewLocator { get; set; } - Type IStyleable.StyleKey => typeof(TransitioningContentControl); + protected override Type StyleKeyOverride => typeof(TransitioningContentControl); /// /// Invoked when ReactiveUI router navigates to a view model. diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs index d541e6b436..5069add9fd 100644 --- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs +++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs @@ -170,9 +170,9 @@ namespace Avalonia.Win32 WM_TRAYMOUSE = WindowsMessage.WM_USER + 1024, } - private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter, IStyleable + private class TrayIconMenuFlyoutPresenter : MenuFlyoutPresenter { - Type IStyleable.StyleKey => typeof(MenuFlyoutPresenter); + protected override Type StyleKeyOverride => typeof(MenuFlyoutPresenter); public override void Close() { diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs index 3d4852c4ff..86249c66ff 100644 --- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs @@ -1031,9 +1031,9 @@ namespace Avalonia.Controls.UnitTests textShaperImpl: new HeadlessTextShaperStub())); } - private class ItemsControlWithContainer : ItemsControl, IStyleable + private class ItemsControlWithContainer : ItemsControl { - Type IStyleable.StyleKey => typeof(ItemsControl); + protected override Type StyleKeyOverride => typeof(ItemsControl); protected internal override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { @@ -1046,9 +1046,9 @@ namespace Avalonia.Controls.UnitTests } } - private class ContainerControl : ContentControl, IStyleable + private class ContainerControl : ContentControl { - Type IStyleable.StyleKey => typeof(ContentControl); + protected override Type StyleKeyOverride => typeof(ContentControl); } private record Item(string Caption, string? Value = null); diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs index 78ac6bb3e2..a4181fa8e8 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests_Multiple.cs @@ -1392,9 +1392,9 @@ namespace Avalonia.Controls.UnitTests.Primitives public void Toggle(int index) => UpdateSelection(index, true, false, true); } - private class TestSelectorWithContainers : TestSelector, IStyleable + private class TestSelectorWithContainers : TestSelector { - Type IStyleable.StyleKey => typeof(TestSelector); + protected override Type StyleKeyOverride => typeof(TestSelector); protected internal override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs index 15ba871e8c..0d3eb80ae7 100644 --- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs @@ -603,9 +603,9 @@ namespace Avalonia.Controls.UnitTests public string Value { get; } } - private class TestTabControl : TabControl, IStyleable + private class TestTabControl : TabControl { - Type IStyleable.StyleKey => typeof(TabControl); + protected override Type StyleKeyOverride => typeof(TabControl); public new ISelectionModel Selection => base.Selection; } } diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs index d4d188f584..241ebc2d38 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Converters/AvaloniaPropertyConverterTest.cs @@ -1,12 +1,10 @@ using System; -using Moq; -using Avalonia.Collections; +using System.ComponentModel; using Avalonia.Markup.Xaml.Converters; +using Avalonia.Markup.Xaml.XamlIl.Runtime; using Avalonia.Styling; +using Moq; using Xunit; -using System.ComponentModel; -using Avalonia.Markup.Xaml.XamlIl.Runtime; -using System.Collections.Generic; namespace Avalonia.Markup.Xaml.UnitTests.Converters { @@ -112,61 +110,17 @@ namespace Avalonia.Markup.Xaml.UnitTests.Converters return tdMock.Object; } - private class Class1 : AvaloniaObject, IStyleable + private class Class1 : StyledElement { public static readonly StyledProperty FooProperty = AvaloniaProperty.Register("Foo"); - public IAvaloniaReadOnlyList Classes - { - get { throw new NotImplementedException(); } - } - - public string Name - { - get { throw new NotImplementedException(); } - } - - public Type StyleKey - { - get { throw new NotImplementedException(); } - } - - public AvaloniaObject TemplatedParent - { - get { throw new NotImplementedException(); } - } - - public ControlTheme GetEffectiveTheme() - { - throw new NotImplementedException(); - } - public ThemeVariant ThemeVariant { get { throw new NotImplementedException(); } } - public event EventHandler ThemeVariantChanged; - - public void DetachStyles() - { - throw new NotImplementedException(); - } - public void DetachStyles(IReadOnlyList styles) - { - throw new NotImplementedException(); - } - - public void InvalidateStyles() - { - throw new NotImplementedException(); - } - - public void StyleApplied(IStyleInstance instance) - { - throw new NotImplementedException(); - } + public event EventHandler ThemeVariantChanged; } private class AttachedOwner From 5b7d5a33aefb8b6a052521842e15239acd386c46 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 May 2023 14:04:27 +0200 Subject: [PATCH 09/11] Fix benchmarks compile error, --- .../Utilities/AvaloniaPropertyDictionaryBenchmarks.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs index e160c8dfa8..ef21100d1f 100644 --- a/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs +++ b/tests/Avalonia.Benchmarks/Utilities/AvaloniaPropertyDictionaryBenchmarks.cs @@ -172,7 +172,7 @@ internal sealed class AvaloniaPropertyValueStoreOld internal class MockProperty : StyledProperty { - public MockProperty(string name) : base(name, typeof(object), new StyledPropertyMetadata()) + public MockProperty(string name) : base(name, typeof(object), typeof(object), new StyledPropertyMetadata()) { } } From 5878814bed7738d7549dcb38b996e0eabb170882 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 May 2023 15:08:57 +0200 Subject: [PATCH 10/11] Added test for #9438. --- .../Primitives/SelectingItemsControlTests.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs index 9d2ffb1fa2..51745e1687 100644 --- a/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs +++ b/tests/Avalonia.Controls.UnitTests/Primitives/SelectingItemsControlTests.cs @@ -2160,6 +2160,45 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + [Fact] + public void Does_Not_Write_To_Bound_SelectedItem_When_DataContext_Changes() + { + // Issue #9438. + var vm1 = new SelectionViewModel(); + vm1.Items.Add("foo"); + vm1.Items.Add("bar"); + vm1.SelectedItem = "bar"; + + var vm2 = new SelectionViewModel(); + vm2.Items.Add("foo"); + vm2.Items.Add("bar"); + vm2.SelectedItem = "bar"; + + var target = new SelectingItemsControl + { + DataContext = vm1, + [!ItemsControl.ItemsSourceProperty] = new Binding("Items"), + [!SelectingItemsControl.SelectedItemProperty] = new Binding("SelectedItem"), + Template = Template(), + }; + + Assert.Equal("bar", target.SelectedItem); + Assert.Equal(1, target.SelectedIndex); + + var selectedItemChangedRaised = 0; + vm2.PropertyChanged += (s, e) => + { + if (e.PropertyName == nameof(vm2.SelectedItem)) + { + ++selectedItemChangedRaised; + } + }; + + target.DataContext = vm2; + + Assert.Equal(0, selectedItemChangedRaised); + } + private static IDisposable Start() { return UnitTestApplication.Start(TestServices.StyledWindow); @@ -2238,6 +2277,7 @@ namespace Avalonia.Controls.UnitTests.Primitives private class SelectionViewModel : NotifyingBase { private int _selectedIndex = -1; + private object _selectedItem; public SelectionViewModel() { @@ -2255,6 +2295,16 @@ namespace Avalonia.Controls.UnitTests.Primitives } } + public object SelectedItem + { + get => _selectedItem; + set + { + _selectedItem = value; + RaisePropertyChanged(); + } + } + public ObservableCollection Items { get; } public ObservableCollection SelectedItems { get; } } @@ -2267,6 +2317,9 @@ namespace Avalonia.Controls.UnitTests.Primitives private class TestSelector : SelectingItemsControl { + public new static readonly DirectProperty SelectedItemsProperty = + SelectingItemsControl.SelectedItemsProperty; + public TestSelector() { From 8c9d610438847b092f4072e21a6fc6d4d859aa67 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 15 May 2023 17:30:42 +0200 Subject: [PATCH 11/11] Make some AvaloniaProperty APIs internal. - Constructors are now internal: properties should only be created using the `Register*` methods anyway - `Notifying` is now internal, this should only be needed by `DataContext` and the constructor which set this was already made internal - Base `OverrideMetadata` methods are now internal: the implementations on `StyledProperty` should be used --- src/Avalonia.Base/AttachedProperty.cs | 2 +- src/Avalonia.Base/AvaloniaProperty.cs | 10 +++--- src/Avalonia.Base/AvaloniaProperty`1.cs | 8 ++--- src/Avalonia.Base/DirectProperty.cs | 41 +------------------------ src/Avalonia.Base/DirectPropertyBase.cs | 4 +-- src/Avalonia.Base/StyledProperty.cs | 2 +- 6 files changed, 14 insertions(+), 53 deletions(-) diff --git a/src/Avalonia.Base/AttachedProperty.cs b/src/Avalonia.Base/AttachedProperty.cs index 27844023ae..6269df2a26 100644 --- a/src/Avalonia.Base/AttachedProperty.cs +++ b/src/Avalonia.Base/AttachedProperty.cs @@ -17,7 +17,7 @@ namespace Avalonia /// The property metadata. /// Whether the property inherits its value. /// A value validation callback. - public AttachedProperty( + internal AttachedProperty( string name, Type ownerType, Type hostType, diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index 332ca1802c..95f8eea852 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -42,7 +42,7 @@ namespace Avalonia /// The class that the property being is registered on. /// The property metadata. /// A callback. - protected AvaloniaProperty( + private protected AvaloniaProperty( string name, Type valueType, Type ownerType, @@ -76,7 +76,7 @@ namespace Avalonia /// The direct property to copy. /// The new owner type. /// Optional overridden metadata. - protected AvaloniaProperty( + private protected AvaloniaProperty( AvaloniaProperty source, Type ownerType, AvaloniaPropertyMetadata? metadata) @@ -153,7 +153,7 @@ namespace Avalonia /// will be true before the property change notifications are sent and false afterwards. This /// callback is intended to support Control.IsDataContextChanging. /// - public Action? Notifying { get; } + internal Action? Notifying { get; } /// /// Gets the integer ID that represents this property. @@ -558,7 +558,7 @@ namespace Avalonia /// /// The type. /// The metadata. - protected void OverrideMetadata(Type type, AvaloniaPropertyMetadata metadata) + private protected void OverrideMetadata(Type type, AvaloniaPropertyMetadata metadata) { _ = type ?? throw new ArgumentNullException(nameof(type)); _ = metadata ?? throw new ArgumentNullException(nameof(metadata)); @@ -577,7 +577,7 @@ namespace Avalonia _singleMetadata = null; } - protected abstract IObservable GetChanged(); + private protected abstract IObservable GetChanged(); private AvaloniaPropertyMetadata GetMetadataWithOverrides(Type type) { diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs index f08c0e0a02..ea2f3577e5 100644 --- a/src/Avalonia.Base/AvaloniaProperty`1.cs +++ b/src/Avalonia.Base/AvaloniaProperty`1.cs @@ -22,7 +22,7 @@ namespace Avalonia /// The class that the property being is registered on. /// The property metadata. /// A callback. - protected AvaloniaProperty( + private protected AvaloniaProperty( string name, Type ownerType, Type hostType, @@ -39,7 +39,7 @@ namespace Avalonia /// The property to copy. /// The new owner type. /// Optional overridden metadata. - protected AvaloniaProperty( + private protected AvaloniaProperty( AvaloniaProperty source, Type ownerType, AvaloniaPropertyMetadata? metadata) @@ -68,10 +68,10 @@ namespace Avalonia _changed.OnNext(e); } - protected override IObservable GetChanged() => Changed; + private protected override IObservable GetChanged() => Changed; [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] - protected BindingValue TryConvert(object? value) + private protected BindingValue TryConvert(object? value) { if (value == UnsetValue) { diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index d02e277074..dd02553eaf 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -24,7 +24,7 @@ namespace Avalonia /// Gets the current value of the property. /// Sets the value of the property. May be null. /// The property metadata. - public DirectProperty( + internal DirectProperty( string name, Func getter, Action? setter, @@ -106,45 +106,6 @@ namespace Avalonia return result; } - /// - /// Registers the direct property on another type. - /// - /// The type of the additional owner. - /// Gets the current value of the property. - /// Sets the value of the property. - /// - /// The value to use when the property is set to - /// - /// The default binding mode for the property. - /// - /// Whether the property is interested in data validation. - /// - /// The property. - public DirectProperty AddOwnerWithDataValidation( - Func getter, - Action setter, - TValue unsetValue = default!, - BindingMode defaultBindingMode = BindingMode.Default, - bool enableDataValidation = false) - where TNewOwner : AvaloniaObject - { - var metadata = new DirectPropertyMetadata( - unsetValue: unsetValue, - defaultBindingMode: defaultBindingMode, - enableDataValidation: enableDataValidation); - - metadata.Merge(GetMetadata(), this); - - var result = new DirectProperty( - this, - getter, - setter, - metadata); - - AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result); - return result; - } - /// internal override TValue InvokeGetter(AvaloniaObject instance) { diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 94694e6209..4dddc12794 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -20,7 +20,7 @@ namespace Avalonia /// The name of the property. /// The type of the class that registers the property. /// The property metadata. - protected DirectPropertyBase( + private protected DirectPropertyBase( string name, Type ownerType, AvaloniaPropertyMetadata metadata) @@ -35,7 +35,7 @@ namespace Avalonia /// The property to copy. /// The new owner type. /// Optional overridden metadata. - protected DirectPropertyBase( + private protected DirectPropertyBase( DirectPropertyBase source, Type ownerType, AvaloniaPropertyMetadata metadata) diff --git a/src/Avalonia.Base/StyledProperty.cs b/src/Avalonia.Base/StyledProperty.cs index 76ba83b88d..ee8748956e 100644 --- a/src/Avalonia.Base/StyledProperty.cs +++ b/src/Avalonia.Base/StyledProperty.cs @@ -24,7 +24,7 @@ namespace Avalonia /// This method is not part of the property's metadata and so cannot be changed after registration. /// /// A callback. - public StyledProperty( + internal StyledProperty( string name, Type ownerType, Type hostType,