diff --git a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
index a8c3abe8f2..04ab17c4e1 100644
--- a/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Direct2D1.RenderTests.v3.ncrunchproject
@@ -1,7 +1,6 @@
- 1000
- True
+ 3000
True
\ No newline at end of file
diff --git a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
index 7973ad72e5..ad2cec2ae3 100644
--- a/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
+++ b/src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
@@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication
{
Margin = new Thickness(30),
Background = Brushes.Yellow,
- Children = new Avalonia.Controls.Controls
+ Children =
{
new TextBlock
{
diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index efcbb57244..34278c397f 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -51,6 +51,21 @@ namespace Avalonia
///
private EventHandler _propertyChanged;
+ private DeferredSetter _directDeferredSetter;
+
+ ///
+ /// Delayed setter helper for direct properties. Used to fix #855.
+ ///
+ private DeferredSetter DirectPropertyDeferredSetter
+ {
+ get
+ {
+ return _directDeferredSetter ??
+ (_directDeferredSetter = new DeferredSetter());
+ }
+ }
+
+
///
/// Initializes a new instance of the class.
///
@@ -225,6 +240,19 @@ namespace Avalonia
return (T)GetValue((AvaloniaProperty)property);
}
+ ///
+ /// Checks whether a is animating.
+ ///
+ /// The property.
+ /// True if the property is animating, otherwise false.
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ Contract.Requires(property != null);
+ VerifyAccess();
+
+ return _values.TryGetValue(property, out PriorityValue value) ? value.IsAnimating : false;
+ }
+
///
/// Checks whether a is set on this object.
///
@@ -539,6 +567,45 @@ namespace Avalonia
}
}
+ ///
+ /// A callback type for encapsulating complex logic for setting direct properties.
+ ///
+ /// The type of the property.
+ /// The value to which to set the property.
+ /// The backing field for the property.
+ /// A wrapper for the property-changed notification.
+ protected delegate void SetAndRaiseCallback(T value, ref T field, Action notifyWrapper);
+
+ ///
+ /// Sets the backing field for a direct avalonia property, raising the
+ /// event if the value has changed.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The backing field.
+ /// A callback called to actually set the value to the backing field.
+ /// The value.
+ ///
+ /// True if the value changed, otherwise false.
+ ///
+ protected bool SetAndRaise(
+ AvaloniaProperty property,
+ ref T field,
+ SetAndRaiseCallback setterCallback,
+ T value)
+ {
+ Contract.Requires(setterCallback != null);
+ return DirectPropertyDeferredSetter.SetAndNotify(
+ property,
+ ref field,
+ (object val, ref T backing, Action notify) =>
+ {
+ setterCallback((T)val, ref backing, notify);
+ return true;
+ },
+ value);
+ }
+
///
/// Sets the backing field for a direct avalonia property, raising the
/// event if the value has changed.
@@ -553,17 +620,32 @@ namespace Avalonia
protected bool SetAndRaise(AvaloniaProperty property, ref T field, T value)
{
VerifyAccess();
- if (!object.Equals(field, value))
- {
- var old = field;
- field = value;
- RaisePropertyChanged(property, old, value, BindingPriority.LocalValue);
- return true;
- }
- else
- {
- return false;
- }
+ return SetAndRaise(
+ property,
+ ref field,
+ (T val, ref T backing, Action notifyWrapper)
+ => SetAndRaiseCore(property, ref backing, val, notifyWrapper),
+ value);
+ }
+
+ ///
+ /// Default assignment logic for SetAndRaise.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// The backing field.
+ /// The value.
+ /// A wrapper for the property-changed notification.
+ ///
+ /// True if the value changed, otherwise false.
+ ///
+ private bool SetAndRaiseCore(AvaloniaProperty property, ref T field, T value, Action notifyWrapper)
+ {
+ var old = field;
+ field = value;
+
+ notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
+ return true;
}
///
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index e96679e643..1da2ecb942 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -138,17 +138,9 @@ namespace Avalonia
AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
- // TODO: Subject.Create is not yet in stable Rx : once it is, remove the
- // AnonymousSubject classes and use Subject.Create.
- var output = new Subject();
- var result = new AnonymousSubject(
- Observer.Create(
- x => output.OnNext(x),
- e => output.OnError(e),
- () => output.OnCompleted()),
+ return Subject.Create(
+ Observer.Create(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
- o.Bind(property, output, priority);
- return result;
}
///
@@ -169,17 +161,9 @@ namespace Avalonia
AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue)
{
- // TODO: Subject.Create is not yet in stable Rx : once it is, remove the
- // AnonymousSubject classes from this file and use Subject.Create.
- var output = new Subject();
- var result = new AnonymousSubject(
- Observer.Create(
- x => output.OnNext(x),
- e => output.OnError(e),
- () => output.OnCompleted()),
+ return Subject.Create(
+ Observer.Create(x => o.SetValue(property, x, priority)),
o.GetObservable(property));
- o.Bind(property, output, priority);
- return result;
}
///
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index a3c3015a38..41c2ad6e54 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -350,14 +350,15 @@ namespace Avalonia.Collections
public void MoveRange(int oldIndex, int count, int newIndex)
{
var items = _inner.GetRange(oldIndex, count);
+ var modifiedNewIndex = newIndex;
_inner.RemoveRange(oldIndex, count);
if (newIndex > oldIndex)
{
- newIndex -= count;
+ modifiedNewIndex -= count;
}
- _inner.InsertRange(newIndex, items);
+ _inner.InsertRange(modifiedNewIndex, items);
if (_collectionChanged != null)
{
diff --git a/src/Avalonia.Base/Collections/IAvaloniaList.cs b/src/Avalonia.Base/Collections/IAvaloniaList.cs
index 0233cee7a9..48c36976a5 100644
--- a/src/Avalonia.Base/Collections/IAvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/IAvaloniaList.cs
@@ -36,6 +36,21 @@ namespace Avalonia.Collections
/// The items.
void InsertRange(int index, IEnumerable items);
+ ///
+ /// Moves an item to a new index.
+ ///
+ /// The index of the item to move.
+ /// The index to move the item to.
+ void Move(int oldIndex, int newIndex);
+
+ ///
+ /// Moves multiple items to a new index.
+ ///
+ /// The first index of the items to move.
+ /// The number of items to move.
+ /// The index to move the items to.
+ void MoveRange(int oldIndex, int count, int newIndex);
+
///
/// Removes multiple items from the collection.
///
diff --git a/src/Avalonia.Base/IAvaloniaObject.cs b/src/Avalonia.Base/IAvaloniaObject.cs
index c11bab2236..c11f8ada7e 100644
--- a/src/Avalonia.Base/IAvaloniaObject.cs
+++ b/src/Avalonia.Base/IAvaloniaObject.cs
@@ -31,6 +31,13 @@ namespace Avalonia
/// The value.
T GetValue(AvaloniaProperty property);
+ ///
+ /// Checks whether a is animating.
+ ///
+ /// The property.
+ /// True if the property is animating, otherwise false.
+ bool IsAnimating(AvaloniaProperty property);
+
///
/// Checks whether a is set on this object.
///
diff --git a/src/Avalonia.Base/PriorityValue.cs b/src/Avalonia.Base/PriorityValue.cs
index 3726fb7ae5..9b5318083a 100644
--- a/src/Avalonia.Base/PriorityValue.cs
+++ b/src/Avalonia.Base/PriorityValue.cs
@@ -28,8 +28,10 @@ namespace Avalonia
{
private readonly Type _valueType;
private readonly SingleOrDictionary _levels = new SingleOrDictionary();
- private object _value;
+
private readonly Func _validate;
+ private static readonly DeferredSetter delayedSetter = new DeferredSetter();
+ private (object value, int priority) _value;
///
/// Initializes a new instance of the class.
@@ -47,11 +49,22 @@ namespace Avalonia
Owner = owner;
Property = property;
_valueType = valueType;
- _value = AvaloniaProperty.UnsetValue;
- ValuePriority = int.MaxValue;
+ _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate;
}
+ ///
+ /// Gets a value indicating whether the property is animating.
+ ///
+ public bool IsAnimating
+ {
+ get
+ {
+ return ValuePriority <= (int)BindingPriority.Animation &&
+ GetLevel(ValuePriority).ActiveBindingIndex != -1;
+ }
+ }
+
///
/// Gets the owner of the value.
///
@@ -65,16 +78,12 @@ namespace Avalonia
///
/// Gets the current value.
///
- public object Value => _value;
+ public object Value => _value.value;
///
/// Gets the priority of the binding that is currently active.
///
- public int ValuePriority
- {
- get;
- private set;
- }
+ public int ValuePriority => _value.priority;
///
/// Adds a new binding.
@@ -234,25 +243,36 @@ namespace Avalonia
/// The priority level that the value came from.
private void UpdateValue(object value, int priority)
{
- var notification = value as BindingNotification;
+ delayedSetter.SetAndNotify(this,
+ ref _value,
+ UpdateCore,
+ (value, priority));
+ }
+
+ private bool UpdateCore(
+ (object value, int priority) update,
+ ref (object value, int priority) backing,
+ Action notify)
+ {
+ var val = update.value;
+ var notification = val as BindingNotification;
object castValue;
if (notification != null)
{
- value = (notification.HasValue) ? notification.Value : null;
+ val = (notification.HasValue) ? notification.Value : null;
}
- if (TypeUtilities.TryConvertImplicit(_valueType, value, out castValue))
+ if (TypeUtilities.TryConvertImplicit(_valueType, val, out castValue))
{
- var old = _value;
+ var old = backing.value;
if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{
castValue = _validate(castValue);
}
- ValuePriority = priority;
- _value = castValue;
+ backing = (castValue, update.priority);
if (notification?.HasValue == true)
{
@@ -261,7 +281,7 @@ namespace Avalonia
if (notification == null || notification.HasValue)
{
- Owner?.Changed(this, old, _value);
+ notify(() => Owner?.Changed(this, old, Value));
}
if (notification != null)
@@ -272,14 +292,15 @@ namespace Avalonia
else
{
Logger.Error(
- LogArea.Binding,
+ LogArea.Binding,
Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
- Property.Name,
- _valueType,
- value,
- value?.GetType());
+ Property.Name,
+ _valueType,
+ val,
+ val?.GetType());
}
+ return true;
}
}
}
diff --git a/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs b/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
deleted file mode 100644
index c7bed15ecb..0000000000
--- a/src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Avalonia.Reactive
-{
- public class AnonymousSubject : AnonymousSubject, ISubject
- {
- public AnonymousSubject(IObserver observer, IObservable observable)
- : base(observer, observable)
- {
- }
- }
-}
diff --git a/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs b/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
deleted file mode 100644
index 18551d564e..0000000000
--- a/src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
+++ /dev/null
@@ -1,49 +0,0 @@
-// Copyright (c) The Avalonia Project. All rights reserved.
-// Licensed under the MIT license. See licence.md file in the project root for full license information.
-
-using System;
-using System.Reactive.Subjects;
-
-namespace Avalonia.Reactive
-{
- public class AnonymousSubject : ISubject
- {
- private readonly IObserver _observer;
- private readonly IObservable _observable;
-
- public AnonymousSubject(IObserver observer, IObservable observable)
- {
- _observer = observer;
- _observable = observable;
- }
-
- public void OnCompleted()
- {
- _observer.OnCompleted();
- }
-
- public void OnError(Exception error)
- {
- if (error == null)
- throw new ArgumentNullException("error");
-
- _observer.OnError(error);
- }
-
- public void OnNext(T value)
- {
- _observer.OnNext(value);
- }
-
- public IDisposable Subscribe(IObserver observer)
- {
- if (observer == null)
- throw new ArgumentNullException("observer");
-
- //
- // [OK] Use of unsafe Subscribe: non-pretentious wrapping of an observable sequence.
- //
- return _observable.Subscribe/*Unsafe*/(observer);
- }
- }
-}
diff --git a/src/Avalonia.Base/Utilities/DeferredSetter.cs b/src/Avalonia.Base/Utilities/DeferredSetter.cs
new file mode 100644
index 0000000000..fdfa160134
--- /dev/null
+++ b/src/Avalonia.Base/Utilities/DeferredSetter.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Reactive.Disposables;
+using System.Runtime.CompilerServices;
+using System.Text;
+
+namespace Avalonia.Utilities
+{
+ ///
+ /// A utility class to enable deferring assignment until after property-changed notifications are sent.
+ ///
+ /// The type of the object that represents the property.
+ /// The type of value with which to track the delayed assignment.
+ class DeferredSetter
+ where TProperty: class
+ {
+ private struct NotifyDisposable : IDisposable
+ {
+ private readonly SettingStatus status;
+
+ internal NotifyDisposable(SettingStatus status)
+ {
+ this.status = status;
+ status.Notifying = true;
+ }
+
+ public void Dispose()
+ {
+ status.Notifying = false;
+ }
+ }
+
+ ///
+ /// Information on current setting/notification status of a property.
+ ///
+ private class SettingStatus
+ {
+ public bool Notifying { get; set; }
+
+ private Queue pendingValues;
+
+ public Queue PendingValues
+ {
+ get
+ {
+ return pendingValues ?? (pendingValues = new Queue());
+ }
+ }
+ }
+
+ private readonly ConditionalWeakTable setRecords = new ConditionalWeakTable();
+
+ ///
+ /// Mark the property as currently notifying.
+ ///
+ /// The property to mark as notifying.
+ /// Returns a disposable that when disposed, marks the property as done notifying.
+ private NotifyDisposable MarkNotifying(TProperty property)
+ {
+ Contract.Requires(!IsNotifying(property));
+
+ return new NotifyDisposable(setRecords.GetOrCreateValue(property));
+ }
+
+ ///
+ /// Check if the property is currently notifying listeners.
+ ///
+ /// The property.
+ /// If the property is currently notifying listeners.
+ private bool IsNotifying(TProperty property)
+ => setRecords.TryGetValue(property, out var value) && value.Notifying;
+
+ ///
+ /// Add a pending assignment for the property.
+ ///
+ /// The property.
+ /// The value to assign.
+ private void AddPendingSet(TProperty property, TSetRecord value)
+ {
+ Contract.Requires(IsNotifying(property));
+
+ setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
+ }
+
+ ///
+ /// Checks if there are any pending assignments for the property.
+ ///
+ /// The property to check.
+ /// If the property has any pending assignments.
+ private bool HasPendingSet(TProperty property)
+ {
+ return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
+ }
+
+ ///
+ /// Gets the first pending assignment for the property.
+ ///
+ /// The property to check.
+ /// The first pending assignment for the property.
+ private TSetRecord GetFirstPendingSet(TProperty property)
+ {
+ return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
+ }
+
+ public delegate bool SetterDelegate(TSetRecord record, ref TValue backing, Action notifyCallback);
+
+ ///
+ /// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
+ ///
+ /// The property to set.
+ /// The backing field for the property
+ ///
+ /// A callback that actually sets the property.
+ /// The first parameter is the value to set, and the second is a wrapper that takes a callback that sends the property-changed notification.
+ ///
+ /// The value to try to set.
+ public bool SetAndNotify(
+ TProperty property,
+ ref TValue backing,
+ SetterDelegate setterCallback,
+ TSetRecord value)
+ {
+ Contract.Requires(setterCallback != null);
+ if (!IsNotifying(property))
+ {
+ bool updated = false;
+ if (!object.Equals(value, backing))
+ {
+ updated = setterCallback(value, ref backing, notification =>
+ {
+ using (MarkNotifying(property))
+ {
+ notification();
+ }
+ });
+ }
+ while (HasPendingSet(property))
+ {
+ updated |= setterCallback(GetFirstPendingSet(property), ref backing, notification =>
+ {
+ using (MarkNotifying(property))
+ {
+ notification();
+ }
+ });
+ }
+ return updated;
+ }
+ else if(!object.Equals(value, backing))
+ {
+ AddPendingSet(property, value);
+ }
+ return false;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 8c79e5dce5..59281c5ad0 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -549,7 +549,7 @@ namespace Avalonia.Controls
}
else
{
- if (addedDate.HasValue && !(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
+ if (!(SelectedDates.Count > 0 && SelectedDates[0] == addedDate.Value))
{
foreach (DateTime item in SelectedDates)
{
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index d4777b2f8a..6e1e1a05f1 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -621,7 +621,6 @@ namespace Avalonia.Controls
Contract.Requires(property != null);
Contract.Requires(selector != null);
Contract.Requires(className != null);
- Contract.Requires(property != null);
if (string.IsNullOrWhiteSpace(className))
{
diff --git a/src/Avalonia.Controls/DropDown.cs b/src/Avalonia.Controls/DropDown.cs
index fa2e0c1e16..6b27c479ba 100644
--- a/src/Avalonia.Controls/DropDown.cs
+++ b/src/Avalonia.Controls/DropDown.cs
@@ -96,6 +96,16 @@ namespace Avalonia.Controls
this.UpdateSelectionBoxItem(this.SelectedItem);
}
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+
+ if (!e.Handled && e.NavigationMethod == NavigationMethod.Directional)
+ {
+ e.Handled = UpdateSelectionFromEventSource(e.Source);
+ }
+ }
+
///
protected override void OnKeyDown(KeyEventArgs e)
{
@@ -104,7 +114,7 @@ namespace Avalonia.Controls
if (!e.Handled)
{
if (e.Key == Key.F4 ||
- (e.Key == Key.Down && ((e.Modifiers & InputModifiers.Alt) != 0)))
+ ((e.Key == Key.Down || e.Key == Key.Up) && ((e.Modifiers & InputModifiers.Alt) != 0)))
{
IsDropDownOpen = !IsDropDownOpen;
e.Handled = true;
@@ -114,6 +124,27 @@ namespace Avalonia.Controls
IsDropDownOpen = false;
e.Handled = true;
}
+
+ if (!IsDropDownOpen)
+ {
+ if (e.Key == Key.Down)
+ {
+ if (SelectedIndex == -1)
+ SelectedIndex = 0;
+
+ if (++SelectedIndex >= ItemCount)
+ SelectedIndex = 0;
+
+ e.Handled = true;
+ }
+ else if (e.Key == Key.Up)
+ {
+ if (--SelectedIndex < 0)
+ SelectedIndex = ItemCount - 1;
+
+ e.Handled = true;
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls/IPanel.cs b/src/Avalonia.Controls/IPanel.cs
index 5bcfd33e86..ce2c63ffc8 100644
--- a/src/Avalonia.Controls/IPanel.cs
+++ b/src/Avalonia.Controls/IPanel.cs
@@ -9,8 +9,8 @@ namespace Avalonia.Controls
public interface IPanel : IControl
{
///
- /// Gets or sets the children of the .
+ /// Gets the children of the .
///
- Controls Children { get; set; }
+ Controls Children { get; }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/ItemsControl.cs b/src/Avalonia.Controls/ItemsControl.cs
index aa209e0462..4366de1cd6 100644
--- a/src/Avalonia.Controls/ItemsControl.cs
+++ b/src/Avalonia.Controls/ItemsControl.cs
@@ -11,6 +11,7 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
+using Avalonia.Input;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
@@ -106,6 +107,12 @@ namespace Avalonia.Controls
set { SetAndRaise(ItemsProperty, ref _items, value); }
}
+ public int ItemCount
+ {
+ get;
+ private set;
+ }
+
///
/// Gets or sets the panel used to display the items.
///
@@ -352,6 +359,10 @@ namespace Avalonia.Controls
RemoveControlItemsFromLogicalChildren(e.OldItems);
break;
}
+
+ int? count = (Items as IList)?.Count;
+ if (count != null)
+ ItemCount = (int)count;
var collection = sender as ICollection;
PseudoClasses.Set(":empty", collection == null || collection.Count == 0);
diff --git a/src/Avalonia.Controls/Panel.cs b/src/Avalonia.Controls/Panel.cs
index 3272d3779b..a2cb013300 100644
--- a/src/Avalonia.Controls/Panel.cs
+++ b/src/Avalonia.Controls/Panel.cs
@@ -25,8 +25,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty BackgroundProperty =
Border.BackgroundProperty.AddOwner();
- private readonly Controls _children = new Controls();
-
///
/// Initializes static members of the class.
///
@@ -40,38 +38,14 @@ namespace Avalonia.Controls
///
public Panel()
{
- _children.CollectionChanged += ChildrenChanged;
+ Children.CollectionChanged += ChildrenChanged;
}
///
- /// Gets or sets the children of the .
+ /// Gets the children of the .
///
- ///
- /// Even though this property can be set, the setter is only intended for use in object
- /// initializers. Assigning to this property does not change the underlying collection,
- /// it simply clears the existing collection and adds the contents of the assigned
- /// collection.
- ///
[Content]
- public Controls Children
- {
- get
- {
- return _children;
- }
-
- set
- {
- Contract.Requires(value != null);
-
- if (_children != value)
- {
- VisualChildren.Clear();
- _children.Clear();
- _children.AddRange(value);
- }
- }
- }
+ public Controls Children { get; } = new Controls();
///
/// Gets or Sets Panel background brush.
@@ -115,6 +89,11 @@ namespace Avalonia.Controls
VisualChildren.AddRange(e.NewItems.OfType());
break;
+ case NotifyCollectionChangedAction.Move:
+ LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex);
+ break;
+
case NotifyCollectionChangedAction.Remove:
controls = e.OldItems.OfType().ToList();
LogicalChildren.RemoveAll(controls);
@@ -132,11 +111,7 @@ namespace Avalonia.Controls
break;
case NotifyCollectionChangedAction.Reset:
- controls = e.OldItems.OfType().ToList();
- LogicalChildren.Clear();
- VisualChildren.Clear();
- VisualChildren.AddRange(_children);
- break;
+ throw new NotSupportedException();
}
InvalidateMeasure();
diff --git a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
index ab09a4701d..2e668fda95 100644
--- a/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
+++ b/src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
@@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
- var old = SelectedIndex;
- var effective = (value >= 0 && value < Items?.Cast().Count()) ? value : -1;
-
- if (old != effective)
+ SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action notifyWrapper) =>
{
- _selectedIndex = effective;
- RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue);
- SelectedItem = ElementAt(Items, effective);
- }
+ var old = backing;
+ var effective = (val >= 0 && val < Items?.Cast().Count()) ? val : -1;
+
+ if (old != effective)
+ {
+ backing = effective;
+ notifyWrapper(() =>
+ RaisePropertyChanged(
+ SelectedIndexProperty,
+ old,
+ effective,
+ BindingPriority.LocalValue));
+ SelectedItem = ElementAt(Items, effective);
+ }
+ }, value);
}
else
{
@@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives
{
if (_updateCount == 0)
{
- var old = SelectedItem;
- var index = IndexOf(Items, value);
- var effective = index != -1 ? value : null;
-
- if (!object.Equals(effective, old))
+ SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action notifyWrapper) =>
{
- _selectedItem = effective;
- RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue);
- SelectedIndex = index;
+ var old = backing;
+ var index = IndexOf(Items, val);
+ var effective = index != -1 ? val : null;
- if (effective != null)
+ if (!object.Equals(effective, old))
{
- if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+ backing = effective;
+
+ notifyWrapper(() =>
+ RaisePropertyChanged(
+ SelectedItemProperty,
+ old,
+ effective,
+ BindingPriority.LocalValue));
+
+ SelectedIndex = index;
+
+ if (effective != null)
+ {
+ if (SelectedItems.Count != 1 || SelectedItems[0] != effective)
+ {
+ _syncingSelectedItems = true;
+ SelectedItems.Clear();
+ SelectedItems.Add(effective);
+ _syncingSelectedItems = false;
+ }
+ }
+ else if (SelectedItems.Count > 0)
{
- _syncingSelectedItems = true;
SelectedItems.Clear();
- SelectedItems.Add(effective);
- _syncingSelectedItems = false;
}
}
- else if (SelectedItems.Count > 0)
- {
- SelectedItems.Clear();
- }
- }
+ }, value);
}
else
{
@@ -297,7 +315,7 @@ namespace Avalonia.Controls.Primitives
.OfType()
.FirstOrDefault(x => x.LogicalParent == this && ItemContainerGenerator?.IndexFromContainer(x) != -1);
- return item as IControl;
+ return item;
}
///
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index 079e571d29..fa3ecdedef 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -176,10 +176,7 @@ namespace Avalonia.Controls
SelectedItem = item;
- if (SelectedItem != null)
- {
- MarkContainerSelected(container, true);
- }
+ MarkContainerSelected(container, true);
}
}
diff --git a/src/Avalonia.Controls/VirtualizingStackPanel.cs b/src/Avalonia.Controls/VirtualizingStackPanel.cs
index 834f6d218b..409dd231ad 100644
--- a/src/Avalonia.Controls/VirtualizingStackPanel.cs
+++ b/src/Avalonia.Controls/VirtualizingStackPanel.cs
@@ -134,12 +134,14 @@ namespace Avalonia.Controls
protected override IInputElement GetControlInDirection(NavigationDirection direction, IControl from)
{
+ if (from == null)
+ return null;
+
var logicalScrollable = Parent as ILogicalScrollable;
- var fromControl = from as IControl;
- if (logicalScrollable?.IsLogicalScrollEnabled == true && fromControl != null)
+ if (logicalScrollable?.IsLogicalScrollEnabled == true)
{
- return logicalScrollable.GetControlInDirection(direction, fromControl);
+ return logicalScrollable.GetControlInDirection(direction, from);
}
else
{
diff --git a/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs b/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
index 7e4e5a8564..d445f1cd70 100644
--- a/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
+++ b/src/Avalonia.Diagnostics/Views/TreePage.xaml.cs
@@ -27,6 +27,12 @@ namespace Avalonia.Diagnostics.Views
if (layer != null)
{
+ if (_adorner != null)
+ {
+ ((Panel)_adorner.Parent).Children.Remove(_adorner);
+ _adorner = null;
+ }
+
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),
diff --git a/src/Avalonia.Visuals/Media/PathMarkupParser.cs b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
index fbc189546c..9e4a3cbeae 100644
--- a/src/Avalonia.Visuals/Media/PathMarkupParser.cs
+++ b/src/Avalonia.Visuals/Media/PathMarkupParser.cs
@@ -320,7 +320,7 @@ namespace Avalonia.Media
if (c == 'E')
{
readSign = false;
- readExponent = c == 'E';
+ readExponent = true;
}
}
else
diff --git a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
index f7befa646a..041d8f8f6b 100644
--- a/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
@@ -25,11 +25,9 @@ namespace Avalonia.Rendering
private readonly IRenderLoop _renderLoop;
private readonly IVisual _root;
private readonly ISceneBuilder _sceneBuilder;
- private readonly RenderLayers _layers;
private bool _running;
private Scene _scene;
- private IRenderTarget _renderTarget;
private DirtyVisuals _dirty;
private IRenderTargetBitmapImpl _overlay;
private bool _updateQueued;
@@ -56,7 +54,7 @@ namespace Avalonia.Rendering
_dispatcher = dispatcher ?? Dispatcher.UIThread;
_root = root;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _layers = new RenderLayers();
+ Layers = new RenderLayers();
_renderLoop = renderLoop;
}
@@ -78,9 +76,9 @@ namespace Avalonia.Rendering
Contract.Requires(renderTarget != null);
_root = root;
- _renderTarget = renderTarget;
+ RenderTarget = renderTarget;
_sceneBuilder = sceneBuilder ?? new SceneBuilder();
- _layers = new RenderLayers();
+ Layers = new RenderLayers();
}
///
@@ -94,6 +92,16 @@ namespace Avalonia.Rendering
///
public string DebugFramesPath { get; set; }
+ ///
+ /// Gets the render layers.
+ ///
+ internal RenderLayers Layers { get; }
+
+ ///
+ /// Gets the current render target.
+ ///
+ internal IRenderTarget RenderTarget { get; private set; }
+
///
public void AddDirty(IVisual visual)
{
@@ -173,9 +181,9 @@ namespace Avalonia.Rendering
bool renderOverlay = DrawDirtyRects || DrawFps;
bool composite = false;
- if (_renderTarget == null)
+ if (RenderTarget == null)
{
- _renderTarget = ((IRenderRoot)_root).CreateRenderTarget();
+ RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
if (renderOverlay)
@@ -191,8 +199,8 @@ namespace Avalonia.Rendering
if (scene.Generation != _lastSceneId)
{
- context = _renderTarget.CreateDrawingContext(this);
- _layers.Update(scene, context);
+ context = RenderTarget.CreateDrawingContext(this);
+ Layers.Update(scene, context);
RenderToLayers(scene);
@@ -208,13 +216,13 @@ namespace Avalonia.Rendering
if (renderOverlay)
{
- context = context ?? _renderTarget.CreateDrawingContext(this);
+ context = context ?? RenderTarget.CreateDrawingContext(this);
RenderOverlay(scene, context);
RenderComposite(scene, context);
}
else if (composite)
{
- context = context ?? _renderTarget.CreateDrawingContext(this);
+ context = context ?? RenderTarget.CreateDrawingContext(this);
RenderComposite(scene, context);
}
@@ -224,8 +232,8 @@ namespace Avalonia.Rendering
catch (RenderTargetCorruptedException ex)
{
Logging.Logger.Information("Renderer", this, "Render target was corrupted. Exception: {0}", ex);
- _renderTarget?.Dispose();
- _renderTarget = null;
+ RenderTarget?.Dispose();
+ RenderTarget = null;
}
}
@@ -235,9 +243,11 @@ namespace Avalonia.Rendering
{
clipBounds = node.ClipBounds.Intersect(clipBounds);
- if (!clipBounds.IsEmpty)
+ if (!clipBounds.IsEmpty && node.Opacity > 0)
{
- node.BeginRender(context);
+ var isLayerRoot = node.Visual == layer;
+
+ node.BeginRender(context, isLayerRoot);
foreach (var operation in node.DrawOperations)
{
@@ -251,7 +261,7 @@ namespace Avalonia.Rendering
Render(context, (VisualNode)child, layer, clipBounds);
}
- node.EndRender(context);
+ node.EndRender(context, isLayerRoot);
}
}
}
@@ -262,7 +272,7 @@ namespace Avalonia.Rendering
{
foreach (var layer in scene.Layers)
{
- var renderTarget = _layers[layer.LayerRoot].Bitmap;
+ var renderTarget = Layers[layer.LayerRoot].Bitmap;
var node = (VisualNode)scene.FindNode(layer.LayerRoot);
if (node != null)
@@ -322,7 +332,7 @@ namespace Avalonia.Rendering
foreach (var layer in scene.Layers)
{
- var bitmap = _layers[layer.LayerRoot].Bitmap;
+ var bitmap = Layers[layer.LayerRoot].Bitmap;
var sourceRect = new Rect(0, 0, bitmap.PixelWidth, bitmap.PixelHeight);
if (layer.GeometryClip != null)
@@ -353,7 +363,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
- RenderFps(context, clientRect, true);
+ RenderFps(context, clientRect, scene.Layers.Count);
}
}
@@ -442,7 +452,7 @@ namespace Avalonia.Rendering
{
var index = 0;
- foreach (var layer in _layers)
+ foreach (var layer in Layers)
{
var fileName = Path.Combine(DebugFramesPath, $"frame-{id}-layer-{index++}.png");
layer.Bitmap.Save(fileName);
diff --git a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
index 2d5a864089..84313f0906 100644
--- a/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
+++ b/src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
@@ -69,7 +69,7 @@ namespace Avalonia.Rendering
if (DrawFps)
{
- RenderFps(context.PlatformImpl, _root.Bounds, true);
+ RenderFps(context.PlatformImpl, _root.Bounds, null);
}
}
}
diff --git a/src/Avalonia.Visuals/Rendering/RendererBase.cs b/src/Avalonia.Visuals/Rendering/RendererBase.cs
index 707b31998a..eac362e997 100644
--- a/src/Avalonia.Visuals/Rendering/RendererBase.cs
+++ b/src/Avalonia.Visuals/Rendering/RendererBase.cs
@@ -22,15 +22,12 @@ namespace Avalonia.Rendering
};
}
- protected void RenderFps(IDrawingContextImpl context, Rect clientRect, bool incrementFrameCount)
+ protected void RenderFps(IDrawingContextImpl context, Rect clientRect, int? layerCount)
{
var now = _stopwatch.Elapsed;
var elapsed = now - _lastFpsUpdate;
- if (incrementFrameCount)
- {
- ++_framesThisSecond;
- }
+ ++_framesThisSecond;
if (elapsed.TotalSeconds > 1)
{
@@ -39,7 +36,15 @@ namespace Avalonia.Rendering
_lastFpsUpdate = now;
}
- _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+ if (layerCount.HasValue)
+ {
+ _fpsText.Text = string.Format("Layers: {0} FPS: {1:000}", layerCount, _fps);
+ }
+ else
+ {
+ _fpsText.Text = string.Format("FPS: {0:000}", _fps);
+ }
+
var size = _fpsText.Measure();
var rect = new Rect(clientRect.Right - size.Width, 0, size.Width, size.Height);
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
index 0d2fc17b95..234cadbf31 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/IVisualNode.cs
@@ -72,13 +72,15 @@ namespace Avalonia.Rendering.SceneGraph
/// Sets up the drawing context for rendering the node's geometry.
///
/// The drawing context.
- void BeginRender(IDrawingContextImpl context);
+ /// Whether to skip pushing the control's opacity.
+ void BeginRender(IDrawingContextImpl context, bool skipOpacity);
///
/// Resets the drawing context after rendering the node's geometry.
///
/// The drawing context.
- void EndRender(IDrawingContextImpl context);
+ /// Whether to skip popping the control's opacity.
+ void EndRender(IDrawingContextImpl context, bool skipOpacity);
///
/// Hit test the geometry in this node.
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
index 90ef78de37..8f4f487e08 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/SceneBuilder.cs
@@ -167,7 +167,6 @@ namespace Avalonia.Rendering.SceneGraph
using (context.PushPostTransform(m))
using (context.PushTransformContainer())
{
- var startLayer = opacity < 1 || visual.OpacityMask != null;
var clipBounds = bounds.TransformToAABB(contextImpl.Transform).Intersect(clip);
forceRecurse = forceRecurse ||
@@ -179,9 +178,11 @@ namespace Avalonia.Rendering.SceneGraph
node.ClipToBounds = clipToBounds;
node.GeometryClip = visual.Clip?.PlatformImpl;
node.Opacity = opacity;
- node.OpacityMask = visual.OpacityMask;
- if (startLayer)
+ // TODO: Check equality between node.OpacityMask and visual.OpacityMask before assigning.
+ node.OpacityMask = visual.OpacityMask?.ToImmutable();
+
+ if (ShouldStartLayer(visual))
{
if (node.LayerRoot != visual)
{
@@ -192,7 +193,7 @@ namespace Avalonia.Rendering.SceneGraph
UpdateLayer(node, scene.Layers[node.LayerRoot]);
}
}
- else if (!startLayer && node.LayerRoot == node.Visual && node.Parent != null)
+ else if (node.LayerRoot == node.Visual && node.Parent != null)
{
ClearLayer(scene, node);
}
@@ -366,6 +367,14 @@ namespace Avalonia.Rendering.SceneGraph
}
}
+ private static bool ShouldStartLayer(IVisual visual)
+ {
+ var o = visual as IAvaloniaObject;
+ return visual.VisualChildren.Count > 0 &&
+ o != null &&
+ o.IsAnimating(Visual.OpacityProperty);
+ }
+
private static IGeometryImpl CreateLayerGeometryClip(VisualNode node)
{
IGeometryImpl result = null;
diff --git a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
index dd5740e4a9..6bea4d9bd6 100644
--- a/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
+++ b/src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
@@ -22,6 +22,7 @@ namespace Avalonia.Rendering.SceneGraph
private List _children;
private List _drawOperations;
private bool _drawOperationsCloned;
+ private Matrix transformRestore;
///
/// Initializes a new instance of the class.
@@ -218,8 +219,10 @@ namespace Avalonia.Rendering.SceneGraph
}
///
- public void BeginRender(IDrawingContextImpl context)
+ public void BeginRender(IDrawingContextImpl context, bool skipOpacity)
{
+ transformRestore = context.Transform;
+
if (ClipToBounds)
{
context.Transform = Matrix.Identity;
@@ -228,24 +231,47 @@ namespace Avalonia.Rendering.SceneGraph
context.Transform = Transform;
+ if (Opacity != 1 && !skipOpacity)
+ {
+ context.PushOpacity(Opacity);
+ }
+
if (GeometryClip != null)
{
context.PushGeometryClip(GeometryClip);
}
+
+ if (OpacityMask != null)
+ {
+ context.PushOpacityMask(OpacityMask, ClipBounds);
+ }
}
///
- public void EndRender(IDrawingContextImpl context)
+ public void EndRender(IDrawingContextImpl context, bool skipOpacity)
{
+ if (OpacityMask != null)
+ {
+ context.PopOpacityMask();
+ }
+
if (GeometryClip != null)
{
context.PopGeometryClip();
}
+ if (Opacity != 1 && !skipOpacity)
+ {
+ context.PopOpacity();
+ }
+
if (ClipToBounds)
{
+ context.Transform = Matrix.Identity;
context.PopClip();
}
+
+ context.Transform = transformRestore;
}
private Rect CalculateBounds()
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index bc65d4f69f..3662fe50be 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -537,6 +537,19 @@ namespace Avalonia
v.SetVisualParent(null);
}
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ foreach (Visual v in e.OldItems)
+ {
+ v.SetVisualParent(null);
+ }
+
+ foreach (Visual v in e.NewItems)
+ {
+ v.SetVisualParent(this);
+ }
+
break;
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
index eb5f87ea2a..7f750144df 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/Binding.cs
@@ -66,7 +66,7 @@ namespace Avalonia.Markup.Xaml.Data
///
/// Gets or sets the binding path.
///
- public string Path { get; set; }
+ public string Path { get; set; } = "";
///
/// Gets or sets the binding priority.
@@ -93,53 +93,53 @@ namespace Avalonia.Markup.Xaml.Data
bool enableDataValidation = false)
{
Contract.Requires(target != null);
-
anchor = anchor ?? DefaultAnchor?.Target;
-
- var pathInfo = ParsePath(Path);
- ValidateState(pathInfo);
+
enableDataValidation = enableDataValidation && Priority == BindingPriority.LocalValue;
-
+
ExpressionObserver observer;
- if (pathInfo.ElementName != null || ElementName != null)
+ if (ElementName != null)
{
observer = CreateElementObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.ElementName ?? ElementName,
- pathInfo.Path);
+ ElementName,
+ Path,
+ enableDataValidation);
}
else if (Source != null)
{
- observer = CreateSourceObserver(Source, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(Source, Path, enableDataValidation);
}
else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
{
observer = CreateDataContexObserver(
target,
- pathInfo.Path,
+ Path,
targetProperty == Control.DataContextProperty,
anchor,
enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.Self)
{
- observer = CreateSourceObserver(target, pathInfo.Path, enableDataValidation);
+ observer = CreateSourceObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.TemplatedParent)
{
- observer = CreateTemplatedParentObserver(target, pathInfo.Path);
+ observer = CreateTemplatedParentObserver(target, Path, enableDataValidation);
}
else if (RelativeSource.Mode == RelativeSourceMode.FindAncestor)
{
- if (RelativeSource.AncestorType == null)
+ if (RelativeSource.Tree == TreeType.Visual && RelativeSource.AncestorType == null)
{
- throw new InvalidOperationException("AncestorType must be set for RelativeSourceModel.FindAncestor.");
+ throw new InvalidOperationException("AncestorType must be set for RelativeSourceMode.FindAncestor when searching the visual tree.");
}
observer = CreateFindAncestorObserver(
(target as IControl) ?? (anchor as IControl),
- pathInfo.Path);
+ RelativeSource,
+ Path,
+ enableDataValidation);
}
else
{
@@ -168,53 +168,6 @@ namespace Avalonia.Markup.Xaml.Data
return new InstancedBinding(subject, Mode, Priority);
}
- private static PathInfo ParsePath(string path)
- {
- var result = new PathInfo();
-
- if (string.IsNullOrWhiteSpace(path) || path == ".")
- {
- result.Path = string.Empty;
- }
- else if (path.StartsWith("#"))
- {
- var dot = path.IndexOf('.');
-
- if (dot != -1)
- {
- result.Path = path.Substring(dot + 1);
- result.ElementName = path.Substring(1, dot - 1);
- }
- else
- {
- result.Path = string.Empty;
- result.ElementName = path.Substring(1);
- }
- }
- else
- {
- result.Path = path;
- }
-
- return result;
- }
-
- private void ValidateState(PathInfo pathInfo)
- {
- if (pathInfo.ElementName != null && ElementName != null)
- {
- throw new InvalidOperationException(
- "ElementName property cannot be set when an #elementName path is provided.");
- }
-
- if ((pathInfo.ElementName != null || ElementName != null) &&
- RelativeSource != null)
- {
- throw new InvalidOperationException(
- "ElementName property cannot be set with a RelativeSource.");
- }
- }
-
private ExpressionObserver CreateDataContexObserver(
IAvaloniaObject target,
string path,
@@ -256,7 +209,11 @@ namespace Avalonia.Markup.Xaml.Data
}
}
- private ExpressionObserver CreateElementObserver(IControl target, string elementName, string path)
+ private ExpressionObserver CreateElementObserver(
+ IControl target,
+ string elementName,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -264,35 +221,39 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
ControlLocator.Track(target, elementName),
path,
- false,
+ enableDataValidation,
description);
return result;
}
private ExpressionObserver CreateFindAncestorObserver(
IControl target,
- string path)
+ RelativeSource relativeSource,
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
return new ExpressionObserver(
- ControlLocator.Track(target, RelativeSource.AncestorType, RelativeSource.AncestorLevel -1),
- path);
+ ControlLocator.Track(target, relativeSource.Tree, relativeSource.AncestorLevel - 1, relativeSource.AncestorType),
+ path,
+ enableDataValidation);
}
private ExpressionObserver CreateSourceObserver(
object source,
string path,
- bool enabledDataValidation)
+ bool enableDataValidation)
{
Contract.Requires(source != null);
- return new ExpressionObserver(source, path, enabledDataValidation);
+ return new ExpressionObserver(source, path, enableDataValidation);
}
private ExpressionObserver CreateTemplatedParentObserver(
IAvaloniaObject target,
- string path)
+ string path,
+ bool enableDataValidation)
{
Contract.Requires(target != null);
@@ -303,7 +264,8 @@ namespace Avalonia.Markup.Xaml.Data
var result = new ExpressionObserver(
() => target.GetValue(Control.TemplatedParentProperty),
path,
- update);
+ update,
+ enableDataValidation);
return result;
}
@@ -328,6 +290,7 @@ namespace Avalonia.Markup.Xaml.Data
{
public string Path { get; set; }
public string ElementName { get; set; }
+ public RelativeSource RelativeSource { get; set; }
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
index f77df6853b..825d3b8ba5 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/Data/RelativeSource.cs
@@ -87,5 +87,7 @@ namespace Avalonia.Markup.Xaml.Data
/// Gets or sets a value that describes the type of relative source lookup.
///
public RelativeSourceMode Mode { get; set; }
+
+ public TreeType Tree { get; set; } = TreeType.Visual;
}
}
\ No newline at end of file
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
index 8984498393..c6705cbb4b 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
@@ -29,20 +29,167 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public override object ProvideValue(IServiceProvider serviceProvider)
{
+ var descriptorContext = (ITypeDescriptorContext)serviceProvider;
+
+ var pathInfo = ParsePath(Path, descriptorContext);
+ ValidateState(pathInfo);
+
return new Binding
{
Converter = Converter,
ConverterParameter = ConverterParameter,
- ElementName = ElementName,
+ ElementName = pathInfo.ElementName ?? ElementName,
FallbackValue = FallbackValue,
Mode = Mode,
- Path = Path,
+ Path = pathInfo.Path,
Priority = Priority,
- RelativeSource = RelativeSource,
+ RelativeSource = pathInfo.RelativeSource ?? RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor((ITypeDescriptorContext)serviceProvider))
};
}
+ private class PathInfo
+ {
+ public string Path { get; set; }
+ public string ElementName { get; set; }
+ public RelativeSource RelativeSource { get; set; }
+ }
+
+ private void ValidateState(PathInfo pathInfo)
+ {
+ if (pathInfo.ElementName != null && ElementName != null)
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set when an #elementName path is provided.");
+ }
+
+ if (pathInfo.RelativeSource != null && RelativeSource != null)
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set when a $self or $parent path is provided.");
+ }
+
+ if ((pathInfo.ElementName != null || ElementName != null) &&
+ (pathInfo.RelativeSource != null || RelativeSource != null))
+ {
+ throw new InvalidOperationException(
+ "ElementName property cannot be set with a RelativeSource.");
+ }
+ }
+
+ private static PathInfo ParsePath(string path, ITypeDescriptorContext context)
+ {
+ var result = new PathInfo();
+
+ if (string.IsNullOrWhiteSpace(path) || path == ".")
+ {
+ result.Path = string.Empty;
+ }
+ else if (path.StartsWith("#"))
+ {
+ var dot = path.IndexOf('.');
+
+ if (dot != -1)
+ {
+ result.Path = path.Substring(dot + 1);
+ result.ElementName = path.Substring(1, dot - 1);
+ }
+ else
+ {
+ result.Path = string.Empty;
+ result.ElementName = path.Substring(1);
+ }
+ }
+ else if (path.StartsWith("$"))
+ {
+ var relativeSource = new RelativeSource
+ {
+ Tree = TreeType.Logical
+ };
+ result.RelativeSource = relativeSource;
+ var dot = path.IndexOf('.');
+ string relativeSourceMode;
+ if (dot != -1)
+ {
+ result.Path = path.Substring(dot + 1);
+ relativeSourceMode = path.Substring(1, dot - 1);
+ }
+ else
+ {
+ result.Path = string.Empty;
+ relativeSourceMode = path.Substring(1);
+ }
+
+ if (relativeSourceMode == "self")
+ {
+ relativeSource.Mode = RelativeSourceMode.Self;
+ }
+ else if (relativeSourceMode == "parent")
+ {
+ relativeSource.Mode = RelativeSourceMode.FindAncestor;
+ relativeSource.AncestorLevel = 1;
+ }
+ else if (relativeSourceMode.StartsWith("parent["))
+ {
+ relativeSource.Mode = RelativeSourceMode.FindAncestor;
+ var parentConfigStart = relativeSourceMode.IndexOf('[');
+ if (!relativeSourceMode.EndsWith("]"))
+ {
+ throw new InvalidOperationException("Invalid RelativeSource binding syntax. Expected matching ']' for '['.");
+ }
+ var parentConfigParams = relativeSourceMode.Substring(parentConfigStart + 1).TrimEnd(']').Split(';');
+ if (parentConfigParams.Length > 2 || parentConfigParams.Length == 0)
+ {
+ throw new InvalidOperationException("Expected either 1 or 2 parameters for RelativeSource binding syntax");
+ }
+ else if (parentConfigParams.Length == 1)
+ {
+ if (int.TryParse(parentConfigParams[0], out int level))
+ {
+ relativeSource.AncestorType = null;
+ relativeSource.AncestorLevel = level + 1;
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ }
+ }
+ else
+ {
+ relativeSource.AncestorType = LookupAncestorType(parentConfigParams[0], context);
+ relativeSource.AncestorLevel = int.Parse(parentConfigParams[1]) + 1;
+ }
+ }
+ else
+ {
+ throw new InvalidOperationException($"Invalid RelativeSource binding syntax: {relativeSourceMode}");
+ }
+ }
+ else
+ {
+ result.Path = path;
+ }
+
+ return result;
+ }
+
+ private static Type LookupAncestorType(string ancestorTypeName, ITypeDescriptorContext context)
+ {
+ var parts = ancestorTypeName.Split(':');
+ if (parts.Length == 0 || parts.Length > 2)
+ {
+ throw new InvalidOperationException("Invalid type name");
+ }
+
+ if (parts.Length == 1)
+ {
+ return context.ResolveType(string.Empty, parts[0]);
+ }
+ else
+ {
+ return context.ResolveType(parts[0], parts[1]);
+ }
+ }
private static object GetDefaultAnchor(ITypeDescriptorContext context)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
index 4664947b8e..c5fe83977f 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/TemplateBindingExtension.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
ElementName = ElementName,
Mode = Mode,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
- Path = Path,
+ Path = Path ?? string.Empty,
Priority = Priority,
};
}
diff --git a/src/Markup/Avalonia.Markup/ControlLocator.cs b/src/Markup/Avalonia.Markup/ControlLocator.cs
index de8415d6db..1a82c0a4fd 100644
--- a/src/Markup/Avalonia.Markup/ControlLocator.cs
+++ b/src/Markup/Avalonia.Markup/ControlLocator.cs
@@ -11,6 +11,21 @@ using Avalonia.VisualTree;
namespace Avalonia.Markup
{
+ ///
+ /// The type of tree via which to track a control.
+ ///
+ public enum TreeType
+ {
+ ///
+ /// The visual tree.
+ ///
+ Visual,
+ ///
+ /// The logical tree.
+ ///
+ Logical,
+ }
+
///
/// Locates controls relative to other controls.
///
@@ -27,13 +42,13 @@ namespace Avalonia.Markup
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.AttachedToLogicalTree -= x)
.Select(x => ((IControl)x.Sender).FindNameScope())
.StartWith(relativeTo.FindNameScope());
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromLogicalTree += x,
- x => relativeTo.DetachedFromLogicalTree += x)
+ x => relativeTo.DetachedFromLogicalTree -= x)
.Select(x => (INameScope)null);
return attached.Merge(detached).Select(nameScope =>
@@ -68,37 +83,75 @@ namespace Avalonia.Markup
///
/// The control relative from which the other control should be found.
///
- /// The type of the ancestor to find.
+ /// The tree via which to track the control.
///
/// The level of ancestor control to look for. Use 0 for the first ancestor of the
/// requested type.
///
- public static IObservable Track(IControl relativeTo, Type ancestorType, int ancestorLevel)
+ /// The type of the ancestor to find.
+ public static IObservable Track(IControl relativeTo, TreeType tree, int ancestorLevel, Type ancestorType = null)
+ {
+ return TrackAttachmentToTree(relativeTo, tree).Select(isAttachedToTree =>
+ {
+ if (isAttachedToTree)
+ {
+ if (tree == TreeType.Visual)
+ {
+ return relativeTo.GetVisualAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ else
+ {
+ return relativeTo.GetLogicalAncestors()
+ .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+ .ElementAtOrDefault(ancestorLevel) as IControl;
+ }
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ private static IObservable TrackAttachmentToTree(IControl relativeTo, TreeType tree)
+ {
+ return tree == TreeType.Visual ? TrackAttachmentToVisualTree(relativeTo) : TrackAttachmentToLogicalTree(relativeTo);
+ }
+
+ private static IObservable TrackAttachmentToVisualTree(IControl relativeTo)
{
var attached = Observable.FromEventPattern(
x => relativeTo.AttachedToVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.AttachedToVisualTree -= x)
.Select(x => true)
.StartWith(relativeTo.IsAttachedToVisualTree);
var detached = Observable.FromEventPattern(
x => relativeTo.DetachedFromVisualTree += x,
- x => relativeTo.DetachedFromVisualTree += x)
+ x => relativeTo.DetachedFromVisualTree -= x)
.Select(x => false);
- return attached.Merge(detached).Select(isAttachedToVisualTree =>
- {
- if (isAttachedToVisualTree)
- {
- return relativeTo.GetVisualAncestors()
- .Where(x => ancestorType.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()))
- .ElementAtOrDefault(ancestorLevel) as IControl;
- }
- else
- {
- return null;
- }
- });
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
+ }
+
+ private static IObservable TrackAttachmentToLogicalTree(IControl relativeTo)
+ {
+ var attached = Observable.FromEventPattern(
+ x => relativeTo.AttachedToLogicalTree += x,
+ x => relativeTo.AttachedToLogicalTree -= x)
+ .Select(x => true)
+ .StartWith(relativeTo.IsAttachedToLogicalTree);
+
+ var detached = Observable.FromEventPattern(
+ x => relativeTo.DetachedFromLogicalTree += x,
+ x => relativeTo.DetachedFromLogicalTree -= x)
+ .Select(x => false);
+
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
}
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
index a824a38867..563b372c78 100644
--- a/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
+++ b/src/Markup/Avalonia.Markup/Data/Parsers/ArgumentListParser.cs
@@ -51,15 +51,7 @@ namespace Avalonia.Markup.Data.Parsers
}
}
- if (!r.End)
- {
- r.Take();
- return result;
- }
- else
- {
- throw new ExpressionParseException(r.Position, "Expected ']'.");
- }
+ throw new ExpressionParseException(r.Position, "Expected ']'.");
}
return null;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
index 6a72923ce3..b1bfdcbfeb 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
@@ -54,7 +54,6 @@ namespace Avalonia.Direct2D1.Media
_finishedCallback = finishedCallback;
_directWriteFactory = directWriteFactory;
_imagingFactory = imagingFactory;
- _swapChain = swapChain;
_renderTarget.BeginDraw();
}
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
index 4d6559a078..c75150ca6d 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
@@ -2,22 +2,22 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
-using System.Collections;
-using System.Collections.Generic;
+using System.ComponentModel;
+using System.Reactive.Concurrency;
using System.Reactive.Linq;
using System.Reactive.Subjects;
-using Microsoft.Reactive.Testing;
+using System.Threading;
+using System.Threading.Tasks;
using Avalonia.Data;
using Avalonia.Logging;
-using Avalonia.UnitTests;
-using Xunit;
-using System.Threading.Tasks;
+using Avalonia.Markup.Xaml.Data;
using Avalonia.Platform;
-using System.Threading;
-using Moq;
-using System.Reactive.Disposables;
-using System.Reactive.Concurrency;
using Avalonia.Threading;
+using Avalonia.UnitTests;
+using Avalonia.Diagnostics;
+using Microsoft.Reactive.Testing;
+using Moq;
+using Xunit;
namespace Avalonia.Base.UnitTests
{
@@ -363,7 +363,7 @@ namespace Avalonia.Base.UnitTests
Assert.True(called);
}
}
-
+
[Fact]
public async Task Bind_With_Scheduler_Executes_On_Scheduler()
{
@@ -387,6 +387,77 @@ namespace Avalonia.Base.UnitTests
}
}
+ [Fact]
+ public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Value = 50
+ };
+
+ var target = new Class1();
+
+ target.Bind(Class1.DoubleValueProperty,
+ new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+ var child = new Class1();
+
+ child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+
+ // Issues #855 and #824 were causing a StackOverflowException at this point.
+ target.DoubleValue = 51.001;
+
+ Assert.Equal(2, viewModel.SetterInvokedCount);
+
+ double expected = 51;
+
+ Assert.Equal(expected, viewModel.Value);
+ Assert.Equal(expected, target.DoubleValue);
+ Assert.Equal(expected, child.DoubleValue);
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_No_Value_Returns_False()
+ {
+ var target = new Class1();
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Animation_Value_Returns_False()
+ {
+ var target = new Class1();
+
+ target.SetValue(Class1.FooProperty, "foo", BindingPriority.Animation);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Non_Animation_Binding_Returns_False()
+ {
+ var target = new Class1();
+ var source = new Subject();
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.LocalValue);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
+ [Fact]
+ public void IsAnimating_On_Property_With_Animation_Binding_Returns_True()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject("foo");
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+ Assert.True(target.IsAnimating(Class1.FooProperty));
+ }
+
///
/// Returns an observable that returns a single value but does not complete.
///
@@ -405,6 +476,15 @@ namespace Avalonia.Base.UnitTests
public static readonly StyledProperty QuxProperty =
AvaloniaProperty.Register("Qux", 5.6);
+
+ public static readonly StyledProperty DoubleValueProperty =
+ AvaloniaProperty.Register(nameof(DoubleValue));
+
+ public double DoubleValue
+ {
+ get { return GetValue(DoubleValueProperty); }
+ set { SetValue(DoubleValueProperty, value); }
+ }
}
private class Class2 : Class1
@@ -431,5 +511,40 @@ namespace Avalonia.Base.UnitTests
return InstancedBinding.OneTime(_source);
}
}
+
+ private class TestStackOverflowViewModel : INotifyPropertyChanged
+ {
+ public int SetterInvokedCount { get; private set; }
+
+ public const int MaxInvokedCount = 1000;
+
+ private double _value;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public double Value
+ {
+ get { return _value; }
+ set
+ {
+ if (_value != value)
+ {
+ SetterInvokedCount++;
+ if (SetterInvokedCount < MaxInvokedCount)
+ {
+ _value = (int)value;
+ if (_value > 75) _value = 75;
+ if (_value < 25) _value = 25;
+ }
+ else
+ {
+ _value = value;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+ }
+ }
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
index e9cb2bf450..6d10f50276 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
+++ b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
@@ -3,10 +3,11 @@
using System;
using System.Collections.Generic;
+using System.ComponentModel;
using System.Reactive.Subjects;
-using Avalonia;
using Avalonia.Data;
using Avalonia.Logging;
+using Avalonia.Markup.Xaml.Data;
using Avalonia.UnitTests;
using Xunit;
@@ -208,7 +209,7 @@ namespace Avalonia.Base.UnitTests
{
var target = new Class1();
- Assert.Throws(() =>
+ Assert.Throws(() =>
target.SetValue(Class1.BarProperty, "newvalue"));
}
@@ -217,7 +218,7 @@ namespace Avalonia.Base.UnitTests
{
var target = new Class1();
- Assert.Throws(() =>
+ Assert.Throws(() =>
target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
}
@@ -227,7 +228,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1();
var source = new Subject();
- Assert.Throws(() =>
+ Assert.Throws(() =>
target.Bind(Class1.BarProperty, source));
}
@@ -439,12 +440,46 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata().DefaultBindingMode);
}
+ [Fact]
+ public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Value = 50
+ };
+
+ var target = new Class1();
+
+ target.Bind(Class1.DoubleValueProperty, new Binding("Value")
+ {
+ Mode = BindingMode.TwoWay,
+ Source = viewModel
+ });
+
+ var child = new Class1();
+
+ child[!!Class1.DoubleValueProperty] = target[!!Class1.DoubleValueProperty];
+
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+
+ // Issues #855 and #824 were causing a StackOverflowException at this point.
+ target.DoubleValue = 51.001;
+
+ Assert.Equal(2, viewModel.SetterInvokedCount);
+
+ double expected = 51;
+
+ Assert.Equal(expected, viewModel.Value);
+ Assert.Equal(expected, target.DoubleValue);
+ Assert.Equal(expected, child.DoubleValue);
+ }
+
private class Class1 : AvaloniaObject
{
public static readonly DirectProperty FooProperty =
AvaloniaProperty.RegisterDirect(
- "Foo",
- o => o.Foo,
+ "Foo",
+ o => o.Foo,
(o, v) => o.Foo = v,
unsetValue: "unset");
@@ -453,14 +488,21 @@ namespace Avalonia.Base.UnitTests
public static readonly DirectProperty BazProperty =
AvaloniaProperty.RegisterDirect(
- "Bar",
- o => o.Baz,
- (o,v) => o.Baz = v,
+ "Bar",
+ o => o.Baz,
+ (o, v) => o.Baz = v,
unsetValue: -1);
+ public static readonly DirectProperty DoubleValueProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(DoubleValue),
+ o => o.DoubleValue,
+ (o, v) => o.DoubleValue = v);
+
private string _foo = "initial";
private readonly string _bar = "bar";
private int _baz = 5;
+ private double _doubleValue;
public string Foo
{
@@ -478,6 +520,12 @@ namespace Avalonia.Base.UnitTests
get { return _baz; }
set { SetAndRaise(BazProperty, ref _baz, value); }
}
+
+ public double DoubleValue
+ {
+ get { return _doubleValue; }
+ set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+ }
}
private class Class2 : AvaloniaObject
@@ -497,5 +545,40 @@ namespace Avalonia.Base.UnitTests
set { SetAndRaise(FooProperty, ref _foo, value); }
}
}
+
+ private class TestStackOverflowViewModel : INotifyPropertyChanged
+ {
+ public int SetterInvokedCount { get; private set; }
+
+ public const int MaxInvokedCount = 1000;
+
+ private double _value;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public double Value
+ {
+ get { return _value; }
+ set
+ {
+ if (_value != value)
+ {
+ SetterInvokedCount++;
+ if (SetterInvokedCount < MaxInvokedCount)
+ {
+ _value = (int)value;
+ if (_value > 75) _value = 75;
+ if (_value < 25) _value = 25;
+ }
+ else
+ {
+ _value = value;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+ }
+ }
+ }
+ }
}
-}
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
index 587816b07b..fd731455d8 100644
--- a/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Collections/AvaloniaListTests.cs
@@ -83,6 +83,28 @@ namespace Avalonia.Base.UnitTests.Collections
Assert.Equal(new[] { 6, 7, 8, 9, 10, 1, 2, 3, 4, 5 }, target);
}
+ [Fact]
+ public void MoveRange_Raises_Correct_CollectionChanged_Event()
+ {
+ var target = new AvaloniaList(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 });
+ var raised = false;
+
+ target.CollectionChanged += (s, e) =>
+ {
+ Assert.Equal(NotifyCollectionChangedAction.Move, e.Action);
+ Assert.Equal(0, e.OldStartingIndex);
+ Assert.Equal(10, e.NewStartingIndex);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.OldItems);
+ Assert.Equal(new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 }, e.NewItems);
+ raised = true;
+ };
+
+ target.MoveRange(0, 9, 10);
+
+ Assert.True(raised);
+ Assert.Equal(new[] { 10, 1, 2, 3, 4, 5, 6, 7, 8, 9 }, target);
+ }
+
[Fact]
public void Adding_Item_Should_Raise_CollectionChanged()
{
diff --git a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
index 3a37585dc0..84ff492512 100644
--- a/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
+++ b/tests/Avalonia.Base.UnitTests/DirectPropertyTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.Reactive.Subjects;
using Avalonia.Data;
using Xunit;
@@ -70,6 +71,17 @@ namespace Avalonia.Base.UnitTests
Assert.Same(p1.Initialized, p2.Initialized);
}
+ [Fact]
+ public void IsAnimating_On_DirectProperty_With_Binding_Returns_False()
+ {
+ var target = new Class1();
+ var source = new BehaviorSubject("foo");
+
+ target.Bind(Class1.FooProperty, source, BindingPriority.Animation);
+
+ Assert.False(target.IsAnimating(Class1.FooProperty));
+ }
+
private class Class1 : AvaloniaObject
{
public static readonly DirectProperty FooProperty =
diff --git a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
index 1d987e2238..c16b89e0b6 100644
--- a/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
+++ b/tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
@@ -49,6 +49,7 @@
+
diff --git a/tests/Avalonia.Benchmarks/Base/Properties.cs b/tests/Avalonia.Benchmarks/Base/Properties.cs
new file mode 100644
index 0000000000..0a020961d5
--- /dev/null
+++ b/tests/Avalonia.Benchmarks/Base/Properties.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Reactive.Subjects;
+using BenchmarkDotNet.Attributes;
+
+namespace Avalonia.Benchmarks.Base
+{
+ [MemoryDiagnoser]
+ public class AvaloniaObjectBenchmark
+ {
+ private Class1 target = new Class1();
+ private Subject intBinding = new Subject();
+
+ public AvaloniaObjectBenchmark()
+ {
+ target.SetValue(Class1.IntProperty, 123);
+ }
+
+ [Benchmark]
+ public void ClearAndSetIntProperty()
+ {
+ target.ClearValue(Class1.IntProperty);
+ target.SetValue(Class1.IntProperty, 123);
+ }
+
+ [Benchmark]
+ public void BindIntProperty()
+ {
+ using (target.Bind(Class1.IntProperty, intBinding))
+ {
+ for (var i = 0; i < 100; ++i)
+ {
+ intBinding.OnNext(i);
+ }
+ }
+ }
+
+ class Class1 : AvaloniaObject
+ {
+ public static readonly AvaloniaProperty IntProperty =
+ AvaloniaProperty.Register("Int");
+ }
+ }
+}
diff --git a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
index db4c3c0eca..3de67839a7 100644
--- a/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
@@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new DockPanel
{
- Children = new Controls
+ Children =
{
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Top },
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
@@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new DockPanel
{
- Children = new Controls
+ Children =
{
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Left },
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },
diff --git a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs
index b5de8c67fa..29e60a8f83 100644
--- a/tests/Avalonia.Controls.UnitTests/DropDownTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/DropDownTests.cs
@@ -89,7 +89,7 @@ namespace Avalonia.Controls.UnitTests
return new Panel
{
Name = "container",
- Children = new Controls
+ Children =
{
new ContentControl
{
diff --git a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
index 5d622a5fc1..a1ba608ec6 100644
--- a/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
@@ -22,14 +22,14 @@ namespace Avalonia.Controls.UnitTests
{
var grid = new Grid()
{
- RowDefinitions = new RowDefinitions("*,Auto,*"),
- ColumnDefinitions = new ColumnDefinitions("*,*"),
- Children = new Controls()
- {
- new Border { [Grid.RowProperty] = 0 },
- new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
- new Border { [Grid.RowProperty] = 2 }
- }
+ RowDefinitions = new RowDefinitions("*,Auto,*"),
+ ColumnDefinitions = new ColumnDefinitions("*,*"),
+ Children =
+ {
+ new Border { [Grid.RowProperty] = 0 },
+ new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
+ new Border { [Grid.RowProperty] = 2 }
+ }
};
var root = new TestRoot { Child = grid };
@@ -43,14 +43,14 @@ namespace Avalonia.Controls.UnitTests
{
var grid = new Grid()
{
- ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
- RowDefinitions = new RowDefinitions("*,*"),
- Children = new Controls()
- {
- new Border { [Grid.ColumnProperty] = 0 },
- new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
- new Border { [Grid.ColumnProperty] = 2 },
- }
+ ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
+ RowDefinitions = new RowDefinitions("*,*"),
+ Children =
+ {
+ new Border { [Grid.ColumnProperty] = 0 },
+ new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+ new Border { [Grid.ColumnProperty] = 2 },
+ }
};
var root = new TestRoot { Child = grid };
@@ -64,14 +64,14 @@ namespace Avalonia.Controls.UnitTests
{
var grid = new Grid()
{
- ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
- RowDefinitions = new RowDefinitions("Auto,Auto"),
- Children = new Controls()
- {
- new Border { [Grid.ColumnProperty] = 0 },
- new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
- new Border { [Grid.ColumnProperty] = 2 },
- }
+ ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
+ RowDefinitions = new RowDefinitions("Auto,Auto"),
+ Children =
+ {
+ new Border { [Grid.ColumnProperty] = 0 },
+ new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
+ new Border { [Grid.ColumnProperty] = 2 },
+ }
};
var root = new TestRoot { Child = grid };
@@ -99,11 +99,11 @@ namespace Avalonia.Controls.UnitTests
var grid = new Grid()
{
- RowDefinitions = rowDefinitions,
- Children = new Controls()
- {
- control1, splitter, control2
- }
+ RowDefinitions = rowDefinitions,
+ Children =
+ {
+ control1, splitter, control2
+ }
};
var root = new TestRoot { Child = grid };
@@ -131,14 +131,14 @@ namespace Avalonia.Controls.UnitTests
{
var grid = new Grid()
{
- ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
- RowDefinitions = new RowDefinitions("*,*"),
- Children = new Controls()
- {
- new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
- new Border { [Grid.ColumnProperty] = 1 },
- new Border { [Grid.ColumnProperty] = 2 },
- }
+ ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
+ RowDefinitions = new RowDefinitions("*,*"),
+ Children =
+ {
+ new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
+ new Border { [Grid.ColumnProperty] = 1 },
+ new Border { [Grid.ColumnProperty] = 2 },
+ }
};
var root = new TestRoot { Child = grid };
@@ -171,11 +171,11 @@ namespace Avalonia.Controls.UnitTests
var grid = new Grid()
{
- ColumnDefinitions = columnDefinitions,
- Children = new Controls()
- {
- control1, splitter, control2
- }
+ ColumnDefinitions = columnDefinitions,
+ Children =
+ {
+ control1, splitter, control2
+ }
};
var root = new TestRoot { Child = grid };
diff --git a/tests/Avalonia.Controls.UnitTests/GridTests.cs b/tests/Avalonia.Controls.UnitTests/GridTests.cs
index a10bb38322..c5aea6501f 100644
--- a/tests/Avalonia.Controls.UnitTests/GridTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/GridTests.cs
@@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests
new RowDefinition(GridLength.Auto),
new RowDefinition(GridLength.Auto),
},
- Children = new Controls
+ Children =
{
new Border
{
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index c7992fe80f..fee4994ee3 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -1,11 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
+using System.Collections.Generic;
+using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
+using Avalonia.Data;
using Avalonia.Input;
using Avalonia.LogicalTree;
+using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling;
using Avalonia.VisualTree;
using Xunit;
@@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, target.SelectedIndex);
}
+ [Fact]
+ public void SelectedItem_Should_Not_Cause_StackOverflow()
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Items = new List { "foo", "bar", "baz" }
+ };
+
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ DataContext = viewModel,
+ Items = viewModel.Items
+ };
+
+ target.Bind(ListBox.SelectedItemProperty,
+ new Binding("SelectedItem") { Mode = BindingMode.TwoWay });
+
+ Assert.Equal(0, viewModel.SetterInvokedCount);
+
+ // In Issue #855, a Stackoverflow occured here.
+ target.SelectedItem = viewModel.Items[2];
+
+ Assert.Equal(viewModel.Items[1], target.SelectedItem);
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+ }
+
+ private class TestStackOverflowViewModel : INotifyPropertyChanged
+ {
+ public List Items { get; set; }
+
+ public int SetterInvokedCount { get; private set; }
+
+ public const int MaxInvokedCount = 1000;
+
+ private string _selectedItem;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public string SelectedItem
+ {
+ get { return _selectedItem; }
+ set
+ {
+ if (_selectedItem != value)
+ {
+ SetterInvokedCount++;
+
+ int index = Items.IndexOf(value);
+
+ if (MaxInvokedCount > SetterInvokedCount && index > 0)
+ {
+ _selectedItem = Items[index - 1];
+ }
+ else
+ {
+ _selectedItem = value;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItem)));
+ }
+ }
+ }
+ }
+
private Control CreateListBoxTemplate(ITemplatedControl parent)
{
return new ScrollViewer
@@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate();
}
}
-}
+}
\ No newline at end of file
diff --git a/tests/Avalonia.Controls.UnitTests/PanelTests.cs b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
index fb1ae3ba1a..ed239120d6 100644
--- a/tests/Avalonia.Controls.UnitTests/PanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/PanelTests.cs
@@ -2,8 +2,8 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
-using Avalonia.Collections;
using Avalonia.LogicalTree;
+using Avalonia.VisualTree;
using Xunit;
namespace Avalonia.Controls.UnitTests
@@ -18,20 +18,9 @@ namespace Avalonia.Controls.UnitTests
panel.Children.Add(child);
- Assert.Equal(child.Parent, panel);
- Assert.Equal(child.GetLogicalParent(), panel);
- }
-
- [Fact]
- public void Setting_Controls_Should_Set_Child_Controls_Parent()
- {
- var panel = new Panel();
- var child = new Control();
-
- panel.Children = new Controls { child };
-
- Assert.Equal(child.Parent, panel);
- Assert.Equal(child.GetLogicalParent(), panel);
+ Assert.Same(child.Parent, panel);
+ Assert.Same(child.GetLogicalParent(), panel);
+ Assert.Same(child.GetVisualParent(), panel);
}
[Fact]
@@ -45,6 +34,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child.Parent);
Assert.Null(child.GetLogicalParent());
+ Assert.Null(child.GetVisualParent());
}
[Fact]
@@ -60,62 +50,70 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
Assert.Null(child2.Parent);
Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child2.GetVisualParent());
}
[Fact]
- public void Resetting_Panel_Children_Should_Clear_Child_Controls_Parent()
+ public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
{
var panel = new Panel();
var child1 = new Control();
var child2 = new Control();
panel.Children.Add(child1);
- panel.Children.Add(child2);
- panel.Children = new Controls();
+ panel.Children[0] = child2;
Assert.Null(child1.Parent);
Assert.Null(child1.GetLogicalParent());
- Assert.Null(child2.Parent);
- Assert.Null(child2.GetLogicalParent());
+ Assert.Null(child1.GetVisualParent());
+ Assert.Same(child2.Parent, panel);
+ Assert.Same(child2.GetLogicalParent(), panel);
+ Assert.Same(child2.GetVisualParent(), panel);
}
[Fact]
- public void Setting_Children_Should_Make_Controls_Appear_In_Panel_Children()
+ public void Child_Control_Should_Appear_In_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
- panel.Children = new Controls { child };
+ panel.Children.Add(child);
Assert.Equal(new[] { child }, panel.Children);
Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
[Fact]
- public void Child_Control_Should_Appear_In_Panel_Children()
+ public void Removing_Child_Control_Should_Remove_From_Panel_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
panel.Children.Add(child);
+ panel.Children.Remove(child);
- Assert.Equal(new[] { child }, panel.Children);
- Assert.Equal(new[] { child }, panel.GetLogicalChildren());
+ Assert.Equal(new Control[0], panel.Children);
+ Assert.Empty(panel.GetLogicalChildren());
+ Assert.Empty(panel.GetVisualChildren());
}
[Fact]
- public void Removing_Child_Control_Should_Remove_From_Panel_Children()
+ public void Moving_Panel_Children_Should_Reoder_Logical_And_Visual_Children()
{
var panel = new Panel();
- var child = new Control();
+ var child1 = new Control();
+ var child2 = new Control();
- panel.Children.Add(child);
- panel.Children.Remove(child);
+ panel.Children.Add(child1);
+ panel.Children.Add(child2);
+ panel.Children.Move(1, 0);
- Assert.Equal(new Control[0], panel.Children);
- Assert.Equal(new ILogical[0], panel.GetLogicalChildren());
+ Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
+ Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
}
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
index d3ed077cbf..2dfb30a9f0 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
@@ -2,7 +2,12 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
+using System.ComponentModel;
using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Avalonia.Markup.Xaml.Data;
+using Avalonia.Styling;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
@@ -87,8 +92,111 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Throws(() => target.Value = double.NegativeInfinity);
}
+ [Theory]
+ [InlineData(true)]
+ [InlineData(false)]
+ public void SetValue_Should_Not_Cause_StackOverflow(bool useXamlBinding)
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Value = 50
+ };
+
+ Track track = null;
+
+ var target = new TestRange()
+ {
+ Template = new FuncControlTemplate(c =>
+ {
+ track = new Track()
+ {
+ Width = 100,
+ Orientation = Orientation.Horizontal,
+ [~~Track.MinimumProperty] = c[~~RangeBase.MinimumProperty],
+ [~~Track.MaximumProperty] = c[~~RangeBase.MaximumProperty],
+
+ Name = "PART_Track",
+ Thumb = new Thumb()
+ };
+
+ if (useXamlBinding)
+ {
+ track.Bind(Track.ValueProperty, new Binding("Value")
+ {
+ Mode = BindingMode.TwoWay,
+ Source = c,
+ Priority = BindingPriority.Style
+ });
+ }
+ else
+ {
+ track[~~Track.ValueProperty] = c[~~RangeBase.ValueProperty];
+ }
+
+ return track;
+ }),
+ Minimum = 0,
+ Maximum = 100,
+ DataContext = viewModel
+ };
+
+ target.Bind(TestRange.ValueProperty, new Binding("Value") { Mode = BindingMode.TwoWay });
+
+ target.ApplyTemplate();
+ track.Measure(new Size(100, 0));
+ track.Arrange(new Rect(0, 0, 100, 0));
+
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+
+ // Issues #855 and #824 were causing a StackOverflowException at this point.
+ target.Value = 51.001;
+
+ Assert.Equal(2, viewModel.SetterInvokedCount);
+
+ double expected = 51;
+
+ Assert.Equal(expected, viewModel.Value);
+ Assert.Equal(expected, target.Value);
+ Assert.Equal(expected, track.Value);
+ }
+
private class TestRange : RangeBase
{
}
+
+ private class TestStackOverflowViewModel : INotifyPropertyChanged
+ {
+ public int SetterInvokedCount { get; private set; }
+
+ public const int MaxInvokedCount = 1000;
+
+ private double _value;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public double Value
+ {
+ get { return _value; }
+ set
+ {
+ if (_value != value)
+ {
+ SetterInvokedCount++;
+ if (SetterInvokedCount < MaxInvokedCount)
+ {
+ _value = (int)value;
+ if (_value > 75) _value = 75;
+ if (_value < 25) _value = 25;
+ }
+ else
+ {
+ _value = value;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+ }
+ }
+ }
+ }
}
}
\ No newline at end of file
diff --git a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
index be3c34ac2e..cd71717619 100644
--- a/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
@@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new Panel
{
- Children = new Controls
+ Children =
{
new TextBlock(),
new Border(),
@@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new Panel
{
- Children = new Controls
+ Children =
{
new TextBlock(),
new Border(),
@@ -124,7 +124,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
Child = new Panel
{
- Children = new Controls
+ Children =
{
new TextBlock(),
new Border(),
@@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{
return new StackPanel
{
- Children = new Controls
+ Children =
{
new TextBlock
{
diff --git a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
index 8f815a85c9..68dcfcb770 100644
--- a/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
@@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
new RowDefinition(1, GridUnitType.Star),
new RowDefinition(GridLength.Auto),
},
- Children = new Controls
+ Children =
{
new ScrollContentPresenter
{
diff --git a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
index 56412d732b..ba80cb779a 100644
--- a/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
@@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
{
var target = new StackPanel
{
- Children = new Controls
+ Children =
{
new Border { Height = 20, Width = 120 },
new Border { Height = 30 },
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel
{
Orientation = Orientation.Horizontal,
- Children = new Controls
+ Children =
{
new Border { Width = 20, Height = 120 },
new Border { Width = 30 },
@@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel
{
Gap = 10,
- Children = new Controls
+ Children =
{
new Border { Height = 20, Width = 120 },
new Border { Height = 30 },
@@ -83,7 +83,7 @@ namespace Avalonia.Controls.UnitTests
{
Gap = 10,
Orientation = Orientation.Horizontal,
- Children = new Controls
+ Children =
{
new Border { Width = 20, Height = 120 },
new Border { Width = 30 },
@@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel
{
Height = 60,
- Children = new Controls
+ Children =
{
new Border { Height = 20, Width = 120 },
new Border { Height = 30 },
@@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests
{
Width = 60,
Orientation = Orientation.Horizontal,
- Children = new Controls
+ Children =
{
new Border { Width = 20, Height = 120 },
new Border { Width = 30 },
diff --git a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
index 638d773cd0..67b224d6a0 100644
--- a/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TabControlTests.cs
@@ -272,7 +272,7 @@ namespace Avalonia.Controls.UnitTests
{
return new StackPanel
{
- Children = new Controls
+ Children =
{
new TabStrip
{
diff --git a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
index 17eccc96bc..625d9eb26e 100644
--- a/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
@@ -432,7 +432,7 @@ namespace Avalonia.Controls.UnitTests
{
return new FuncControlTemplate(parent => new Panel
{
- Children = new Controls
+ Children =
{
new ContentPresenter
{
diff --git a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
index 1a46e24b16..cd35627064 100644
--- a/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
@@ -12,12 +12,12 @@ namespace Avalonia.Controls.UnitTests
{
var target = new WrapPanel()
{
- Width = 100,
- Children = new Controls
- {
- new Border { Height = 50, Width = 100 },
- new Border { Height = 50, Width = 100 },
- }
+ Width = 100,
+ Children =
+ {
+ new Border { Height = 50, Width = 100 },
+ new Border { Height = 50, Width = 100 },
+ }
};
target.Measure(Size.Infinity);
@@ -33,12 +33,12 @@ namespace Avalonia.Controls.UnitTests
{
var target = new WrapPanel()
{
- Width = 200,
- Children = new Controls
- {
- new Border { Height = 50, Width = 100 },
- new Border { Height = 50, Width = 100 },
- }
+ Width = 200,
+ Children =
+ {
+ new Border { Height = 50, Width = 100 },
+ new Border { Height = 50, Width = 100 },
+ }
};
target.Measure(Size.Infinity);
@@ -54,13 +54,13 @@ namespace Avalonia.Controls.UnitTests
{
var target = new WrapPanel()
{
- Orientation = Orientation.Vertical,
- Height = 120,
- Children = new Controls
- {
- new Border { Height = 50, Width = 100 },
- new Border { Height = 50, Width = 100 },
- }
+ Orientation = Orientation.Vertical,
+ Height = 120,
+ Children =
+ {
+ new Border { Height = 50, Width = 100 },
+ new Border { Height = 50, Width = 100 },
+ }
};
target.Measure(Size.Infinity);
@@ -76,13 +76,13 @@ namespace Avalonia.Controls.UnitTests
{
var target = new WrapPanel()
{
- Orientation = Orientation.Vertical,
- Height = 60,
- Children = new Controls
- {
- new Border { Height = 50, Width = 100 },
- new Border { Height = 50, Width = 100 },
- }
+ Orientation = Orientation.Vertical,
+ Height = 60,
+ Children =
+ {
+ new Border { Height = 50, Width = 100 },
+ new Border { Height = 50, Width = 100 },
+ }
};
target.Measure(Size.Infinity);
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
index 2b9b0baf01..b81b724e2a 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
@@ -6,8 +6,6 @@ using Xunit;
namespace Avalonia.Input.UnitTests
{
- using Controls = Controls.Controls;
-
public class KeyboardNavigationTests_Arrows
{
[Fact]
@@ -18,12 +16,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -33,7 +31,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -56,12 +54,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -71,7 +69,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -94,12 +92,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -123,16 +121,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -144,7 +142,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -167,7 +165,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
}
@@ -187,17 +185,17 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -209,7 +207,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -232,12 +230,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -246,7 +244,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -269,12 +267,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -283,7 +281,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -306,12 +304,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -321,7 +319,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -343,12 +341,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -358,7 +356,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -380,12 +378,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -395,7 +393,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -418,12 +416,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }),
@@ -433,7 +431,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -456,12 +454,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -471,7 +469,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -495,12 +493,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -524,16 +522,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -545,7 +543,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -568,16 +566,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -589,7 +587,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -632,12 +630,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }),
@@ -647,7 +645,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -670,12 +668,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -685,7 +683,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -708,12 +706,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }),
@@ -723,7 +721,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -745,12 +743,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -760,7 +758,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -783,7 +781,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(current = new Decorator
{
diff --git a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
index 2cc7a4281b..ad70dcd470 100644
--- a/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
+++ b/tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
@@ -6,8 +6,6 @@ using Xunit;
namespace Avalonia.Input.UnitTests
{
- using Controls = Controls.Controls;
-
public class KeyboardNavigationTests_Tab
{
[Fact]
@@ -18,11 +16,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -31,7 +29,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -54,11 +52,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -67,7 +65,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -90,11 +88,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -104,11 +102,11 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -133,11 +131,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -161,15 +159,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -180,7 +178,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -202,7 +200,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
}
@@ -221,15 +219,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -240,7 +238,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -263,12 +261,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -277,7 +275,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -300,12 +298,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -314,7 +312,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -337,12 +335,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -351,7 +349,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -373,12 +371,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -387,7 +385,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -410,12 +408,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -424,7 +422,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -448,12 +446,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
(container = new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }),
@@ -462,7 +460,7 @@ namespace Avalonia.Input.UnitTests
}),
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -487,12 +485,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }),
@@ -501,7 +499,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -525,12 +523,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
(container = new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -539,7 +537,7 @@ namespace Avalonia.Input.UnitTests
}),
new StackPanel
{
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -564,11 +562,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }),
@@ -577,7 +575,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -600,11 +598,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -613,7 +611,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -636,11 +634,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -664,15 +662,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -683,7 +681,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -706,15 +704,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -725,7 +723,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -767,12 +765,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }),
@@ -781,7 +779,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -804,12 +802,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -818,7 +816,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -841,12 +839,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }),
@@ -855,7 +853,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -877,12 +875,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -891,7 +889,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
new Button { Name = "Button5" },
@@ -914,11 +912,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
new Button { Name = "Button2" },
@@ -928,7 +926,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
- Children = new Controls
+ Children =
{
new Button { Name = "Button4" },
(current = new Button { Name = "Button5" }),
@@ -952,12 +950,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
(container = new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
- Children = new Controls
+ Children =
{
new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }),
@@ -966,7 +964,7 @@ namespace Avalonia.Input.UnitTests
}),
new StackPanel
{
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -991,12 +989,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
- Children = new Controls
+ Children =
{
new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
- Children = new Controls
+ Children =
{
(next = new Button { Name = "Button1" }),
new Button { Name = "Button2" },
@@ -1005,7 +1003,7 @@ namespace Avalonia.Input.UnitTests
},
new StackPanel
{
- Children = new Controls
+ Children =
{
(current = new Button { Name = "Button4" }),
new Button { Name = "Button5" },
@@ -1028,7 +1026,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel
{
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
- Children = new Controls
+ Children =
{
(current = new Decorator
{
diff --git a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
index 361e7678be..4fce3fec0e 100644
--- a/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
+++ b/tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
@@ -275,10 +275,10 @@ namespace Avalonia.Layout.UnitTests
{
Child = panel = new StackPanel
{
- Children = new Controls.Controls
- {
- (border = new Border())
- }
+ Children =
+ {
+ (border = new Border())
+ }
}
};
diff --git a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs
index a5414f1e8c..b3e983036d 100644
--- a/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs
+++ b/tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs
@@ -23,7 +23,7 @@ namespace Avalonia.Markup.UnitTests
{
Child = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }),
@@ -49,7 +49,7 @@ namespace Avalonia.Markup.UnitTests
{
Child = (panel = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(relativeTo = new TextBlock
{
@@ -84,7 +84,7 @@ namespace Avalonia.Markup.UnitTests
{
Child = panel = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }),
@@ -114,7 +114,7 @@ namespace Avalonia.Markup.UnitTests
{
Child = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(relativeTo = new TextBlock
{
@@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests
{
Child = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(target2 = new TextBlock { Name = "target" }),
}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index eb6dc8b5e5..a3e4ad1418 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -21,6 +21,7 @@
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
index 230e61f300..71c5385c23 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
@@ -13,6 +13,7 @@ using Moq;
using Xunit;
using System.ComponentModel;
using System.Runtime.CompilerServices;
+using Avalonia.UnitTests;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -337,6 +338,167 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", target.Content);
}
+ [Fact]
+ public void StyledProperty_SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Value = 50
+ };
+
+ var target = new StyledPropertyClass();
+
+ target.Bind(StyledPropertyClass.DoubleValueProperty,
+ new Binding("Value") { Mode = BindingMode.TwoWay, Source = viewModel });
+
+ var child = new StyledPropertyClass();
+
+ child.Bind(StyledPropertyClass.DoubleValueProperty,
+ new Binding("DoubleValue")
+ {
+ Mode = BindingMode.TwoWay,
+ Source = target
+ });
+
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+
+ //here in real life stack overflow exception is thrown issue #855 and #824
+ target.DoubleValue = 51.001;
+
+ Assert.Equal(2, viewModel.SetterInvokedCount);
+
+ double expected = 51;
+
+ Assert.Equal(expected, viewModel.Value);
+ Assert.Equal(expected, target.DoubleValue);
+ Assert.Equal(expected, child.DoubleValue);
+ }
+
+ [Fact]
+ public void SetValue_Should_Not_Cause_StackOverflow_And_Have_Correct_Values()
+ {
+ var viewModel = new TestStackOverflowViewModel()
+ {
+ Value = 50
+ };
+
+ var target = new DirectPropertyClass();
+
+ target.Bind(DirectPropertyClass.DoubleValueProperty, new Binding("Value")
+ {
+ Mode = BindingMode.TwoWay,
+ Source = viewModel
+ });
+
+ var child = new DirectPropertyClass();
+
+ child.Bind(DirectPropertyClass.DoubleValueProperty,
+ new Binding("DoubleValue")
+ {
+ Mode = BindingMode.TwoWay,
+ Source = target
+ });
+
+ Assert.Equal(1, viewModel.SetterInvokedCount);
+
+ //here in real life stack overflow exception is thrown issue #855 and #824
+ target.DoubleValue = 51.001;
+
+ Assert.Equal(2, viewModel.SetterInvokedCount);
+
+ double expected = 51;
+
+ Assert.Equal(expected, viewModel.Value);
+ Assert.Equal(expected, target.DoubleValue);
+ Assert.Equal(expected, child.DoubleValue);
+ }
+
+ private class StyledPropertyClass : AvaloniaObject
+ {
+ public static readonly StyledProperty DoubleValueProperty =
+ AvaloniaProperty.Register(nameof(DoubleValue));
+
+ public double DoubleValue
+ {
+ get { return GetValue(DoubleValueProperty); }
+ set { SetValue(DoubleValueProperty, value); }
+ }
+ }
+
+ private class DirectPropertyClass : AvaloniaObject
+ {
+ public static readonly DirectProperty DoubleValueProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(DoubleValue),
+ o => o.DoubleValue,
+ (o, v) => o.DoubleValue = v);
+
+ private double _doubleValue;
+ public double DoubleValue
+ {
+ get { return _doubleValue; }
+ set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
+ }
+ }
+
+ private class TestStackOverflowViewModel : INotifyPropertyChanged
+ {
+ public int SetterInvokedCount { get; private set; }
+
+ public const int MaxInvokedCount = 1000;
+
+ private double _value;
+
+ public event PropertyChangedEventHandler PropertyChanged;
+
+ public double Value
+ {
+ get { return _value; }
+ set
+ {
+ if (_value != value)
+ {
+ SetterInvokedCount++;
+ if (SetterInvokedCount < MaxInvokedCount)
+ {
+ _value = (int)value;
+ if (_value > 75) _value = 75;
+ if (_value < 25) _value = 25;
+ }
+ else
+ {
+ _value = value;
+ }
+
+ PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Value)));
+ }
+ }
+ }
+ }
+
+
+ [Fact]
+ public void Binding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var textBlock = window.FindControl("textBlock");
+
+ window.DataContext = "foo";
+ window.ApplyTemplate();
+
+ Assert.Equal("foo", textBlock.Text);
+ }
+ }
+
private class TwoWayBindingTest : Control
{
public static readonly StyledProperty TwoWayProperty =
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
index 40a4a6f0b3..d582964987 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
@@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{
Child = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
new TextBlock
{
@@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{
Child = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(source = new TextBlock
{
@@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{
Child = stackPanel = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(target = new TextBlock
{
@@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{
Child = stackPanel = new StackPanel
{
- Children = new Controls.Controls
+ Children =
{
(target = new ContentControl
{
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
index 197afe46ee..ccb13039f1 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_TemplatedParent.cs
@@ -11,6 +11,9 @@ using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling;
using Xunit;
using System.Reactive.Disposables;
+using Avalonia.UnitTests;
+using Avalonia.VisualTree;
+using System.Linq;
namespace Avalonia.Markup.Xaml.UnitTests.Data
{
@@ -56,6 +59,35 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
BindingPriority.TemplatedParent));
}
+ [Fact]
+ public void TemplateBinding_With_Null_Path_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+ button.ApplyTemplate();
+
+ var textBlock = (TextBlock)button.GetVisualChildren().Single();
+ Assert.Equal("Avalonia.Controls.Button", textBlock.Text);
+ }
+ }
+
private Mock CreateTarget(
ITemplatedControl templatedParent = null,
string text = null)
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs
index 9125e1e643..095aae7742 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BindingTests_RelativeSource.cs
@@ -3,6 +3,7 @@
using Avalonia.Controls;
using Avalonia.UnitTests;
+using System;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
@@ -77,6 +78,77 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
+ [Fact]
+ public void Binding_To_First_Ancestor_Without_AncestorType_Throws_Exception()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ Assert.Throws( () => loader.Load(xaml));
+ }
+ }
+
+ [Fact]
+ public void Binding_To_First_Ancestor_With_Shorthand_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("border2", button.Content);
+ }
+ }
+
+ [Fact]
+ public void Binding_To_First_Ancestor_With_Shorthand_Uses_LogicalTree()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var contentControl = window.FindControl("contentControl");
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("contentControl", button.Content);
+ }
+ }
+
[Fact]
public void Binding_To_Second_Ancestor_Works()
{
@@ -102,6 +174,108 @@ namespace Avalonia.Markup.Xaml.UnitTests.Xaml
}
}
+ [Fact]
+ public void Binding_To_Second_Ancestor_With_Shorthand_Uses_LogicalTree()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var contentControl1 = window.FindControl("contentControl1");
+ var contentControl2 = window.FindControl("contentControl2");
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("contentControl1", button.Content);
+ }
+ }
+
+ [Fact]
+ public void Binding_To_Ancestor_Of_Type_With_Shorthand_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("border2", button.Content);
+ }
+ }
+
+ [Fact]
+ public void Binding_To_Second_Ancestor_With_Shorthand_And_Type_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("border1", button.Content);
+ }
+ }
+
+ [Fact]
+ public void Binding_To_Second_Ancestor_With_Shorthand_Works()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+ ";
+ var loader = new AvaloniaXamlLoader();
+ var window = (Window)loader.Load(xaml);
+ var button = window.FindControl("button");
+
+ window.ApplyTemplate();
+
+ Assert.Equal("border1", button.Content);
+ }
+ }
+
[Fact]
public void Binding_To_Ancestor_With_Namespace_Works()
{
diff --git a/tests/Avalonia.RenderTests/GeometryClippingTests.cs b/tests/Avalonia.RenderTests/GeometryClippingTests.cs
index 01af638e6d..7a01c308a4 100644
--- a/tests/Avalonia.RenderTests/GeometryClippingTests.cs
+++ b/tests/Avalonia.RenderTests/GeometryClippingTests.cs
@@ -29,7 +29,7 @@ namespace Avalonia.Direct2D1.RenderTests
Clip = StreamGeometry.Parse("F1 M 0,0 H 76 V 76 Z"),
Width = 76,
Height = 76,
- Children = new Avalonia.Controls.Controls
+ Children =
{
new Path
{
diff --git a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
index 60589f6a00..099b022862 100644
--- a/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
+++ b/tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
@@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{
return new Panel
{
- Children = new Avalonia.Controls.Controls
+ Children =
{
new Image
{
diff --git a/tests/Avalonia.RenderTests/OpacityMaskTests.cs b/tests/Avalonia.RenderTests/OpacityMaskTests.cs
index b53d9a70b1..1934654c26 100644
--- a/tests/Avalonia.RenderTests/OpacityMaskTests.cs
+++ b/tests/Avalonia.RenderTests/OpacityMaskTests.cs
@@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests
},
Width = 76,
Height = 76,
- Children = new Avalonia.Controls.Controls
+ Children =
{
new Path
{
diff --git a/tests/Avalonia.RenderTests/SVGPathTests.cs b/tests/Avalonia.RenderTests/SVGPathTests.cs
index d1ed0ae1cf..3d0275c072 100644
--- a/tests/Avalonia.RenderTests/SVGPathTests.cs
+++ b/tests/Avalonia.RenderTests/SVGPathTests.cs
@@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests
Background = Brushes.Yellow,
Width = 76,
Height = 76,
- Children = new Avalonia.Controls.Controls
+ Children =
{
new Path
{
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
index 352efe5358..d837f2cb2f 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
@@ -135,6 +135,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ throw new NotImplementedException();
+ }
+
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
diff --git a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
index c413904c8f..b75b59c212 100644
--- a/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
+++ b/tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
@@ -165,6 +165,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ throw new NotImplementedException();
+ }
+
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
diff --git a/tests/Avalonia.Styling.UnitTests/TestControlBase.cs b/tests/Avalonia.Styling.UnitTests/TestControlBase.cs
index f57ac836fb..82be755a39 100644
--- a/tests/Avalonia.Styling.UnitTests/TestControlBase.cs
+++ b/tests/Avalonia.Styling.UnitTests/TestControlBase.cs
@@ -59,6 +59,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ throw new NotImplementedException();
+ }
+
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
diff --git a/tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs b/tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
index 07a988f410..03b2f03bf2 100644
--- a/tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
+++ b/tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
@@ -67,6 +67,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
+ public bool IsAnimating(AvaloniaProperty property)
+ {
+ throw new NotImplementedException();
+ }
+
public bool IsSet(AvaloniaProperty property)
{
throw new NotImplementedException();
diff --git a/tests/Avalonia.UnitTests/TestRoot.cs b/tests/Avalonia.UnitTests/TestRoot.cs
index 9ec053f075..dc137e3533 100644
--- a/tests/Avalonia.UnitTests/TestRoot.cs
+++ b/tests/Avalonia.UnitTests/TestRoot.cs
@@ -16,8 +16,6 @@ namespace Avalonia.UnitTests
public class TestRoot : Decorator, IFocusScope, ILayoutRoot, IInputRoot, INameScope, IRenderRoot, IStyleRoot
{
private readonly NameScope _nameScope = new NameScope();
- private readonly IRenderTarget _renderTarget = Mock.Of(
- x => x.CreateDrawingContext(It.IsAny()) == Mock.Of());
public TestRoot()
{
@@ -65,7 +63,21 @@ namespace Avalonia.UnitTests
IStyleHost IStyleHost.StylingParent => StylingParent;
- public IRenderTarget CreateRenderTarget() => _renderTarget;
+ public IRenderTarget CreateRenderTarget()
+ {
+ var dc = new Mock();
+ dc.Setup(x => x.CreateLayer(It.IsAny())).Returns(() =>
+ {
+ var layerDc = new Mock();
+ var layer = new Mock();
+ layer.Setup(x => x.CreateDrawingContext(It.IsAny())).Returns(layerDc.Object);
+ return layer.Object;
+ });
+
+ var result = new Mock();
+ result.Setup(x => x.CreateDrawingContext(It.IsAny())).Returns(dc.Object);
+ return result.Object;
+ }
public void Invalidate(Rect rect)
{
diff --git a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
index bcb5250be6..b87e72516b 100644
--- a/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
+++ b/tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100,
Height = 100,
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
(target = new TestControl
{
@@ -47,7 +47,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100,
Height = 100,
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
(target = new TestControl
{
@@ -74,7 +74,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100,
Height = 100,
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
new Canvas
{
@@ -82,7 +82,7 @@ namespace Avalonia.Visuals.UnitTests
Height = 100,
[Canvas.LeftProperty] = 50,
[Canvas.TopProperty] = 50,
- Children = new Controls.Controls
+ Children =
{
(target = new TestControl
{
@@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100,
Height = 100,
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
(target = new TestControl
{
@@ -138,7 +138,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100,
Height = 100,
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
new Border
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
index cec95d4807..c97070a2aa 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Reactive.Subjects;
using Avalonia.Controls;
+using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
@@ -19,28 +21,18 @@ namespace Avalonia.Visuals.UnitTests.Rendering
[Fact]
public void First_Frame_Calls_UpdateScene_On_Dispatcher()
{
- var loop = new Mock();
var root = new TestRoot();
var dispatcher = new Mock();
dispatcher.Setup(x => x.InvokeAsync(It.IsAny(), DispatcherPriority.Render))
.Callback((a, p) => a());
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: MockSceneBuilder(root).Object,
- dispatcher: dispatcher.Object);
+ CreateTargetAndRunFrame(root, dispatcher: dispatcher.Object);
- target.Start();
- RunFrame(loop);
-
-#if !NETCOREAPP1_1 // Delegate.Method is not available in netcoreapp1.1
dispatcher.Verify(x =>
x.InvokeAsync(
It.Is(a => a.Method.Name == "UpdateScene"),
DispatcherPriority.Render));
-#endif
}
[Fact]
@@ -49,15 +41,8 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var loop = new Mock();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
- var dispatcher = new ImmediateDispatcher();
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder.Object,
- dispatcher: dispatcher);
- target.Start();
- RunFrame(loop);
+ CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
sceneBuilder.Verify(x => x.UpdateAll(It.IsAny()));
}
@@ -68,12 +53,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var loop = new Mock();
var root = new TestRoot();
var sceneBuilder = MockSceneBuilder(root);
- var dispatcher = new ImmediateDispatcher();
var target = new DeferredRenderer(
root,
loop.Object,
- sceneBuilder: sceneBuilder.Object,
- dispatcher: dispatcher);
+ sceneBuilder: sceneBuilder.Object);
target.Start();
IgnoreFirstFrame(loop, sceneBuilder);
@@ -127,12 +110,93 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
[Fact]
- public void Frame_Should_Create_Layer_For_Root()
+ public void Should_Push_Opacity_For_Controls_With_Less_Than_1_Opacity()
+ {
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ Opacity = 0.5,
+ }
+ };
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var target = CreateTargetAndRunFrame(root);
+ var context = GetLayerContext(target, root);
+ var animation = new BehaviorSubject(0.5);
+
+ context.Verify(x => x.PushOpacity(0.5), Times.Once);
+ context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+ context.Verify(x => x.PopOpacity(), Times.Once);
+ }
+
+ [Fact]
+ public void Should_Not_Draw_Controls_With_0_Opacity()
+ {
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ Opacity = 0,
+ Child = new Border
+ {
+ Background = Brushes.Green,
+ }
+ }
+ };
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var target = CreateTargetAndRunFrame(root);
+ var context = GetLayerContext(target, root);
+ var animation = new BehaviorSubject(0.5);
+
+ context.Verify(x => x.PushOpacity(0.5), Times.Never);
+ context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Never);
+ context.Verify(x => x.PopOpacity(), Times.Never);
+ }
+
+ [Fact]
+ public void Should_Push_Opacity_Mask()
+ {
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ OpacityMask = Brushes.Green,
+ }
+ };
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var target = CreateTargetAndRunFrame(root);
+ var context = GetLayerContext(target, root);
+ var animation = new BehaviorSubject(0.5);
+
+ context.Verify(x => x.PushOpacityMask(Brushes.Green, new Rect(0, 0, 100, 100)), Times.Once);
+ context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+ context.Verify(x => x.PopOpacityMask(), Times.Once);
+ }
+
+ [Fact]
+ public void Should_Create_Layer_For_Root()
{
var loop = new Mock();
var root = new TestRoot();
var rootLayer = new Mock();
- var dispatcher = new ImmediateDispatcher();
var sceneBuilder = new Mock();
sceneBuilder.Setup(x => x.UpdateAll(It.IsAny()))
@@ -143,23 +207,53 @@ namespace Avalonia.Visuals.UnitTests.Rendering
});
var renderInterface = new Mock();
+ var target = CreateTargetAndRunFrame(root, sceneBuilder: sceneBuilder.Object);
- var target = new DeferredRenderer(
- root,
- loop.Object,
- sceneBuilder: sceneBuilder.Object,
- //layerFactory: layers.Object,
- dispatcher: dispatcher);
+ Assert.Single(target.Layers);
+ }
- target.Start();
+ [Fact]
+ public void Should_Create_And_Delete_Layers_For_Controls_With_Animated_Opacity()
+ {
+ Border border;
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = new Border
+ {
+ Background = Brushes.Red,
+ Child = border = new Border
+ {
+ Background = Brushes.Green,
+ Child = new Canvas(),
+ Opacity = 0.9,
+ }
+ }
+ };
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
+
+ var loop = new Mock();
+ var target = CreateTargetAndRunFrame(root, loop: loop);
+
+ Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
+
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
RunFrame(loop);
- var context = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
- context.Verify(x => x.CreateLayer(root.ClientSize));
+ Assert.Equal(new IVisual[] { root, border }, target.Layers.Select(x => x.LayerRoot));
+
+ animation.OnCompleted();
+ RunFrame(loop);
+
+ Assert.Equal(new[] { root }, target.Layers.Select(x => x.LayerRoot));
}
[Fact]
- public void Should_Create_And_Delete_Layers_For_Transparent_Controls()
+ public void Should_Not_Create_Layer_For_Childless_Control_With_Animated_Opacity()
{
Border border;
var root = new TestRoot
@@ -176,51 +270,96 @@ namespace Avalonia.Visuals.UnitTests.Rendering
}
};
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
root.Measure(Size.Infinity);
root.Arrange(new Rect(root.DesiredSize));
- var rootLayer = CreateLayer();
- var borderLayer = CreateLayer();
- var renderTargetContext = Mock.Get(root.CreateRenderTarget().CreateDrawingContext(null));
- renderTargetContext.SetupSequence(x => x.CreateLayer(It.IsAny()))
- .Returns(rootLayer)
- .Returns(borderLayer);
-
var loop = new Mock();
- var target = new DeferredRenderer(
- root,
- loop.Object,
- dispatcher: new ImmediateDispatcher());
- root.Renderer = target;
+ var target = CreateTargetAndRunFrame(root, loop: loop);
- target.Start();
- RunFrame(loop);
+ Assert.Single(target.Layers);
+ }
+
+ [Fact]
+ public void Should_Not_Push_Opacity_For_Transparent_Layer_Root_Control()
+ {
+ Border border;
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = border = new Border
+ {
+ Background = Brushes.Red,
+ Child = new Canvas(),
+ }
+ };
+
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
- var rootContext = Mock.Get(rootLayer.CreateDrawingContext(null));
- var borderContext = Mock.Get(borderLayer.CreateDrawingContext(null));
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
- rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
- rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
- borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
+ var target = CreateTargetAndRunFrame(root);
+ var context = GetLayerContext(target, border);
- rootContext.ResetCalls();
- borderContext.ResetCalls();
- border.Opacity = 0.5;
- RunFrame(loop);
+ context.Verify(x => x.PushOpacity(0.5), Times.Never);
+ context.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
+ context.Verify(x => x.PopOpacity(), Times.Never);
+ }
+
+ [Fact]
+ public void Should_Draw_Transparent_Layer_With_Correct_Opacity()
+ {
+ Border border;
+ var root = new TestRoot
+ {
+ Width = 100,
+ Height = 100,
+ Child = border = new Border
+ {
+ Background = Brushes.Red,
+ Child = new Canvas(),
+ }
+ };
+
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
+ root.Measure(Size.Infinity);
+ root.Arrange(new Rect(root.DesiredSize));
- rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
- rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Never);
- borderContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
+ var target = CreateTargetAndRunFrame(root);
+ var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null));
+ var borderLayer = target.Layers[border].Bitmap;
- rootContext.ResetCalls();
- borderContext.ResetCalls();
- border.Opacity = 1;
+ context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny(), It.IsAny()));
+ }
+
+ private DeferredRenderer CreateTargetAndRunFrame(
+ TestRoot root,
+ Mock loop = null,
+ ISceneBuilder sceneBuilder = null,
+ IDispatcher dispatcher = null)
+ {
+ loop = loop ?? new Mock();
+ var target = new DeferredRenderer(
+ root,
+ loop.Object,
+ sceneBuilder: sceneBuilder,
+ dispatcher: dispatcher ?? new ImmediateDispatcher());
+ root.Renderer = target;
+ target.Start();
RunFrame(loop);
+ return target;
+ }
- Mock.Get(borderLayer).Verify(x => x.Dispose());
- rootContext.Verify(x => x.FillRectangle(Brushes.Red, new Rect(0, 0, 100, 100), 0), Times.Once);
- rootContext.Verify(x => x.FillRectangle(Brushes.Green, new Rect(0, 0, 100, 100), 0), Times.Once);
- borderContext.Verify(x => x.FillRectangle(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never);
+ private Mock GetLayerContext(DeferredRenderer renderer, IControl layerRoot)
+ {
+ return Mock.Get(renderer.Layers[layerRoot].Bitmap.CreateDrawingContext(null));
}
private void IgnoreFirstFrame(Mock loop, Mock sceneBuilder)
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
index 9e2f1fc293..3a9e45a02b 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
@@ -154,7 +154,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Width = 200,
Height = 200,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Width = 200,
Height = 200,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -255,7 +255,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Height = 200,
Background = Brushes.Red,
ClipToBounds = false,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -303,7 +303,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100,
Height = 200,
Background = Brushes.Red,
- Children = new Controls.Controls
+ Children =
{
new Panel()
{
@@ -312,7 +312,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
(target = new Border()
{
@@ -354,7 +354,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100,
Height = 200,
Background = Brushes.Red,
- Children = new Controls.Controls
+ Children =
{
(target = new Border()
{
@@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Content = new StackPanel()
{
- Children = new Controls.Controls
+ Children =
{
(item1 = new Border()
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
index c8a19a9f46..9a1d8cb59c 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs
@@ -126,7 +126,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Width = 200,
Height = 200,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Width = 200,
Height = 200,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -238,7 +238,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Height = 200,
Background = Brushes.Red,
ClipToBounds = false,
- Children = new Controls.Controls
+ Children =
{
new Border
{
@@ -287,7 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100,
Height = 200,
Background = Brushes.Red,
- Children = new Controls.Controls
+ Children =
{
new Panel()
{
@@ -296,7 +296,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true,
- Children = new Controls.Controls
+ Children =
{
(target = new Border()
{
@@ -339,7 +339,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100,
Height = 200,
Background = Brushes.Red,
- Children = new Controls.Controls
+ Children =
{
(target = new Border()
{
@@ -359,7 +359,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{
Content = new StackPanel()
{
- Children = new Controls.Controls
+ Children =
{
(item1 = new Border()
{
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
index d0f7671956..44b9ebe8ce 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests.cs
@@ -9,6 +9,8 @@ using Xunit;
using Avalonia.Layout;
using Moq;
using Avalonia.Platform;
+using System.Reactive.Subjects;
+using Avalonia.Data;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
@@ -620,13 +622,15 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Margin = new Thickness(0, 10, 0, 0),
Child = border = new Border
{
- Opacity = 0.5,
Background = Brushes.Red,
Child = canvas = new Canvas(),
}
}
};
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
diff --git a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
index ed74f12075..f2d137249a 100644
--- a/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
+++ b/tests/Avalonia.Visuals.UnitTests/Rendering/SceneGraph/SceneBuilderTests_Layers.cs
@@ -7,14 +7,15 @@ using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
using Avalonia.Layout;
-using Avalonia.Rendering;
+using System.Reactive.Subjects;
+using Avalonia.Data;
namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
{
public partial class SceneBuilderTests
{
[Fact]
- public void Control_With_Transparency_Should_Start_New_Layer()
+ public void Control_With_Animated_Opacity_And_Children_Should_Start_New_Layer()
{
using (TestApplication())
{
@@ -31,10 +32,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
- Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
- Child = canvas = new Canvas(),
+ Child = canvas = new Canvas()
}
}
};
@@ -42,6 +42,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService();
layout.ExecuteInitialLayoutPass(tree);
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
@@ -58,7 +61,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Assert.Equal(2, scene.Layers.Count());
Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
- border.Opacity = 1;
+ animation.OnCompleted();
scene = scene.Clone();
sceneBuilder.Update(scene, border);
@@ -80,13 +83,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
}
[Fact]
- public void Control_With_OpacityMask_Should_Start_New_Layer()
+ public void Control_With_Animated_Opacity_And_No_Children_Should_Not_Start_New_Layer()
{
using (TestApplication())
{
Decorator decorator;
Border border;
- Canvas canvas;
var tree = new TestRoot
{
Padding = new Thickness(10),
@@ -97,10 +99,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
- OpacityMask = Brushes.Red,
Background = Brushes.Red,
- Padding = new Thickness(12),
- Child = canvas = new Canvas(),
}
}
};
@@ -108,45 +107,19 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService();
layout.ExecuteInitialLayoutPass(tree);
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
- var rootNode = (VisualNode)scene.Root;
- var borderNode = (VisualNode)scene.FindNode(border);
- var canvasNode = (VisualNode)scene.FindNode(canvas);
-
- Assert.Same(tree, rootNode.LayerRoot);
- Assert.Same(border, borderNode.LayerRoot);
- Assert.Same(border, canvasNode.LayerRoot);
- Assert.Equal(Brushes.Red, scene.Layers[border].OpacityMask);
-
- Assert.Equal(2, scene.Layers.Count());
- Assert.Empty(scene.Layers.Select(x => x.LayerRoot).Except(new IVisual[] { tree, border }));
-
- border.OpacityMask = null;
- scene = scene.Clone();
-
- sceneBuilder.Update(scene, border);
-
- rootNode = (VisualNode)scene.Root;
- borderNode = (VisualNode)scene.FindNode(border);
- canvasNode = (VisualNode)scene.FindNode(canvas);
-
- Assert.Same(tree, rootNode.LayerRoot);
- Assert.Same(tree, borderNode.LayerRoot);
- Assert.Same(tree, canvasNode.LayerRoot);
Assert.Single(scene.Layers);
-
- var rootDirty = scene.Layers[tree].Dirty;
-
- Assert.Single(rootDirty);
- Assert.Equal(new Rect(21, 21, 58, 78), rootDirty.Single());
}
}
[Fact]
- public void Removing_Transparent_Control_Should_Remove_Layers()
+ public void Removing_Control_With_Animated_Opacity_Should_Remove_Layers()
{
using (TestApplication())
{
@@ -163,13 +136,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
- Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas
{
- Opacity = 0.75,
- },
+ Children = { new TextBlock() },
+ }
}
}
};
@@ -177,6 +149,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService();
layout.ExecuteInitialLayoutPass(tree);
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+ canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
@@ -210,13 +186,12 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Padding = new Thickness(11),
Child = border = new Border
{
- Opacity = 0.5,
Background = Brushes.Red,
Padding = new Thickness(12),
Child = canvas = new Canvas
{
- Opacity = 0.75,
- },
+ Children = { new TextBlock() },
+ }
}
}
};
@@ -224,6 +199,10 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService();
layout.ExecuteInitialLayoutPass(tree);
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+ canvas.Bind(Canvas.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);
@@ -256,6 +235,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
Child = border = new Border
{
Opacity = 0.5,
+ Child = new Canvas(),
}
}
};
@@ -263,6 +243,9 @@ namespace Avalonia.Visuals.UnitTests.Rendering.SceneGraph
var layout = AvaloniaLocator.Current.GetService();
layout.ExecuteInitialLayoutPass(tree);
+ var animation = new BehaviorSubject(0.5);
+ border.Bind(Border.OpacityProperty, animation, BindingPriority.Animation);
+
var scene = new Scene(tree);
var sceneBuilder = new SceneBuilder();
sceneBuilder.UpdateAll(scene);