committed by
GitHub
44 changed files with 1851 additions and 170 deletions
@ -0,0 +1,8 @@ |
|||||
|
namespace Avalonia.PropertyStore |
||||
|
{ |
||||
|
internal interface IBatchUpdate |
||||
|
{ |
||||
|
void BeginBatchUpdate(); |
||||
|
void EndBatchUpdate(); |
||||
|
} |
||||
|
} |
||||
@ -1,6 +1,7 @@ |
|||||
Compat issues with assembly Avalonia.Controls: |
Compat issues with assembly Avalonia.Controls: |
||||
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
MembersMustExist : Member 'public void Avalonia.Controls.Embedding.Offscreen.OffscreenTopLevelImplBase.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
||||
|
MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaProperty Avalonia.Controls.Notifications.NotificationCard.CloseOnClickProperty' does not exist in the implementation but it does exist in the contract. |
||||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. |
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.ICursorImpl)' is present in the implementation but not in the contract. |
||||
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. |
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' is present in the contract but not in the implementation. |
||||
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
MembersMustExist : Member 'public void Avalonia.Platform.ITopLevelImpl.SetCursor(Avalonia.Platform.IPlatformHandle)' does not exist in the implementation but it does exist in the contract. |
||||
Total Issues: 4 |
Total Issues: 5 |
||||
|
|||||
@ -0,0 +1,7 @@ |
|||||
|
namespace Avalonia.Platform |
||||
|
{ |
||||
|
public interface IApplicationPlatformEvents |
||||
|
{ |
||||
|
void RaiseUrlsOpened(string[] urls); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia |
||||
|
{ |
||||
|
public class UrlOpenedEventArgs : EventArgs |
||||
|
{ |
||||
|
public UrlOpenedEventArgs(string[] urls) |
||||
|
{ |
||||
|
Urls = urls; |
||||
|
} |
||||
|
|
||||
|
public string[] Urls { get; } |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Native.Interop; |
||||
|
using Avalonia.Platform; |
||||
|
|
||||
|
namespace Avalonia.Native |
||||
|
{ |
||||
|
internal class AvaloniaNativeApplicationPlatform : CallbackBase, IAvnApplicationEvents |
||||
|
{ |
||||
|
void IAvnApplicationEvents.FilesOpened(IAvnStringArray urls) |
||||
|
{ |
||||
|
((IApplicationPlatformEvents)Application.Current).RaiseUrlsOpened(urls.ToStringArray()); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
#nullable enable |
||||
|
|
||||
|
namespace Avalonia.Styling |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A <see cref="Setter"/> which has been instanced on a control and whose value is lazily
|
||||
|
/// evaluated.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The target property type.</typeparam>
|
||||
|
internal class PropertySetterLazyInstance<T> : SingleSubscriberObservableBase<BindingValue<T>>, |
||||
|
ISetterInstance |
||||
|
{ |
||||
|
private readonly IStyleable _target; |
||||
|
private readonly StyledPropertyBase<T>? _styledProperty; |
||||
|
private readonly DirectPropertyBase<T>? _directProperty; |
||||
|
private readonly Func<T> _valueFactory; |
||||
|
private BindingValue<T> _value; |
||||
|
private IDisposable? _subscription; |
||||
|
private bool _isActive; |
||||
|
|
||||
|
public PropertySetterLazyInstance( |
||||
|
IStyleable target, |
||||
|
StyledPropertyBase<T> property, |
||||
|
Func<T> valueFactory) |
||||
|
{ |
||||
|
_target = target; |
||||
|
_styledProperty = property; |
||||
|
_valueFactory = valueFactory; |
||||
|
} |
||||
|
|
||||
|
public PropertySetterLazyInstance( |
||||
|
IStyleable target, |
||||
|
DirectPropertyBase<T> property, |
||||
|
Func<T> valueFactory) |
||||
|
{ |
||||
|
_target = target; |
||||
|
_directProperty = property; |
||||
|
_valueFactory = valueFactory; |
||||
|
} |
||||
|
|
||||
|
public void Start(bool hasActivator) |
||||
|
{ |
||||
|
_isActive = !hasActivator; |
||||
|
|
||||
|
if (_styledProperty is object) |
||||
|
{ |
||||
|
var priority = hasActivator ? BindingPriority.StyleTrigger : BindingPriority.Style; |
||||
|
_subscription = _target.Bind(_styledProperty, this, priority); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_subscription = _target.Bind(_directProperty, this); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Activate() |
||||
|
{ |
||||
|
if (!_isActive) |
||||
|
{ |
||||
|
_isActive = true; |
||||
|
PublishNext(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public void Deactivate() |
||||
|
{ |
||||
|
if (_isActive) |
||||
|
{ |
||||
|
_isActive = false; |
||||
|
PublishNext(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
if (_subscription is object) |
||||
|
{ |
||||
|
var sub = _subscription; |
||||
|
_subscription = null; |
||||
|
sub.Dispose(); |
||||
|
} |
||||
|
else if (_isActive) |
||||
|
{ |
||||
|
if (_styledProperty is object) |
||||
|
{ |
||||
|
_target.ClearValue(_styledProperty); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
_target.ClearValue(_directProperty); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
base.Dispose(); |
||||
|
} |
||||
|
|
||||
|
protected override void Subscribed() => PublishNext(); |
||||
|
protected override void Unsubscribed() { } |
||||
|
|
||||
|
private T GetValue() |
||||
|
{ |
||||
|
if (_value.HasValue) |
||||
|
{ |
||||
|
return _value.Value; |
||||
|
} |
||||
|
|
||||
|
_value = _valueFactory(); |
||||
|
return _value.Value; |
||||
|
} |
||||
|
|
||||
|
private void PublishNext() |
||||
|
{ |
||||
|
if (_isActive) |
||||
|
{ |
||||
|
GetValue(); |
||||
|
PublishNext(_value); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PublishNext(default); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,494 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reactive; |
||||
|
using System.Reactive.Disposables; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Text; |
||||
|
using Avalonia.Data; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests |
||||
|
{ |
||||
|
public class AvaloniaObjectTests_BatchUpdate |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void SetValue_Should_Not_Raise_Property_Changes_During_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<string>(); |
||||
|
|
||||
|
target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); |
||||
|
target.BeginBatchUpdate(); |
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
|
||||
|
Assert.Empty(raised); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Binding_Should_Not_Raise_Property_Changes_During_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("foo"); |
||||
|
var raised = new List<string>(); |
||||
|
|
||||
|
target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); |
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
|
||||
|
Assert.Empty(raised); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Binding_Completion_Should_Not_Raise_Property_Changes_During_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("foo"); |
||||
|
var raised = new List<string>(); |
||||
|
|
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
target.GetObservable(TestClass.FooProperty).Skip(1).Subscribe(x => raised.Add(x)); |
||||
|
target.BeginBatchUpdate(); |
||||
|
observable.OnCompleted(); |
||||
|
|
||||
|
Assert.Empty(raised); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_Change_Should_Be_Raised_After_Batch_Update_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Equal("foo", target.Foo); |
||||
|
Assert.Null(raised[0].OldValue); |
||||
|
Assert.Equal("foo", raised[0].NewValue); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_Change_Should_Be_Raised_After_Batch_Update_2() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.SetValue(TestClass.FooProperty, "bar", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.BarProperty, raised[0].Property); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[1].Property); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("baz"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.BarProperty, raised[0].Property); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[1].Property); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_2() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("foo"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.BarProperty, raised[0].Property); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[1].Property); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void SetValue_And_Binding_Changes_Should_Be_Raised_In_Correct_Order_After_Batch_Update_3() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("qux"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.BarProperty, "bar", BindingPriority.LocalValue); |
||||
|
target.SetValue(TestClass.FooProperty, "baz", BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.BarProperty, raised[0].Property); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[1].Property); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Binding_Change_Should_Be_Raised_After_Batch_Update_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("foo"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Equal("foo", target.Foo); |
||||
|
Assert.Null(raised[0].OldValue); |
||||
|
Assert.Equal("foo", raised[0].NewValue); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Binding_Change_Should_Be_Raised_After_Batch_Update_2() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("bar"); |
||||
|
var observable2 = new TestObservable<string>("baz"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.SetValue(TestClass.FooProperty, "foo", BindingPriority.LocalValue); |
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Equal("baz", target.Foo); |
||||
|
Assert.Equal("foo", raised[0].OldValue); |
||||
|
Assert.Equal("baz", raised[0].NewValue); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Binding_Completion_Should_Be_Raised_After_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable = new TestObservable<string>("foo"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.Bind(TestClass.FooProperty, observable, BindingPriority.LocalValue); |
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
observable.OnCompleted(); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Null(target.Foo); |
||||
|
Assert.Equal("foo", raised[0].OldValue); |
||||
|
Assert.Null(raised[0].NewValue); |
||||
|
Assert.Equal(BindingPriority.Unset, raised[0].Priority); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void ClearValue_Change_Should_Be_Raised_After_Batch_Update_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.Foo = "foo"; |
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.ClearValue(TestClass.FooProperty); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, raised.Count); |
||||
|
Assert.Null(target.Foo); |
||||
|
Assert.Equal("foo", raised[0].OldValue); |
||||
|
Assert.Null(raised[0].NewValue); |
||||
|
Assert.Equal(BindingPriority.Unset, raised[0].Priority); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Bindings_Should_Be_Subscribed_Before_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
|
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); |
||||
|
|
||||
|
Assert.Equal(1, observable1.SubscribeCount); |
||||
|
Assert.Equal(1, observable2.SubscribeCount); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Non_Active_Binding_Should_Not_Be_Subscribed_Before_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
|
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); |
||||
|
|
||||
|
Assert.Equal(1, observable1.SubscribeCount); |
||||
|
Assert.Equal(0, observable2.SubscribeCount); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void LocalValue_Bindings_Should_Be_Subscribed_During_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
// We need to subscribe to LocalValue bindings even if we've got a batch operation
|
||||
|
// in progress because otherwise we don't know whether the binding or a subsequent
|
||||
|
// SetValue with local priority will win. Notifications however shouldn't be sent.
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.LocalValue); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.LocalValue); |
||||
|
|
||||
|
Assert.Equal(1, observable1.SubscribeCount); |
||||
|
Assert.Equal(1, observable2.SubscribeCount); |
||||
|
Assert.Empty(raised); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Style_Bindings_Should_Not_Be_Subscribed_During_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.StyleTrigger); |
||||
|
|
||||
|
Assert.Equal(0, observable1.SubscribeCount); |
||||
|
Assert.Equal(0, observable2.SubscribeCount); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.Style); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(0, observable1.SubscribeCount); |
||||
|
Assert.Equal(1, observable2.SubscribeCount); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Active_Style_Binding_Should_Be_Subscribed_After_Batch_Uppdate_2() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var observable1 = new TestObservable<string>("foo"); |
||||
|
var observable2 = new TestObservable<string>("bar"); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Bind(TestClass.FooProperty, observable1, BindingPriority.StyleTrigger); |
||||
|
target.Bind(TestClass.FooProperty, observable2, BindingPriority.Style); |
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal(1, observable1.SubscribeCount); |
||||
|
Assert.Equal(0, observable2.SubscribeCount); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Change_Can_Be_Triggered_By_Ending_Batch_Update_1() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Foo = "foo"; |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => |
||||
|
{ |
||||
|
if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") |
||||
|
target.Bar = "bar"; |
||||
|
}; |
||||
|
|
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal("foo", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[0].Property); |
||||
|
Assert.Equal(TestClass.BarProperty, raised[1].Property); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Change_Can_Be_Triggered_By_Ending_Batch_Update_2() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Foo = "foo"; |
||||
|
target.Bar = "baz"; |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => |
||||
|
{ |
||||
|
if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") |
||||
|
target.Bar = "bar"; |
||||
|
}; |
||||
|
|
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Equal("foo", target.Foo); |
||||
|
Assert.Equal("bar", target.Bar); |
||||
|
Assert.Equal(2, raised.Count); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Batch_Update_Can_Be_Triggered_By_Ending_Batch_Update() |
||||
|
{ |
||||
|
var target = new TestClass(); |
||||
|
var raised = new List<AvaloniaPropertyChangedEventArgs>(); |
||||
|
|
||||
|
target.PropertyChanged += (s, e) => raised.Add(e); |
||||
|
|
||||
|
target.BeginBatchUpdate(); |
||||
|
target.Foo = "foo"; |
||||
|
target.Bar = "baz"; |
||||
|
|
||||
|
// Simulates the following scenario:
|
||||
|
// - A control is added to the logical tree
|
||||
|
// - A batch update is started to apply styles
|
||||
|
// - Ending the batch update triggers something which removes the control from the logical tree
|
||||
|
// - A new batch update is started to detach styles
|
||||
|
target.PropertyChanged += (s, e) => |
||||
|
{ |
||||
|
if (e.Property == TestClass.FooProperty && (string)e.NewValue == "foo") |
||||
|
{ |
||||
|
target.BeginBatchUpdate(); |
||||
|
target.ClearValue(TestClass.FooProperty); |
||||
|
target.ClearValue(TestClass.BarProperty); |
||||
|
target.EndBatchUpdate(); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
target.EndBatchUpdate(); |
||||
|
|
||||
|
Assert.Null(target.Foo); |
||||
|
Assert.Null(target.Bar); |
||||
|
Assert.Equal(2, raised.Count); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[0].Property); |
||||
|
Assert.Null(raised[0].OldValue); |
||||
|
Assert.Equal("foo", raised[0].NewValue); |
||||
|
Assert.Equal(TestClass.FooProperty, raised[1].Property); |
||||
|
Assert.Equal("foo", raised[1].OldValue); |
||||
|
Assert.Null(raised[1].NewValue); |
||||
|
} |
||||
|
|
||||
|
public class TestClass : AvaloniaObject |
||||
|
{ |
||||
|
public static readonly StyledProperty<string> FooProperty = |
||||
|
AvaloniaProperty.Register<TestClass, string>(nameof(Foo)); |
||||
|
|
||||
|
public static readonly StyledProperty<string> BarProperty = |
||||
|
AvaloniaProperty.Register<TestClass, string>(nameof(Bar)); |
||||
|
|
||||
|
public string Foo |
||||
|
{ |
||||
|
get => GetValue(FooProperty); |
||||
|
set => SetValue(FooProperty, value); |
||||
|
} |
||||
|
|
||||
|
public string Bar |
||||
|
{ |
||||
|
get => GetValue(BarProperty); |
||||
|
set => SetValue(BarProperty, value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public class TestObservable<T> : ObservableBase<BindingValue<T>> |
||||
|
{ |
||||
|
private readonly T _value; |
||||
|
private IObserver<BindingValue<T>> _observer; |
||||
|
|
||||
|
public TestObservable(T value) => _value = value; |
||||
|
|
||||
|
public int SubscribeCount { get; private set; } |
||||
|
|
||||
|
public void OnCompleted() => _observer.OnCompleted(); |
||||
|
public void OnError(Exception e) => _observer.OnError(e); |
||||
|
|
||||
|
protected override IDisposable SubscribeCore(IObserver<BindingValue<T>> observer) |
||||
|
{ |
||||
|
++SubscribeCount; |
||||
|
_observer = observer; |
||||
|
observer.OnNext(_value); |
||||
|
return Disposable.Empty; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,74 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Markup.Xaml; |
||||
|
using Avalonia.Markup.Xaml.Styling; |
||||
|
using Avalonia.Platform; |
||||
|
using Avalonia.Shared.PlatformSupport; |
||||
|
using Avalonia.Styling; |
||||
|
using Avalonia.UnitTests; |
||||
|
using BenchmarkDotNet.Attributes; |
||||
|
using Moq; |
||||
|
|
||||
|
namespace Avalonia.Benchmarks.Themes |
||||
|
{ |
||||
|
[MemoryDiagnoser] |
||||
|
public class FluentBenchmark |
||||
|
{ |
||||
|
private readonly IDisposable _app; |
||||
|
private readonly TestRoot _root; |
||||
|
|
||||
|
public FluentBenchmark() |
||||
|
{ |
||||
|
_app = CreateApp(); |
||||
|
_root = new TestRoot(true, null) |
||||
|
{ |
||||
|
Renderer = new NullRenderer() |
||||
|
}; |
||||
|
|
||||
|
_root.LayoutManager.ExecuteInitialLayoutPass(); |
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_app.Dispose(); |
||||
|
} |
||||
|
|
||||
|
[Benchmark] |
||||
|
public void RepeatButton() |
||||
|
{ |
||||
|
var button = new RepeatButton(); |
||||
|
_root.Child = button; |
||||
|
_root.LayoutManager.ExecuteLayoutPass(); |
||||
|
} |
||||
|
|
||||
|
private static IDisposable CreateApp() |
||||
|
{ |
||||
|
var services = new TestServices( |
||||
|
assetLoader: new AssetLoader(), |
||||
|
globalClock: new MockGlobalClock(), |
||||
|
platform: new AppBuilder().RuntimePlatform, |
||||
|
renderInterface: new MockPlatformRenderInterface(), |
||||
|
standardCursorFactory: Mock.Of<ICursorFactory>(), |
||||
|
styler: new Styler(), |
||||
|
theme: () => LoadFluentTheme(), |
||||
|
threadingInterface: new NullThreadingPlatform(), |
||||
|
fontManagerImpl: new MockFontManagerImpl(), |
||||
|
textShaperImpl: new MockTextShaperImpl(), |
||||
|
windowingPlatform: new MockWindowingPlatform()); |
||||
|
|
||||
|
return UnitTestApplication.Start(services); |
||||
|
} |
||||
|
|
||||
|
private static Styles LoadFluentTheme() |
||||
|
{ |
||||
|
AssetLoader.RegisterResUriParsers(); |
||||
|
return new Styles |
||||
|
{ |
||||
|
new Avalonia.Themes.Fluent.FluentTheme(new Uri("avares://Avalonia.Benchmarks")) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue