From aed62ca4a71bb6e77d37035b97b4c9aacde63b3a Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 7 Aug 2020 16:02:37 -0400 Subject: [PATCH 1/8] Fluent: Add missing MenuItem Header styles --- samples/ControlCatalog/Pages/MenuPage.xaml | 4 ++++ src/Avalonia.Themes.Fluent/MenuItem.xaml | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index e9d2301e89..45564f3e41 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -17,12 +17,16 @@ + + + + diff --git a/src/Avalonia.Themes.Fluent/MenuItem.xaml b/src/Avalonia.Themes.Fluent/MenuItem.xaml index 2115319c3c..3a03ec1acf 100644 --- a/src/Avalonia.Themes.Fluent/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/MenuItem.xaml @@ -8,6 +8,8 @@ Height="200"> + @@ -83,7 +85,6 @@ Content="{TemplateBinding Header}" VerticalAlignment="Center" HorizontalAlignment="Stretch" - TextBlock.Foreground="{TemplateBinding Foreground}" Grid.Column="1"> @@ -213,6 +214,9 @@ + @@ -224,6 +228,9 @@ + @@ -231,9 +238,12 @@ - + From 63a4333832300f4d823f1bb6da755d1f017ffeda Mon Sep 17 00:00:00 2001 From: Maksym Katsydan Date: Fri, 7 Aug 2020 16:53:29 -0400 Subject: [PATCH 2/8] ControlCatalog: Update ContextMenuPage and MenuPage samples --- samples/ControlCatalog/App.xaml | 2 +- samples/ControlCatalog/Pages/ContextMenuPage.xaml | 5 +++-- samples/ControlCatalog/Pages/MenuPage.xaml | 4 ++-- src/Avalonia.Themes.Fluent/ContextMenu.xaml | 2 ++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml index 1c8c38d3e5..bab57f3544 100644 --- a/samples/ControlCatalog/App.xaml +++ b/samples/ControlCatalog/App.xaml @@ -11,7 +11,7 @@ diff --git a/samples/ControlCatalog/Pages/ContextMenuPage.xaml b/samples/ControlCatalog/Pages/ContextMenuPage.xaml index 8ccd8e97f7..260162ddb9 100644 --- a/samples/ControlCatalog/Pages/ContextMenuPage.xaml +++ b/samples/ControlCatalog/Pages/ContextMenuPage.xaml @@ -14,13 +14,14 @@ Padding="48,48,48,48"> - + + - + diff --git a/samples/ControlCatalog/Pages/MenuPage.xaml b/samples/ControlCatalog/Pages/MenuPage.xaml index 45564f3e41..2c09cb9b4d 100644 --- a/samples/ControlCatalog/Pages/MenuPage.xaml +++ b/samples/ControlCatalog/Pages/MenuPage.xaml @@ -16,8 +16,8 @@ Defined in XAML - - + + diff --git a/src/Avalonia.Themes.Fluent/ContextMenu.xaml b/src/Avalonia.Themes.Fluent/ContextMenu.xaml index a6b6156944..6ad038676b 100644 --- a/src/Avalonia.Themes.Fluent/ContextMenu.xaml +++ b/src/Avalonia.Themes.Fluent/ContextMenu.xaml @@ -9,6 +9,8 @@ + From 09b042683a51adbb7db402fefa3d8f86ac77edc9 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 10 Aug 2020 15:17:54 +0200 Subject: [PATCH 3/8] Move some tests. Tests for `AvaloniaObject.IsSet` were in the `Metadata` test class, though `IsSet` has nothing to do with metadata. Moved them into `SetValue` which is more relevant. --- .../AvaloniaObjectTests_Metadata.cs | 29 ------------------- .../AvaloniaObjectTests_SetValue.cs | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs index 8d04f817f1..c3f7b816f2 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs @@ -15,35 +15,6 @@ namespace Avalonia.Base.UnitTests p = AttachedOwner.AttachedProperty; } - [Fact] - public void IsSet_Returns_False_For_Unset_Property() - { - var target = new Class1(); - - Assert.False(target.IsSet(Class1.FooProperty)); - } - - [Fact] - public void IsSet_Returns_False_For_Set_Property() - { - var target = new Class1(); - - target.SetValue(Class1.FooProperty, "foo"); - - Assert.True(target.IsSet(Class1.FooProperty)); - } - - [Fact] - public void IsSet_Returns_False_For_Cleared_Property() - { - var target = new Class1(); - - target.SetValue(Class1.FooProperty, "foo"); - target.SetValue(Class1.FooProperty, AvaloniaProperty.UnsetValue); - - Assert.False(target.IsSet(Class1.FooProperty)); - } - private class Class1 : AvaloniaObject { public static readonly StyledProperty FooProperty = diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs index 66a741e122..954a609315 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetValue.cs @@ -39,6 +39,35 @@ namespace Avalonia.Base.UnitTests Assert.Equal(1, raised); } + [Fact] + public void IsSet_Returns_False_For_Unset_Property() + { + var target = new Class1(); + + Assert.False(target.IsSet(Class1.FooProperty)); + } + + [Fact] + public void IsSet_Returns_False_For_Set_Property() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "foo"); + + Assert.True(target.IsSet(Class1.FooProperty)); + } + + [Fact] + public void IsSet_Returns_False_For_Cleared_Property() + { + var target = new Class1(); + + target.SetValue(Class1.FooProperty, "foo"); + target.SetValue(Class1.FooProperty, AvaloniaProperty.UnsetValue); + + Assert.False(target.IsSet(Class1.FooProperty)); + } + [Fact] public void SetValue_Sets_Value() { From 7d2563de3a882543d8f2a497fa5b9bee7afec56e Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 10 Aug 2020 15:46:04 +0200 Subject: [PATCH 4/8] Expose OverrideMetadata for direct properties. --- src/Avalonia.Base/DirectPropertyBase.cs | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index d42c030245..334b90177b 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -102,6 +102,26 @@ namespace Avalonia return (DirectPropertyMetadata)base.GetMetadata(type); } + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(DirectPropertyMetadata metadata) where T : IAvaloniaObject + { + base.OverrideMetadata(typeof(T), metadata); + } + + /// + /// Overrides the metadata for the property on the specified type. + /// + /// The type. + /// The metadata. + public void OverrideMetadata(Type type, DirectPropertyMetadata metadata) + { + base.OverrideMetadata(type, metadata); + } + /// public override void Accept(IAvaloniaPropertyVisitor vistor, ref TData data) { From 7519f857062672281341d3e733e08ca0213e6ef0 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Mon, 10 Aug 2020 16:23:04 +0200 Subject: [PATCH 5/8] Added property metadata tests. --- .../AvaloniaObjectTests_Metadata.cs | 98 ++++++++++++++++--- 1 file changed, 85 insertions(+), 13 deletions(-) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs index c3f7b816f2..161911dfd5 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Metadata.cs @@ -1,5 +1,4 @@ -using System.Linq; -using System.Reactive.Linq; +using System.Runtime.CompilerServices; using Xunit; namespace Avalonia.Base.UnitTests @@ -9,28 +8,101 @@ namespace Avalonia.Base.UnitTests public AvaloniaObjectTests_Metadata() { // Ensure properties are registered. - AvaloniaProperty p; - p = Class1.FooProperty; - p = Class2.BarProperty; - p = AttachedOwner.AttachedProperty; + RuntimeHelpers.RunClassConstructor(typeof(Class1).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(Class2).TypeHandle); + RuntimeHelpers.RunClassConstructor(typeof(Class3).TypeHandle); + } + + public class StyledProperty : AvaloniaObjectTests_Metadata + { + [Fact] + public void Default_Value_Can_Be_Overridden_In_Derived_Class() + { + var baseValue = Class1.StyledProperty.GetDefaultValue(typeof(Class1)); + var derivedValue = Class1.StyledProperty.GetDefaultValue(typeof(Class2)); + + Assert.Equal("foo", baseValue); + Assert.Equal("bar", derivedValue); + } + + [Fact] + public void Default_Value_Can_Be_Overridden_In_AddOwnered_Property() + { + var baseValue = Class1.StyledProperty.GetDefaultValue(typeof(Class1)); + var addOwneredValue = Class1.StyledProperty.GetDefaultValue(typeof(Class3)); + + Assert.Equal("foo", baseValue); + Assert.Equal("baz", addOwneredValue); + } + } + + public class DirectProperty : AvaloniaObjectTests_Metadata + { + [Fact] + public void Unset_Value_Can_Be_Overridden_In_Derived_Class() + { + var baseValue = Class1.DirectProperty.GetUnsetValue(typeof(Class1)); + var derivedValue = Class1.DirectProperty.GetUnsetValue(typeof(Class2)); + + Assert.Equal("foo", baseValue); + Assert.Equal("bar", derivedValue); + } + + [Fact] + public void Unset_Value_Can_Be_Overridden_In_AddOwnered_Property() + { + var baseValue = Class1.DirectProperty.GetUnsetValue(typeof(Class1)); + var addOwneredValue = Class3.DirectProperty.GetUnsetValue(typeof(Class3)); + + Assert.Equal("foo", baseValue); + Assert.Equal("baz", addOwneredValue); + } } private class Class1 : AvaloniaObject { - public static readonly StyledProperty FooProperty = - AvaloniaProperty.Register("Foo"); + public static readonly StyledProperty StyledProperty = + AvaloniaProperty.Register("Styled", "foo"); + + public static readonly DirectProperty DirectProperty = + AvaloniaProperty.RegisterDirect("Styled", o => o.Direct, unsetValue: "foo"); + + private string _direct; + + public string Direct + { + get => _direct; + } } private class Class2 : Class1 { - public static readonly StyledProperty BarProperty = - AvaloniaProperty.Register("Bar"); + static Class2() + { + StyledProperty.OverrideDefaultValue("bar"); + DirectProperty.OverrideMetadata(new DirectPropertyMetadata("bar")); + } } - private class AttachedOwner + private class Class3 : AvaloniaObject { - public static readonly AttachedProperty AttachedProperty = - AvaloniaProperty.RegisterAttached("Attached"); + public static readonly StyledProperty StyledProperty = + Class1.StyledProperty.AddOwner(); + + public static readonly DirectProperty DirectProperty = + Class1.DirectProperty.AddOwner(o => o.Direct, unsetValue: "baz"); + + private string _direct; + + static Class3() + { + StyledProperty.OverrideDefaultValue("baz"); + } + + public string Direct + { + get => _direct; + } } } } From d5716fce8f24aeebad0485b45abe4dedeb3ee53d Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 11 Aug 2020 15:30:36 +0200 Subject: [PATCH 6/8] Added failing data validation test. --- .../AvaloniaObjectTests_DataValidation.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs index e8cc71c723..65f03b3eca 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_DataValidation.cs @@ -62,6 +62,20 @@ namespace Avalonia.Base.UnitTests Assert.Equal(7, result[3].Value); } + [Fact] + public void Binding_Overridden_Validated_Direct_Property_Calls_UpdateDataValidation() + { + var target = new Class2(); + var source = new Subject>(); + + // Class2 overrides `NonValidatedDirectProperty`'s metadata to enable data validation. + target.Bind(Class1.NonValidatedDirectProperty, source); + source.OnNext(1); + + var result = target.Notifications.Cast>().ToList(); + Assert.Equal(1, result.Count); + } + [Fact] public void Bound_Validated_Direct_String_Property_Can_Be_Set_To_Null() { @@ -150,6 +164,15 @@ namespace Avalonia.Base.UnitTests } } + private class Class2 : Class1 + { + static Class2() + { + NonValidatedDirectProperty.OverrideMetadata( + new DirectPropertyMetadata(enableDataValidation: true)); + } + } + public class ViewModel : NotifyingBase { private string _stringValue; From 37bd384794174e273ffd8706b1c157792bc0607c Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 11 Aug 2020 15:39:23 +0200 Subject: [PATCH 7/8] Use EnabledDataValidation from metadata. `DirectProperty` stored a `EnabledDataValidation` flag in its metadata but also had an `IsDataValidationEnabled` property in `DirectPropertyBase` which was the one used by `AvaloniaObject`. Use the version from metadata so that it can be overridden and remove the flag from `DirectPropertyBase`. --- src/Avalonia.Base/AvaloniaObject.cs | 4 ++- src/Avalonia.Base/AvaloniaProperty.cs | 6 ++--- src/Avalonia.Base/DirectProperty.cs | 25 ++++++------------- src/Avalonia.Base/DirectPropertyBase.cs | 19 ++------------ .../AvaloniaObjectTests_Direct.cs | 6 ++--- .../DirectPropertyTests.cs | 3 +-- 6 files changed, 19 insertions(+), 44 deletions(-) diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs index d18f0b3f94..65233f9230 100644 --- a/src/Avalonia.Base/AvaloniaObject.cs +++ b/src/Avalonia.Base/AvaloniaObject.cs @@ -806,7 +806,9 @@ namespace Avalonia break; } - if (p.IsDataValidationEnabled) + var metadata = p.GetMetadata(GetType()); + + if (metadata.EnableDataValidation == true) { UpdateDataValidation(property, value); } diff --git a/src/Avalonia.Base/AvaloniaProperty.cs b/src/Avalonia.Base/AvaloniaProperty.cs index a873d5fd42..39391490b0 100644 --- a/src/Avalonia.Base/AvaloniaProperty.cs +++ b/src/Avalonia.Base/AvaloniaProperty.cs @@ -369,14 +369,14 @@ namespace Avalonia var metadata = new DirectPropertyMetadata( unsetValue: unsetValue, - defaultBindingMode: defaultBindingMode); + defaultBindingMode: defaultBindingMode, + enableDataValidation: enableDataValidation); var result = new DirectProperty( name, getter, setter, - metadata, - enableDataValidation); + metadata); AvaloniaPropertyRegistry.Instance.Register(typeof(TOwner), result); return result; } diff --git a/src/Avalonia.Base/DirectProperty.cs b/src/Avalonia.Base/DirectProperty.cs index d21969502a..a8120fbd4f 100644 --- a/src/Avalonia.Base/DirectProperty.cs +++ b/src/Avalonia.Base/DirectProperty.cs @@ -23,16 +23,12 @@ namespace Avalonia /// Gets the current value of the property. /// Sets the value of the property. May be null. /// The property metadata. - /// - /// Whether the property is interested in data validation. - /// public DirectProperty( string name, Func getter, Action setter, - DirectPropertyMetadata metadata, - bool enableDataValidation) - : base(name, typeof(TOwner), metadata, enableDataValidation) + DirectPropertyMetadata metadata) + : base(name, typeof(TOwner), metadata) { Contract.Requires(getter != null); @@ -47,16 +43,12 @@ namespace Avalonia /// Gets the current value of the property. /// Sets the value of the property. May be null. /// Optional overridden metadata. - /// - /// Whether the property is interested in data validation. - /// private DirectProperty( DirectPropertyBase source, Func getter, Action setter, - DirectPropertyMetadata metadata, - bool enableDataValidation) - : base(source, typeof(TOwner), metadata, enableDataValidation) + DirectPropertyMetadata metadata) + : base(source, typeof(TOwner), metadata) { Contract.Requires(getter != null); @@ -107,7 +99,8 @@ namespace Avalonia { var metadata = new DirectPropertyMetadata( unsetValue: unsetValue, - defaultBindingMode: defaultBindingMode); + defaultBindingMode: defaultBindingMode, + enableDataValidation: enableDataValidation); metadata.Merge(GetMetadata(), this); @@ -115,8 +108,7 @@ namespace Avalonia (DirectPropertyBase)this, getter, setter, - metadata, - enableDataValidation); + metadata); AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result); return result; @@ -155,8 +147,7 @@ namespace Avalonia this, getter, setter, - metadata, - enableDataValidation); + metadata); AvaloniaPropertyRegistry.Instance.Register(typeof(TNewOwner), result); return result; diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs index 334b90177b..dbc2625b86 100644 --- a/src/Avalonia.Base/DirectPropertyBase.cs +++ b/src/Avalonia.Base/DirectPropertyBase.cs @@ -23,17 +23,12 @@ namespace Avalonia /// The name of the property. /// The type of the class that registers the property. /// The property metadata. - /// - /// Whether the property is interested in data validation. - /// protected DirectPropertyBase( string name, Type ownerType, - PropertyMetadata metadata, - bool enableDataValidation) + PropertyMetadata metadata) : base(name, ownerType, metadata) { - IsDataValidationEnabled = enableDataValidation; } /// @@ -42,17 +37,12 @@ namespace Avalonia /// The property to copy. /// The new owner type. /// Optional overridden metadata. - /// - /// Whether the property is interested in data validation. - /// protected DirectPropertyBase( AvaloniaProperty source, Type ownerType, - PropertyMetadata metadata, - bool enableDataValidation) + PropertyMetadata metadata) : base(source, ownerType, metadata) { - IsDataValidationEnabled = enableDataValidation; } /// @@ -60,11 +50,6 @@ namespace Avalonia /// public abstract Type Owner { get; } - /// - /// Gets a value that indicates whether data validation is enabled for the property. - /// - public bool IsDataValidationEnabled { get; } - /// /// Gets the value of the property on the instance. /// diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs index 60d06b359a..81a8de1046 100644 --- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs +++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs @@ -547,8 +547,7 @@ namespace Avalonia.Base.UnitTests "foo", o => "foo", null, - new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay), - false); + new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay)); var bar = foo.AddOwner(o => "bar"); Assert.Equal(BindingMode.TwoWay, bar.GetMetadata().DefaultBindingMode); @@ -562,8 +561,7 @@ namespace Avalonia.Base.UnitTests "foo", o => "foo", null, - new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay), - false); + new DirectPropertyMetadata(defaultBindingMode: BindingMode.TwoWay)); var bar = foo.AddOwner(o => "bar", defaultBindingMode: BindingMode.OneWayToSource); Assert.Equal(BindingMode.TwoWay, bar.GetMetadata().DefaultBindingMode); diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs index 08b62ae100..e7e3b5764f 100644 --- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs +++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs @@ -11,8 +11,7 @@ namespace Avalonia.Base.UnitTests "test", o => null, null, - new DirectPropertyMetadata(), - false); + new DirectPropertyMetadata()); Assert.True(target.IsDirect); } From edd20a414172c3a6318fa7b8a8b3ffe35daa6939 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 11 Aug 2020 15:42:07 +0200 Subject: [PATCH 8/8] Added API changes to ApiCompatBaseline. --- src/Avalonia.Base/ApiCompatBaseline.txt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..b0b7371cd7 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,6 @@ +Compat issues with assembly Avalonia.Base: +MembersMustExist : Member 'public void Avalonia.DirectProperty..ctor(System.String, System.Func, System.Action, Avalonia.DirectPropertyMetadata, System.Boolean)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.DirectPropertyBase..ctor(Avalonia.AvaloniaProperty, System.Type, Avalonia.PropertyMetadata, System.Boolean)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'protected void Avalonia.DirectPropertyBase..ctor(System.String, System.Type, Avalonia.PropertyMetadata, System.Boolean)' does not exist in the implementation but it does exist in the contract. +MembersMustExist : Member 'public System.Boolean Avalonia.DirectPropertyBase.IsDataValidationEnabled.get()' does not exist in the implementation but it does exist in the contract. +Total Issues: 4