From 7788a29a966e6300af857015c59e5e2edae96532 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Nov 2022 14:17:56 +0100 Subject: [PATCH 01/10] Make BindingExpression and ExpressionObserver internal. --- src/Avalonia.Base/Avalonia.Base.csproj | 3 +++ src/Avalonia.Base/Data/Core/BindingExpression.cs | 2 +- src/Avalonia.Base/Data/Core/ExpressionObserver.cs | 2 +- .../MarkupExtensions/CompiledBindingExtension.cs | 2 +- src/Markup/Avalonia.Markup/Avalonia.Markup.csproj | 1 + src/Markup/Avalonia.Markup/Data/Binding.cs | 2 +- src/Markup/Avalonia.Markup/Data/BindingBase.cs | 12 ++++++------ .../Markup/Parsers/ExpressionObserverBuilder.cs | 2 +- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj index cd122a8b67..4a67191132 100644 --- a/src/Avalonia.Base/Avalonia.Base.csproj +++ b/src/Avalonia.Base/Avalonia.Base.csproj @@ -47,6 +47,9 @@ + + + diff --git a/src/Avalonia.Base/Data/Core/BindingExpression.cs b/src/Avalonia.Base/Data/Core/BindingExpression.cs index 55caf8070e..79942cb9ce 100644 --- a/src/Avalonia.Base/Data/Core/BindingExpression.cs +++ b/src/Avalonia.Base/Data/Core/BindingExpression.cs @@ -13,7 +13,7 @@ namespace Avalonia.Data.Core /// that are sent and received. /// [RequiresUnreferencedCode(TrimmingMessages.TypeConvertionRequiresUnreferencedCodeMessage)] - public class BindingExpression : LightweightObservableBase, IAvaloniaSubject, IDescription + internal class BindingExpression : LightweightObservableBase, IAvaloniaSubject, IDescription { private readonly ExpressionObserver _inner; private readonly Type _targetType; diff --git a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs index 0818b5fa62..ce3549c4ad 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionObserver.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionObserver.cs @@ -11,7 +11,7 @@ namespace Avalonia.Data.Core /// /// Observes and sets the value of an expression on an object. /// - public class ExpressionObserver : LightweightObservableBase, IDescription + internal class ExpressionObserver : LightweightObservableBase, IDescription { /// /// An ordered collection of property accessor plugins that can be used to customize diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs index 9990ad4731..d0a4de09ab 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs @@ -35,7 +35,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions }; } - protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) + private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation) { if (Source != null) { diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj index e3878b5bc6..ec44eeb38f 100644 --- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj +++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj @@ -19,6 +19,7 @@ + diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs index 66907f33d0..37e7be5e3f 100644 --- a/src/Markup/Avalonia.Markup/Data/Binding.cs +++ b/src/Markup/Avalonia.Markup/Data/Binding.cs @@ -56,7 +56,7 @@ namespace Avalonia.Data /// public Func? TypeResolver { get; set; } - protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, bool enableDataValidation) + private protected override ExpressionObserver CreateExpressionObserver(AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, bool enableDataValidation) { _ = target ?? throw new ArgumentNullException(nameof(target)); diff --git a/src/Markup/Avalonia.Markup/Data/BindingBase.cs b/src/Markup/Avalonia.Markup/Data/BindingBase.cs index 90f312a249..8c8c50763b 100644 --- a/src/Markup/Avalonia.Markup/Data/BindingBase.cs +++ b/src/Markup/Avalonia.Markup/Data/BindingBase.cs @@ -70,7 +70,7 @@ namespace Avalonia.Data public WeakReference? NameScope { get; set; } - protected abstract ExpressionObserver CreateExpressionObserver( + private protected abstract ExpressionObserver CreateExpressionObserver( AvaloniaObject target, AvaloniaProperty? targetProperty, object? anchor, @@ -127,7 +127,7 @@ namespace Avalonia.Data return new InstancedBinding(subject, Mode, Priority); } - protected ExpressionObserver CreateDataContextObserver( + private protected ExpressionObserver CreateDataContextObserver( AvaloniaObject target, ExpressionNode node, bool targetIsDataContext, @@ -162,7 +162,7 @@ namespace Avalonia.Data } } - protected ExpressionObserver CreateElementObserver( + private protected ExpressionObserver CreateElementObserver( StyledElement target, string elementName, ExpressionNode node) @@ -178,7 +178,7 @@ namespace Avalonia.Data return result; } - protected ExpressionObserver CreateFindAncestorObserver( + private protected ExpressionObserver CreateFindAncestorObserver( StyledElement target, RelativeSource relativeSource, ExpressionNode node) @@ -211,7 +211,7 @@ namespace Avalonia.Data null); } - protected ExpressionObserver CreateSourceObserver( + private protected ExpressionObserver CreateSourceObserver( object source, ExpressionNode node) { @@ -220,7 +220,7 @@ namespace Avalonia.Data return new ExpressionObserver(source, node); } - protected ExpressionObserver CreateTemplatedParentObserver( + private protected ExpressionObserver CreateTemplatedParentObserver( AvaloniaObject target, ExpressionNode node) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs index 00f40dfcd3..97e9698c13 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionObserverBuilder.cs @@ -6,7 +6,7 @@ using Avalonia.Utilities; namespace Avalonia.Markup.Parsers { - public static class ExpressionObserverBuilder + internal static class ExpressionObserverBuilder { [RequiresUnreferencedCode(TrimmingMessages.ReflectionBindingRequiresUnreferencedCodeMessage)] internal static (ExpressionNode Node, SourceMode Mode) Parse(string expression, bool enableValidation = false, Func? typeResolver = null, From cb8a21fb836aeab380541c07b58845005a598b11 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 30 Nov 2022 14:51:13 +0100 Subject: [PATCH 02/10] Make ExpressionNode (and derived) internal. --- src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs | 2 +- src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs | 2 +- src/Avalonia.Base/Data/Core/ExpressionNode.cs | 2 +- src/Avalonia.Base/Data/Core/IndexerNodeBase.cs | 2 +- src/Avalonia.Base/Data/Core/LogicalNotNode.cs | 2 +- src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs | 2 +- src/Avalonia.Base/Data/Core/SettableNode.cs | 2 +- src/Avalonia.Base/Data/Core/StreamNode.cs | 2 +- src/Avalonia.Base/Data/Core/TypeCastNode.cs | 2 +- .../MarkupExtensions/CompiledBindings/CompiledBindingPath.cs | 2 +- .../MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs | 2 +- .../Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs | 2 +- .../Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs | 2 +- src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs | 2 +- 14 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs index 536c14dcf9..92fc843394 100644 --- a/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/AvaloniaPropertyAccessorNode.cs @@ -3,7 +3,7 @@ using Avalonia.Reactive; namespace Avalonia.Data.Core { - public class AvaloniaPropertyAccessorNode : SettableNode + internal class AvaloniaPropertyAccessorNode : SettableNode { private IDisposable? _subscription; private readonly bool _enableValidation; diff --git a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs index 4e142fbee9..b333fa9047 100644 --- a/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/EmptyExpressionNode.cs @@ -1,6 +1,6 @@ namespace Avalonia.Data.Core { - public class EmptyExpressionNode : ExpressionNode + internal class EmptyExpressionNode : ExpressionNode { public override string Description => "."; } diff --git a/src/Avalonia.Base/Data/Core/ExpressionNode.cs b/src/Avalonia.Base/Data/Core/ExpressionNode.cs index 5fb2bb5c13..30fc71cfb4 100644 --- a/src/Avalonia.Base/Data/Core/ExpressionNode.cs +++ b/src/Avalonia.Base/Data/Core/ExpressionNode.cs @@ -2,7 +2,7 @@ using System; namespace Avalonia.Data.Core { - public abstract class ExpressionNode + internal abstract class ExpressionNode { protected static readonly WeakReference UnsetReference = new WeakReference(AvaloniaProperty.UnsetValue); diff --git a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs index 2fad96701d..9ec256225b 100644 --- a/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs +++ b/src/Avalonia.Base/Data/Core/IndexerNodeBase.cs @@ -7,7 +7,7 @@ using Avalonia.Utilities; namespace Avalonia.Data.Core { - public abstract class IndexerNodeBase : SettableNode, + internal abstract class IndexerNodeBase : SettableNode, IWeakEventSubscriber, IWeakEventSubscriber { diff --git a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs index 45837db73d..81b07bfe85 100644 --- a/src/Avalonia.Base/Data/Core/LogicalNotNode.cs +++ b/src/Avalonia.Base/Data/Core/LogicalNotNode.cs @@ -3,7 +3,7 @@ using System.Globalization; namespace Avalonia.Data.Core { - public class LogicalNotNode : ExpressionNode, ITransformNode + internal class LogicalNotNode : ExpressionNode, ITransformNode { public override string Description => "!"; diff --git a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs index 1b79fed6e7..3898d232ec 100644 --- a/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs +++ b/src/Avalonia.Base/Data/Core/PropertyAccessorNode.cs @@ -5,7 +5,7 @@ using Avalonia.Data.Core.Plugins; namespace Avalonia.Data.Core { [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] - public class PropertyAccessorNode : SettableNode + internal class PropertyAccessorNode : SettableNode { private readonly bool _enableValidation; private IPropertyAccessorPlugin? _customPlugin; diff --git a/src/Avalonia.Base/Data/Core/SettableNode.cs b/src/Avalonia.Base/Data/Core/SettableNode.cs index 9ad9ace814..4980e4487a 100644 --- a/src/Avalonia.Base/Data/Core/SettableNode.cs +++ b/src/Avalonia.Base/Data/Core/SettableNode.cs @@ -2,7 +2,7 @@ namespace Avalonia.Data.Core { - public abstract class SettableNode : ExpressionNode + internal abstract class SettableNode : ExpressionNode { public bool SetTargetValue(object? value, BindingPriority priority) { diff --git a/src/Avalonia.Base/Data/Core/StreamNode.cs b/src/Avalonia.Base/Data/Core/StreamNode.cs index ba18a2173b..d3da6414ac 100644 --- a/src/Avalonia.Base/Data/Core/StreamNode.cs +++ b/src/Avalonia.Base/Data/Core/StreamNode.cs @@ -6,7 +6,7 @@ using Avalonia.Reactive; namespace Avalonia.Data.Core { [RequiresUnreferencedCode(TrimmingMessages.ExpressionNodeRequiresUnreferencedCodeMessage)] - public class StreamNode : ExpressionNode + internal class StreamNode : ExpressionNode { private IStreamPlugin? _customPlugin = null; private IDisposable? _subscription; diff --git a/src/Avalonia.Base/Data/Core/TypeCastNode.cs b/src/Avalonia.Base/Data/Core/TypeCastNode.cs index 3a2ca955fa..655bfbc7a1 100644 --- a/src/Avalonia.Base/Data/Core/TypeCastNode.cs +++ b/src/Avalonia.Base/Data/Core/TypeCastNode.cs @@ -4,7 +4,7 @@ using System.Text; namespace Avalonia.Data.Core { - public class TypeCastNode : ExpressionNode + internal class TypeCastNode : ExpressionNode { public override string Description => $"as {TargetType.FullName}"; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs index b7f2261324..2b62d33349 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs @@ -24,7 +24,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.CompiledBindingSafeSupressWarningMessage)] - public ExpressionNode BuildExpression(bool enableValidation) + internal ExpressionNode BuildExpression(bool enableValidation) { ExpressionNode pathRoot = null; ExpressionNode path = null; diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs index 1252ec7eca..b543fd9c01 100644 --- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs +++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs @@ -3,7 +3,7 @@ using Avalonia.Data.Core; namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings { - public class StrongTypeCastNode : TypeCastNode + internal class StrongTypeCastNode : TypeCastNode { private Func _cast; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs index 4fc17e440b..e676c74879 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/ElementNameNode.cs @@ -6,7 +6,7 @@ using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { - public class ElementNameNode : ExpressionNode + internal class ElementNameNode : ExpressionNode { private readonly WeakReference _nameScope; private readonly string _name; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs index ffbd34d492..383a160814 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/FindAncestorNode.cs @@ -5,7 +5,7 @@ using Avalonia.Reactive; namespace Avalonia.Markup.Parsers.Nodes { - public class FindAncestorNode : ExpressionNode + internal class FindAncestorNode : ExpressionNode { private readonly int _level; private readonly Type? _ancestorType; diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs index 1cd233c68a..2cb87efa65 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/Nodes/SelfNode.cs @@ -2,7 +2,7 @@ namespace Avalonia.Markup.Parsers.Nodes { - public class SelfNode : ExpressionNode + internal class SelfNode : ExpressionNode { public override string Description => "$self"; } From 67c9221d3cf550bb17b2ad248bb7bf8b3acda858 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 14 Dec 2022 15:21:24 +0100 Subject: [PATCH 03/10] Tweaked InstancedBinding API. - Remove `Value` from the API, will always contain an `IObservable` from now - Remove subject from the API, caller can try to cast the observable itself --- src/Avalonia.Base/Data/BindingOperations.cs | 59 ++++++++----------- src/Avalonia.Base/Data/InstancedBinding.cs | 43 +++++++------- src/Avalonia.Base/Styling/Setter.cs | 2 +- .../DataGridBoundColumn.cs | 5 +- .../Avalonia.Markup/Data/MultiBinding.cs | 4 +- .../Data/BindingTests.cs | 6 +- .../Data/BindingTests_Converters.cs | 8 +-- .../Data/BindingTests_DataValidation.cs | 6 +- .../Data/MultiBindingTests.cs | 4 +- 9 files changed, 63 insertions(+), 74 deletions(-) diff --git a/src/Avalonia.Base/Data/BindingOperations.cs b/src/Avalonia.Base/Data/BindingOperations.cs index 0b737dd959..e53ae40cb1 100644 --- a/src/Avalonia.Base/Data/BindingOperations.cs +++ b/src/Avalonia.Base/Data/BindingOperations.cs @@ -41,54 +41,41 @@ namespace Avalonia.Data { case BindingMode.Default: case BindingMode.OneWay: - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - return target.Bind(property, binding.Observable, binding.Priority); + return target.Bind(property, binding.Source, binding.Priority); case BindingMode.TwoWay: - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - if (binding.Subject is null) + { + if (binding.Source is not IObserver observer) throw new InvalidOperationException("InstancedBinding does not contain a subject."); return new TwoWayBindingDisposable( - target.Bind(property, binding.Observable, binding.Priority), - target.GetObservable(property).Subscribe(binding.Subject)); + target.Bind(property, binding.Source, binding.Priority), + target.GetObservable(property).Subscribe(observer)); + } case BindingMode.OneTime: - if (binding.Observable is {} source) - { - // Perf: Avoid allocating closure in the outer scope. - var targetCopy = target; - var propertyCopy = property; - var bindingCopy = binding; - - return source - .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) - .Take(1) - .Subscribe(x => targetCopy.SetValue( - propertyCopy, - BindingNotification.ExtractValue(x), - bindingCopy.Priority)); - } - else - { - target.SetValue(property, binding.Value, binding.Priority); - return Disposable.Empty; - } + { + // Perf: Avoid allocating closure in the outer scope. + var targetCopy = target; + var propertyCopy = property; + var bindingCopy = binding; + + return binding.Source + .Where(x => BindingNotification.ExtractValue(x) != AvaloniaProperty.UnsetValue) + .Take(1) + .Subscribe(x => targetCopy.SetValue( + propertyCopy, + BindingNotification.ExtractValue(x), + bindingCopy.Priority)); + } case BindingMode.OneWayToSource: { - if (binding.Observable is null) - throw new InvalidOperationException("InstancedBinding does not contain an observable."); - if (binding.Subject is null) + if (binding.Source is not IObserver observer) throw new InvalidOperationException("InstancedBinding does not contain a subject."); - // Perf: Avoid allocating closure in the outer scope. - var bindingCopy = binding; - return Observable.CombineLatest( - binding.Observable, + binding.Source, target.GetObservable(property), (_, v) => v) - .Subscribe(x => bindingCopy.Subject.OnNext(x)); + .Subscribe(x => observer.OnNext(x)); } default: diff --git a/src/Avalonia.Base/Data/InstancedBinding.cs b/src/Avalonia.Base/Data/InstancedBinding.cs index a60c1d72ec..00e5c3d8e6 100644 --- a/src/Avalonia.Base/Data/InstancedBinding.cs +++ b/src/Avalonia.Base/Data/InstancedBinding.cs @@ -1,5 +1,6 @@ using System; using Avalonia.Reactive; +using ObservableEx = Avalonia.Reactive.Observable; namespace Avalonia.Data { @@ -14,11 +15,23 @@ namespace Avalonia.Data /// public class InstancedBinding { - internal InstancedBinding(object? value, BindingMode mode, BindingPriority priority) + /// + /// Initializes a new instance of the class. + /// + /// The binding source. + /// The binding mode. + /// The priority of the binding. + /// + /// This constructor can be used to create any type of binding and as such requires an + /// as the binding source because this is the only binding + /// source which can be used for all binding modes. If you wish to create an instance with + /// something other than a subject, use one of the static creation methods on this class. + /// + internal InstancedBinding(IObservable source, BindingMode mode, BindingPriority priority) { Mode = mode; Priority = priority; - Value = value; + Source = source ?? throw new ArgumentNullException(nameof(source)); } /// @@ -32,24 +45,12 @@ namespace Avalonia.Data public BindingPriority Priority { get; } /// - /// Gets the value or source of the binding. - /// - public object? Value { get; } - - /// - /// Gets the as an observable. + /// Gets the binding source observable. /// - public IObservable? Observable => Value as IObservable; + public IObservable Source { get; } - /// - /// Gets the as an observer. - /// - public IObserver? Observer => Value as IObserver; - - /// - /// Gets the as an subject. - /// - internal IAvaloniaSubject? Subject => Value as IAvaloniaSubject; + [Obsolete("Use Source property")] + public IObservable Observable => Source; /// /// Creates a new one-time binding with a fixed value. @@ -61,7 +62,7 @@ namespace Avalonia.Data object value, BindingPriority priority = BindingPriority.LocalValue) { - return new InstancedBinding(value, BindingMode.OneTime, priority); + return new InstancedBinding(ObservableEx.SingleValue(value), BindingMode.OneTime, priority); } /// @@ -106,7 +107,7 @@ namespace Avalonia.Data { _ = observer ?? throw new ArgumentNullException(nameof(observer)); - return new InstancedBinding(observer, BindingMode.OneWayToSource, priority); + return new InstancedBinding((IObservable)observer, BindingMode.OneWayToSource, priority); } /// @@ -135,7 +136,7 @@ namespace Avalonia.Data /// An instance. public InstancedBinding WithPriority(BindingPriority priority) { - return new InstancedBinding(Value, Mode, priority); + return new InstancedBinding(Source, Mode, priority); } } } diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index b7b44a7dfe..093597c6a0 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -109,7 +109,7 @@ namespace Avalonia.Styling if (mode == BindingMode.OneWay || mode == BindingMode.TwoWay) { - return new PropertySetterBindingInstance(target, instance, Property, mode, i.Observable!); + return new PropertySetterBindingInstance(target, instance, Property, mode, i.Source); } throw new NotSupportedException(); diff --git a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs index e859a6e725..8f532b9803 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs @@ -7,6 +7,7 @@ using Avalonia.Data; using System; using Avalonia.Controls.Utils; using Avalonia.Markup.Xaml.MarkupExtensions; +using Avalonia.Reactive; namespace Avalonia.Controls { @@ -111,9 +112,9 @@ namespace Avalonia.Controls if (result != null) { - if(result.Subject != null) + if(result.Source is IAvaloniaSubject subject) { - var bindingHelper = new CellEditBinding(result.Subject); + var bindingHelper = new CellEditBinding(subject); var instanceBinding = new InstancedBinding(bindingHelper.InternalSubject, result.Mode, result.Priority); BindingOperations.Apply(target, property, instanceBinding, null); diff --git a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs index 1515ff2c90..993f63b4d3 100644 --- a/src/Markup/Avalonia.Markup/Data/MultiBinding.cs +++ b/src/Markup/Avalonia.Markup/Data/MultiBinding.cs @@ -85,8 +85,8 @@ namespace Avalonia.Data var children = Bindings.Select(x => x.Initiate(target, null)); - var input = children.Select(x => x?.Observable!) - .Where(x => x is not null) + var input = children.Select(x => x?.Source) + .Where(x => x is not null)! .CombineLatest() .Select(x => ConvertValue(x, targetType, converter)) .Where(x => x != BindingOperations.DoNothing); diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs index 656c2cbbbc..3ba8e8354d 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs @@ -334,7 +334,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.IsType(((BindingExpression)result).Converter); } @@ -350,7 +350,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Foo", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.Same(converter.Object, ((BindingExpression)result).Converter); } @@ -367,7 +367,7 @@ namespace Avalonia.Markup.UnitTests.Data Path = "Bar", }; - var result = binding.Initiate(target, TextBox.TextProperty).Value; + var result = binding.Initiate(target, TextBox.TextProperty).Source; Assert.Same("foo", ((BindingExpression)result).ConverterParameter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs index 2a0750b131..680c49d098 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_Converters.cs @@ -24,7 +24,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TextProperty).Observable; + TextBlock.TextProperty).Source; Assert.Same(StringConverters.IsNullOrEmpty, expressionObserver.Converter); } @@ -46,7 +46,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TextProperty).Observable; + TextBlock.TextProperty).Source; Assert.IsType(expressionObserver.Converter); } @@ -69,7 +69,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.TagProperty).Observable; + TextBlock.TagProperty).Source; Assert.IsType(expressionObserver.Converter); } @@ -92,7 +92,7 @@ namespace Avalonia.Markup.UnitTests.Data var expressionObserver = (BindingExpression)target.Initiate( textBlock, - TextBlock.MarginProperty).Observable; + TextBlock.MarginProperty).Source; Assert.Same(DefaultValueConverter.Instance, expressionObserver.Converter); } diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs index 45deb97f51..505eddb146 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingTests_DataValidation.cs @@ -20,7 +20,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: false); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); @@ -38,7 +38,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)); var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); @@ -56,7 +56,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new Binding(nameof(Class1.Foo)) { Priority = BindingPriority.Template }; var instanced = target.Initiate(textBlock, TextBlock.TextProperty, enableDataValidation: true); - var subject = (BindingExpression)instanced.Value; + var subject = (BindingExpression)instanced.Source; object result = null; subject.Subscribe(x => result = x); diff --git a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs index a7ef2c4e4d..bf9631760a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/MultiBindingTests.cs @@ -30,7 +30,7 @@ namespace Avalonia.Markup.UnitTests.Data }; var target = new Control { DataContext = source }; - var observable = binding.Initiate(target, null).Observable; + var observable = binding.Initiate(target, null).Source; var result = await observable.Take(1); Assert.Equal("1,2,3", result); @@ -59,7 +59,7 @@ namespace Avalonia.Markup.UnitTests.Data }; var target = new Control { DataContext = source }; - var observable = binding.Initiate(target, null).Observable; + var observable = binding.Initiate(target, null).Source; var result = await observable.Take(1); Assert.Equal("1,2,3", result); From d6a68c8af838b831ba595e22090578c8bbad91c6 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Tue, 17 Jan 2023 18:34:55 +0100 Subject: [PATCH 04/10] Expose binding plugins in a different API. --- .../Data/Core/Plugins/BindingPlugins.cs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs diff --git a/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs b/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs new file mode 100644 index 0000000000..6d88d55774 --- /dev/null +++ b/src/Avalonia.Base/Data/Core/Plugins/BindingPlugins.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; + +namespace Avalonia.Data.Core.Plugins +{ + /// + /// Holds a registry of plugins used for bindings. + /// + public static class BindingPlugins + { + /// + /// An ordered collection of property accessor plugins that can be used to customize + /// the reading and subscription of property values on a type. + /// + public static IList PropertyAccessors => ExpressionObserver.PropertyAccessors; + + /// + /// An ordered collection of validation checker plugins that can be used to customize + /// the validation of view model and model data. + /// + public static IList DataValidators => ExpressionObserver.DataValidators; + + /// + /// An ordered collection of stream plugins that can be used to customize the behavior + /// of the '^' stream binding operator. + /// + public static IList StreamHandlers => ExpressionObserver.StreamHandlers; + } +} From 43e00b710bc55e7824a7e74661088b4d0f6cc052 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 18 Jan 2023 11:36:33 +0100 Subject: [PATCH 05/10] Make concrete binding plugin classes internal. --- .../Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs | 2 +- .../Data/Core/Plugins/DataAnnotationsValidationPlugin.cs | 2 +- .../Data/Core/Plugins/ExceptionValidationPlugin.cs | 2 +- src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs | 2 +- .../Data/Core/Plugins/InpcPropertyAccessorPlugin.cs | 2 +- src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs | 2 +- src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs | 2 +- src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs index 34f8e568d4..f111d8917b 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/AvaloniaPropertyAccessorPlugin.cs @@ -8,7 +8,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Reads a property from a . /// - public class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin + internal class AvaloniaPropertyAccessorPlugin : IPropertyAccessorPlugin { /// [RequiresUnreferencedCode(TrimmingMessages.PropertyAccessorsRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs index bc300386b9..ba5f59ea23 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on that have s. /// - public class DataAnnotationsValidationPlugin : IDataValidationPlugin + internal class DataAnnotationsValidationPlugin : IDataValidationPlugin { /// [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs index 2bb8da2c74..e60a341309 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ExceptionValidationPlugin.cs @@ -7,7 +7,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties that report errors by throwing exceptions. /// - public class ExceptionValidationPlugin : IDataValidationPlugin + internal class ExceptionValidationPlugin : IDataValidationPlugin { /// [RequiresUnreferencedCode(TrimmingMessages.DataValidationPluginRequiresUnreferencedCodeMessage)] diff --git a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs index 87a2f67ee8..3384a99333 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/IndeiValidationPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// /// Validates properties on objects that implement . /// - public class IndeiValidationPlugin : IDataValidationPlugin + internal class IndeiValidationPlugin : IDataValidationPlugin { private static readonly WeakEvent ErrorsChangedWeakEvent = WeakEvent.Register( diff --git a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs index 5b19e995cc..7c2caf02b4 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs @@ -11,7 +11,7 @@ namespace Avalonia.Data.Core.Plugins /// Reads a property from a standard C# object that optionally supports the /// interface. /// - public class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin + internal class InpcPropertyAccessorPlugin : IPropertyAccessorPlugin { private readonly Dictionary<(Type, string), PropertyInfo?> _propertyLookup = new Dictionary<(Type, string), PropertyInfo?>(); diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs index 2397ce483d..8170edd653 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs @@ -6,7 +6,7 @@ using System.Reflection; namespace Avalonia.Data.Core.Plugins { - public class MethodAccessorPlugin : IPropertyAccessorPlugin + internal class MethodAccessorPlugin : IPropertyAccessorPlugin { private readonly Dictionary<(Type, string), MethodInfo?> _methodLookup = new Dictionary<(Type, string), MethodInfo?>(); diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs index b40628fd35..2b9da0a61a 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// Handles binding to s for the '^' stream binding operator. /// [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] - public class ObservableStreamPlugin : IStreamPlugin + internal class ObservableStreamPlugin : IStreamPlugin { private static MethodInfo? s_observableGeneric; private static MethodInfo? s_observableSelect; diff --git a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs index 715f4604cf..42a050778e 100644 --- a/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs +++ b/src/Avalonia.Base/Data/Core/Plugins/TaskStreamPlugin.cs @@ -10,7 +10,7 @@ namespace Avalonia.Data.Core.Plugins /// Handles binding to s for the '^' stream binding operator. /// [UnconditionalSuppressMessage("Trimming", "IL3050", Justification = TrimmingMessages.IgnoreNativeAotSupressWarningMessage)] - public class TaskStreamPlugin : IStreamPlugin + internal class TaskStreamPlugin : IStreamPlugin { /// /// Checks whether this plugin handles the specified value. From 96038db08fd908a3a25ca223ecdaf539fb21b5c1 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 19 Jan 2023 16:12:00 +0100 Subject: [PATCH 06/10] fix: ItemTemplate is not applied on Menu/MenuItem --- src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml | 1 + src/Avalonia.Themes.Simple/Controls/MenuItem.xaml | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml index e87f4b7d93..72a7797bc3 100644 --- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml @@ -92,6 +92,7 @@ + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding ItemTemplate}"> From f2908c6c79075bdbeb6a3b19a72a789f4245289b Mon Sep 17 00:00:00 2001 From: cristinathoughtpennies Date: Thu, 19 Jan 2023 10:56:24 -0600 Subject: [PATCH 07/10] HeaderDoubleTapped in TreeViewItem is now protected virtual instead of private --- src/Avalonia.Controls/TreeViewItem.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index fb20a7ee14..532d0e32db 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -264,6 +264,15 @@ namespace Avalonia.Controls Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll } } + + protected virtual void HeaderDoubleTapped(object? sender, TappedEventArgs e) + { + if (ItemCount > 0) + { + IsExpanded = !IsExpanded; + e.Handled = true; + } + } private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T : class { @@ -277,14 +286,5 @@ namespace Avalonia.Controls return logical != null ? result : @default; } - - private void HeaderDoubleTapped(object? sender, TappedEventArgs e) - { - if (ItemCount > 0) - { - IsExpanded = !IsExpanded; - e.Handled = true; - } - } } } From 32cfbe6578f2d4952eed49ddd3d6074d3f34895f Mon Sep 17 00:00:00 2001 From: rabbitism Date: Fri, 20 Jan 2023 15:30:27 +0800 Subject: [PATCH 08/10] fix: fix screen display in control catalog. --- samples/ControlCatalog/Pages/ScreenPage.cs | 82 +++++++++++++++++----- 1 file changed, 63 insertions(+), 19 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index b5b80fb147..94fc4da7ef 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -1,5 +1,7 @@ using System; using System.Globalization; +using System.Linq; +using System.Net.Http.Headers; using Avalonia; using Avalonia.Controls; using Avalonia.Media; @@ -12,6 +14,11 @@ namespace ControlCatalog.Pages public class ScreenPage : UserControl { private double _leftMost; + private double _topMost; + private IBrush _primaryBrush = SolidColorBrush.Parse("#FF0078D7"); + private IBrush _defaultBrush = Brushes.LightGray; + private IPen _activePen = new Pen(Brushes.Black); + private IPen _defaultPen = new Pen(Brushes.DarkGray); protected override bool BypassFlowDirectionPolicies => true; @@ -37,51 +44,88 @@ namespace ControlCatalog.Pages var drawBrush = Brushes.Black; Pen p = new Pen(drawBrush); - foreach (Screen screen in screens) + var activeScreen = w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))); + double maxBottom = 0; + + for (int i = 0; i Date: Fri, 20 Jan 2023 15:47:57 +0800 Subject: [PATCH 09/10] fix: remove unused pen. --- samples/ControlCatalog/Pages/ScreenPage.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs index 94fc4da7ef..2cdd031693 100644 --- a/samples/ControlCatalog/Pages/ScreenPage.cs +++ b/samples/ControlCatalog/Pages/ScreenPage.cs @@ -41,9 +41,6 @@ namespace ControlCatalog.Pages var screens = w.Screens.All; var scaling = ((IRenderRoot)w).RenderScaling; - var drawBrush = Brushes.Black; - Pen p = new Pen(drawBrush); - var activeScreen = w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))); double maxBottom = 0; @@ -114,7 +111,7 @@ namespace ControlCatalog.Pages } - context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f+Math.Abs(_topMost), w.Bounds.Width / 10, w.Bounds.Height / 10)); + context.DrawRectangle(_activePen, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f+Math.Abs(_topMost), w.Bounds.Width / 10, w.Bounds.Height / 10)); } private static FormattedText CreateFormattedText(string textToFormat, double size = 12) From b0ebd5e1d1d35ded49d2a12c7f89d39175590cf9 Mon Sep 17 00:00:00 2001 From: cristinathoughtpennies Date: Fri, 20 Jan 2023 09:41:10 -0600 Subject: [PATCH 10/10] Update HeaderDoubleTapped functions to match conventions --- src/Avalonia.Controls/TreeViewItem.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs index 532d0e32db..022e1a74b1 100644 --- a/src/Avalonia.Controls/TreeViewItem.cs +++ b/src/Avalonia.Controls/TreeViewItem.cs @@ -265,7 +265,10 @@ namespace Avalonia.Controls } } - protected virtual void HeaderDoubleTapped(object? sender, TappedEventArgs e) + /// + /// Invoked when the event occurs in the header. + /// + protected virtual void OnHeaderDoubleTapped(TappedEventArgs e) { if (ItemCount > 0) { @@ -286,5 +289,10 @@ namespace Avalonia.Controls return logical != null ? result : @default; } + + private void HeaderDoubleTapped(object? sender, TappedEventArgs e) + { + OnHeaderDoubleTapped(e); + } } }