Browse Source

Merge branch 'master' into fixes/1247-data-validation-popup-crash

pull/1295/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
0193437e79
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  2. 138
      src/Avalonia.Base/AvaloniaObject.cs
  3. 24
      src/Avalonia.Base/AvaloniaObjectExtensions.cs
  4. 46
      src/Avalonia.Base/PriorityBindingEntry.cs
  5. 51
      src/Avalonia.Base/PriorityValue.cs
  6. 16
      src/Avalonia.Base/Reactive/AnonymousSubject`1.cs
  7. 49
      src/Avalonia.Base/Reactive/AnonymousSubject`2.cs
  8. 156
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  9. 1
      src/Avalonia.Controls/ContentControl.cs
  10. 4
      src/Avalonia.Controls/IPanel.cs
  11. 32
      src/Avalonia.Controls/Panel.cs
  12. 15
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  13. 4
      src/Avalonia.Controls/Primitives/RangeBase.cs
  14. 70
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  15. 12
      src/Avalonia.Controls/Primitives/Track.cs
  16. 2
      src/Avalonia.Themes.Default/Button.xaml
  17. 2
      src/Avalonia.Themes.Default/CalendarButton.xaml
  18. 2
      src/Avalonia.Themes.Default/CalendarDayButton.xaml
  19. 2
      src/Avalonia.Themes.Default/CheckBox.xaml
  20. 2
      src/Avalonia.Themes.Default/ContentControl.xaml
  21. 2
      src/Avalonia.Themes.Default/DropDownItem.xaml
  22. 2
      src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml
  23. 8
      src/Avalonia.Themes.Default/Expander.xaml
  24. 2
      src/Avalonia.Themes.Default/LayoutTransformControl.xaml
  25. 2
      src/Avalonia.Themes.Default/ListBoxItem.xaml
  26. 2
      src/Avalonia.Themes.Default/PopupRoot.xaml
  27. 2
      src/Avalonia.Themes.Default/RadioButton.xaml
  28. 2
      src/Avalonia.Themes.Default/RepeatButton.xaml
  29. 2
      src/Avalonia.Themes.Default/TabStripItem.xaml
  30. 2
      src/Avalonia.Themes.Default/ToggleButton.xaml
  31. 2
      src/Avalonia.Themes.Default/ToolTip.xaml
  32. 2
      src/Avalonia.Themes.Default/Window.xaml
  33. 99
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Binding.cs
  34. 129
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs
  35. 1
      tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj
  36. 43
      tests/Avalonia.Benchmarks/Base/Properties.cs
  37. 58
      tests/Avalonia.Controls.UnitTests/ContentControlTests.cs
  38. 4
      tests/Avalonia.Controls.UnitTests/DockPanelTests.cs
  39. 2
      tests/Avalonia.Controls.UnitTests/DropDownTests.cs
  40. 84
      tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs
  41. 2
      tests/Avalonia.Controls.UnitTests/GridTests.cs
  42. 71
      tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
  43. 45
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  44. 108
      tests/Avalonia.Controls.UnitTests/Primitives/RangeBaseTests.cs
  45. 8
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  46. 26
      tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs
  47. 2
      tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs
  48. 12
      tests/Avalonia.Controls.UnitTests/StackPanelTests.cs
  49. 2
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  50. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs
  51. 52
      tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs
  52. 124
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs
  53. 168
      tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs
  54. 8
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  55. 10
      tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs
  56. 1
      tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
  57. 139
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs
  58. 8
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs
  59. 2
      tests/Avalonia.RenderTests/GeometryClippingTests.cs
  60. 2
      tests/Avalonia.RenderTests/Media/VisualBrushTests.cs
  61. 2
      tests/Avalonia.RenderTests/OpacityMaskTests.cs
  62. 2
      tests/Avalonia.RenderTests/SVGPathTests.cs
  63. 12
      tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs
  64. 14
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs
  65. 14
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

2
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -56,7 +56,7 @@ namespace Avalonia.AndroidTestApplication
{ {
Margin = new Thickness(30), Margin = new Thickness(30),
Background = Brushes.Yellow, Background = Brushes.Yellow,
Children = new Avalonia.Controls.Controls Children =
{ {
new TextBlock new TextBlock
{ {

138
src/Avalonia.Base/AvaloniaObject.cs

@ -51,6 +51,21 @@ namespace Avalonia
/// </summary> /// </summary>
private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged; private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
private DeferredSetter<AvaloniaProperty, object> _directDeferredSetter;
/// <summary>
/// Delayed setter helper for direct properties. Used to fix #855.
/// </summary>
private DeferredSetter<AvaloniaProperty, object> DirectPropertyDeferredSetter
{
get
{
return _directDeferredSetter ??
(_directDeferredSetter = new DeferredSetter<AvaloniaProperty, object>());
}
}
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="AvaloniaObject"/> class. /// Initializes a new instance of the <see cref="AvaloniaObject"/> class.
/// </summary> /// </summary>
@ -324,9 +339,6 @@ namespace Avalonia
var description = GetDescription(source); var description = GetDescription(source);
var scheduler = AvaloniaLocator.Current.GetService<IScheduler>() ?? ImmediateScheduler.Instance;
source = source.ObserveOn(scheduler);
if (property.IsDirect) if (property.IsDirect)
{ {
if (property.IsReadOnly) if (property.IsReadOnly)
@ -552,6 +564,45 @@ namespace Avalonia
} }
} }
/// <summary>
/// A callback type for encapsulating complex logic for setting direct properties.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="value">The value to which to set the property.</param>
/// <param name="field">The backing field for the property.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
protected delegate void SetAndRaiseCallback<T>(T value, ref T field, Action<Action> notifyWrapper);
/// <summary>
/// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed.
/// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="setterCallback">A callback called to actually set the value to the backing field.</param>
/// <param name="value">The value.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
protected bool SetAndRaise<T>(
AvaloniaProperty<T> property,
ref T field,
SetAndRaiseCallback<T> setterCallback,
T value)
{
Contract.Requires<ArgumentNullException>(setterCallback != null);
return DirectPropertyDeferredSetter.SetAndNotify(
property,
ref field,
(object val, ref T backing, Action<Action> notify) =>
{
setterCallback((T)val, ref backing, notify);
return true;
},
value);
}
/// <summary> /// <summary>
/// Sets the backing field for a direct avalonia property, raising the /// Sets the backing field for a direct avalonia property, raising the
/// <see cref="PropertyChanged"/> event if the value has changed. /// <see cref="PropertyChanged"/> event if the value has changed.
@ -566,17 +617,32 @@ namespace Avalonia
protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value) protected bool SetAndRaise<T>(AvaloniaProperty<T> property, ref T field, T value)
{ {
VerifyAccess(); VerifyAccess();
if (!object.Equals(field, value)) return SetAndRaise(
{ property,
var old = field; ref field,
field = value; (T val, ref T backing, Action<Action> notifyWrapper)
RaisePropertyChanged(property, old, value, BindingPriority.LocalValue); => SetAndRaiseCore(property, ref backing, val, notifyWrapper),
return true; value);
} }
else
{ /// <summary>
return false; /// Default assignment logic for SetAndRaise.
} /// </summary>
/// <typeparam name="T">The type of the property.</typeparam>
/// <param name="property">The property.</param>
/// <param name="field">The backing field.</param>
/// <param name="value">The value.</param>
/// <param name="notifyWrapper">A wrapper for the property-changed notification.</param>
/// <returns>
/// True if the value changed, otherwise false.
/// </returns>
private bool SetAndRaiseCore<T>(AvaloniaProperty property, ref T field, T value, Action<Action> notifyWrapper)
{
var old = field;
field = value;
notifyWrapper(() => RaisePropertyChanged(property, old, value, BindingPriority.LocalValue));
return true;
} }
/// <summary> /// <summary>
@ -674,29 +740,41 @@ namespace Avalonia
/// <param name="value">The value.</param> /// <param name="value">The value.</param>
private void SetDirectValue(AvaloniaProperty property, object value) private void SetDirectValue(AvaloniaProperty property, object value)
{ {
var notification = value as BindingNotification; void Set()
if (notification != null)
{ {
notification.LogIfError(this, property); var notification = value as BindingNotification;
value = notification.Value;
}
if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue) if (notification != null)
{ {
var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType()); notification.LogIfError(this, property);
var accessor = (IDirectPropertyAccessor)GetRegistered(property); value = notification.Value;
var finalValue = value == AvaloniaProperty.UnsetValue ? }
metadata.UnsetValue : value;
if (notification == null || notification.ErrorType == BindingErrorType.Error || notification.HasValue)
{
var metadata = (IDirectPropertyMetadata)property.GetMetadata(GetType());
var accessor = (IDirectPropertyAccessor)GetRegistered(property);
var finalValue = value == AvaloniaProperty.UnsetValue ?
metadata.UnsetValue : value;
LogPropertySet(property, value, BindingPriority.LocalValue); LogPropertySet(property, value, BindingPriority.LocalValue);
accessor.SetValue(this, finalValue); accessor.SetValue(this, finalValue);
}
if (notification != null)
{
UpdateDataValidation(property, notification);
}
} }
if (notification != null) if (Dispatcher.UIThread.CheckAccess())
{
Set();
}
else
{ {
UpdateDataValidation(property, notification); Dispatcher.UIThread.InvokeAsync(Set);
} }
} }

24
src/Avalonia.Base/AvaloniaObjectExtensions.cs

@ -138,17 +138,9 @@ namespace Avalonia
AvaloniaProperty property, AvaloniaProperty property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the return Subject.Create<object>(
// AnonymousSubject classes and use Subject.Create<T>. Observer.Create<object>(x => o.SetValue(property, x, priority)),
var output = new Subject<object>();
var result = new AnonymousSubject<object>(
Observer.Create<object>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
o.GetObservable(property)); o.GetObservable(property));
o.Bind(property, output, priority);
return result;
} }
/// <summary> /// <summary>
@ -169,17 +161,9 @@ namespace Avalonia
AvaloniaProperty<T> property, AvaloniaProperty<T> property,
BindingPriority priority = BindingPriority.LocalValue) BindingPriority priority = BindingPriority.LocalValue)
{ {
// TODO: Subject.Create<T> is not yet in stable Rx : once it is, remove the return Subject.Create<T>(
// AnonymousSubject classes from this file and use Subject.Create<T>. Observer.Create<T>(x => o.SetValue(property, x, priority)),
var output = new Subject<T>();
var result = new AnonymousSubject<T>(
Observer.Create<T>(
x => output.OnNext(x),
e => output.OnError(e),
() => output.OnCompleted()),
o.GetObservable(property)); o.GetObservable(property));
o.Bind(property, output, priority);
return result;
} }
/// <summary> /// <summary>

46
src/Avalonia.Base/PriorityBindingEntry.cs

@ -3,6 +3,7 @@
using System; using System;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Threading;
namespace Avalonia namespace Avalonia
{ {
@ -92,33 +93,50 @@ namespace Avalonia
private void ValueChanged(object value) private void ValueChanged(object value)
{ {
_owner.Owner.Owner?.VerifyAccess(); void Signal()
var notification = value as BindingNotification;
if (notification != null)
{ {
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error) var notification = value as BindingNotification;
if (notification != null)
{ {
Value = notification.Value; if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
_owner.Changed(this); {
Value = notification.Value;
_owner.Changed(this);
}
if (notification.ErrorType != BindingErrorType.None)
{
_owner.Error(this, notification);
}
} }
else
if (notification.ErrorType != BindingErrorType.None)
{ {
_owner.Error(this, notification); Value = value;
_owner.Changed(this);
} }
} }
if (Dispatcher.UIThread.CheckAccess())
{
Signal();
}
else else
{ {
Value = value; Dispatcher.UIThread.InvokeAsync(Signal);
_owner.Changed(this);
} }
} }
private void Completed() private void Completed()
{ {
_owner.Completed(this); if (Dispatcher.UIThread.CheckAccess())
{
_owner.Completed(this);
}
else
{
Dispatcher.UIThread.InvokeAsync(() => _owner.Completed(this));
}
} }
} }
} }

51
src/Avalonia.Base/PriorityValue.cs

@ -28,8 +28,10 @@ namespace Avalonia
{ {
private readonly Type _valueType; private readonly Type _valueType;
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>(); private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private object _value;
private readonly Func<object, object> _validate; private readonly Func<object, object> _validate;
private static readonly DeferredSetter<PriorityValue, (object value, int priority)> delayedSetter = new DeferredSetter<PriorityValue, (object, int)>();
private (object value, int priority) _value;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="PriorityValue"/> class. /// Initializes a new instance of the <see cref="PriorityValue"/> class.
@ -47,8 +49,7 @@ namespace Avalonia
Owner = owner; Owner = owner;
Property = property; Property = property;
_valueType = valueType; _valueType = valueType;
_value = AvaloniaProperty.UnsetValue; _value = (AvaloniaProperty.UnsetValue, int.MaxValue);
ValuePriority = int.MaxValue;
_validate = validate; _validate = validate;
} }
@ -77,16 +78,12 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets the current value. /// Gets the current value.
/// </summary> /// </summary>
public object Value => _value; public object Value => _value.value;
/// <summary> /// <summary>
/// Gets the priority of the binding that is currently active. /// Gets the priority of the binding that is currently active.
/// </summary> /// </summary>
public int ValuePriority public int ValuePriority => _value.priority;
{
get;
private set;
}
/// <summary> /// <summary>
/// Adds a new binding. /// Adds a new binding.
@ -246,25 +243,36 @@ namespace Avalonia
/// <param name="priority">The priority level that the value came from.</param> /// <param name="priority">The priority level that the value came from.</param>
private void UpdateValue(object value, int priority) 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<Action> notify)
{
var val = update.value;
var notification = val as BindingNotification;
object castValue; object castValue;
if (notification != null) 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) if (_validate != null && castValue != AvaloniaProperty.UnsetValue)
{ {
castValue = _validate(castValue); castValue = _validate(castValue);
} }
ValuePriority = priority; backing = (castValue, update.priority);
_value = castValue;
if (notification?.HasValue == true) if (notification?.HasValue == true)
{ {
@ -273,7 +281,7 @@ namespace Avalonia
if (notification == null || notification.HasValue) if (notification == null || notification.HasValue)
{ {
Owner?.Changed(this, old, _value); notify(() => Owner?.Changed(this, old, Value));
} }
if (notification != null) if (notification != null)
@ -284,14 +292,15 @@ namespace Avalonia
else else
{ {
Logger.Error( Logger.Error(
LogArea.Binding, LogArea.Binding,
Owner, Owner,
"Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})", "Binding produced invalid value for {$Property} ({$PropertyType}): {$Value} ({$ValueType})",
Property.Name, Property.Name,
_valueType, _valueType,
value, val,
value?.GetType()); val?.GetType());
} }
return true;
} }
} }
} }

16
src/Avalonia.Base/Reactive/AnonymousSubject`1.cs

@ -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<T> : AnonymousSubject<T, T>, ISubject<T>
{
public AnonymousSubject(IObserver<T> observer, IObservable<T> observable)
: base(observer, observable)
{
}
}
}

49
src/Avalonia.Base/Reactive/AnonymousSubject`2.cs

@ -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<T, U> : ISubject<T, U>
{
private readonly IObserver<T> _observer;
private readonly IObservable<U> _observable;
public AnonymousSubject(IObserver<T> observer, IObservable<U> 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<U> 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);
}
}
}

156
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
{
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// </summary>
/// <typeparam name="TProperty">The type of the object that represents the property.</typeparam>
/// <typeparam name="TSetRecord">The type of value with which to track the delayed assignment.</typeparam>
class DeferredSetter<TProperty, TSetRecord>
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;
}
}
/// <summary>
/// Information on current setting/notification status of a property.
/// </summary>
private class SettingStatus
{
public bool Notifying { get; set; }
private Queue<TSetRecord> pendingValues;
public Queue<TSetRecord> PendingValues
{
get
{
return pendingValues ?? (pendingValues = new Queue<TSetRecord>());
}
}
}
private readonly ConditionalWeakTable<TProperty, SettingStatus> setRecords = new ConditionalWeakTable<TProperty, SettingStatus>();
/// <summary>
/// Mark the property as currently notifying.
/// </summary>
/// <param name="property">The property to mark as notifying.</param>
/// <returns>Returns a disposable that when disposed, marks the property as done notifying.</returns>
private NotifyDisposable MarkNotifying(TProperty property)
{
Contract.Requires<InvalidOperationException>(!IsNotifying(property));
return new NotifyDisposable(setRecords.GetOrCreateValue(property));
}
/// <summary>
/// Check if the property is currently notifying listeners.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>If the property is currently notifying listeners.</returns>
private bool IsNotifying(TProperty property)
=> setRecords.TryGetValue(property, out var value) && value.Notifying;
/// <summary>
/// Add a pending assignment for the property.
/// </summary>
/// <param name="property">The property.</param>
/// <param name="value">The value to assign.</param>
private void AddPendingSet(TProperty property, TSetRecord value)
{
Contract.Requires<InvalidOperationException>(IsNotifying(property));
setRecords.GetOrCreateValue(property).PendingValues.Enqueue(value);
}
/// <summary>
/// Checks if there are any pending assignments for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>If the property has any pending assignments.</returns>
private bool HasPendingSet(TProperty property)
{
return setRecords.TryGetValue(property, out var status) && status.PendingValues.Count != 0;
}
/// <summary>
/// Gets the first pending assignment for the property.
/// </summary>
/// <param name="property">The property to check.</param>
/// <returns>The first pending assignment for the property.</returns>
private TSetRecord GetFirstPendingSet(TProperty property)
{
return setRecords.GetOrCreateValue(property).PendingValues.Dequeue();
}
public delegate bool SetterDelegate<TValue>(TSetRecord record, ref TValue backing, Action<Action> notifyCallback);
/// <summary>
/// Set the property and notify listeners while ensuring we don't get into a stack overflow as happens with #855 and #824
/// </summary>
/// <param name="property">The property to set.</param>
/// <param name="backing">The backing field for the property</param>
/// <param name="setterCallback">
/// 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.
/// </param>
/// <param name="value">The value to try to set.</param>
public bool SetAndNotify<TValue>(
TProperty property,
ref TValue backing,
SetterDelegate<TValue> setterCallback,
TSetRecord value)
{
Contract.Requires<ArgumentNullException>(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;
}
}
}

1
src/Avalonia.Controls/ContentControl.cs

@ -51,6 +51,7 @@ namespace Avalonia.Controls
/// Gets or sets the content to display. /// Gets or sets the content to display.
/// </summary> /// </summary>
[Content] [Content]
[DependsOn(nameof(ContentTemplate))]
public object Content public object Content
{ {
get { return GetValue(ContentProperty); } get { return GetValue(ContentProperty); }

4
src/Avalonia.Controls/IPanel.cs

@ -9,8 +9,8 @@ namespace Avalonia.Controls
public interface IPanel : IControl public interface IPanel : IControl
{ {
/// <summary> /// <summary>
/// Gets or sets the children of the <see cref="Panel"/>. /// Gets the children of the <see cref="Panel"/>.
/// </summary> /// </summary>
Controls Children { get; set; } Controls Children { get; }
} }
} }

32
src/Avalonia.Controls/Panel.cs

@ -25,8 +25,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty<IBrush> BackgroundProperty = public static readonly StyledProperty<IBrush> BackgroundProperty =
Border.BackgroundProperty.AddOwner<Panel>(); Border.BackgroundProperty.AddOwner<Panel>();
private readonly Controls _children = new Controls();
/// <summary> /// <summary>
/// Initializes static members of the <see cref="Panel"/> class. /// Initializes static members of the <see cref="Panel"/> class.
/// </summary> /// </summary>
@ -40,38 +38,14 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public Panel() public Panel()
{ {
_children.CollectionChanged += ChildrenChanged; Children.CollectionChanged += ChildrenChanged;
} }
/// <summary> /// <summary>
/// Gets or sets the children of the <see cref="Panel"/>. /// Gets the children of the <see cref="Panel"/>.
/// </summary> /// </summary>
/// <remarks>
/// 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.
/// </remarks>
[Content] [Content]
public Controls Children public Controls Children { get; } = new Controls();
{
get
{
return _children;
}
set
{
Contract.Requires<ArgumentNullException>(value != null);
if (_children != value)
{
VisualChildren.Clear();
_children.Clear();
_children.AddRange(value);
}
}
}
/// <summary> /// <summary>
/// Gets or Sets Panel background brush. /// Gets or Sets Panel background brush.

15
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Layout; using Avalonia.Layout;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.VisualTree; using Avalonia.VisualTree;
namespace Avalonia.Controls.Presenters namespace Avalonia.Controls.Presenters
@ -139,6 +140,7 @@ namespace Avalonia.Controls.Presenters
/// <summary> /// <summary>
/// Gets or sets the content to be displayed by the presenter. /// Gets or sets the content to be displayed by the presenter.
/// </summary> /// </summary>
[DependsOn(nameof(ContentTemplate))]
public object Content public object Content
{ {
get { return GetValue(ContentProperty); } get { return GetValue(ContentProperty); }
@ -255,18 +257,9 @@ namespace Avalonia.Controls.Presenters
LogicalChildren.Remove(oldChild); LogicalChildren.Remove(oldChild);
} }
if (newChild.Parent == null) if (newChild.Parent == null && TemplatedParent == null)
{ {
var templatedLogicalParent = TemplatedParent as ILogical; LogicalChildren.Add(newChild);
if (templatedLogicalParent != null)
{
((ISetLogicalParent)newChild).SetParent(templatedLogicalParent);
}
else
{
LogicalChildren.Add(newChild);
}
} }
VisualChildren.Add(newChild); VisualChildren.Add(newChild);

4
src/Avalonia.Controls/Primitives/RangeBase.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using Avalonia.Data;
using Avalonia.Utilities; using Avalonia.Utilities;
namespace Avalonia.Controls.Primitives namespace Avalonia.Controls.Primitives
@ -36,7 +37,8 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.RegisterDirect<RangeBase, double>( AvaloniaProperty.RegisterDirect<RangeBase, double>(
nameof(Value), nameof(Value),
o => o.Value, o => o.Value,
(o, v) => o.Value = v); (o, v) => o.Value = v,
defaultBindingMode: BindingMode.TwoWay);
/// <summary> /// <summary>
/// Defines the <see cref="SmallChange"/> property. /// Defines the <see cref="SmallChange"/> property.

70
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -151,15 +151,23 @@ namespace Avalonia.Controls.Primitives
{ {
if (_updateCount == 0) if (_updateCount == 0)
{ {
var old = SelectedIndex; SetAndRaise(SelectedIndexProperty, ref _selectedIndex, (int val, ref int backing, Action<Action> notifyWrapper) =>
var effective = (value >= 0 && value < Items?.Cast<object>().Count()) ? value : -1;
if (old != effective)
{ {
_selectedIndex = effective; var old = backing;
RaisePropertyChanged(SelectedIndexProperty, old, effective, BindingPriority.LocalValue); var effective = (val >= 0 && val < Items?.Cast<object>().Count()) ? val : -1;
SelectedItem = ElementAt(Items, effective);
} if (old != effective)
{
backing = effective;
notifyWrapper(() =>
RaisePropertyChanged(
SelectedIndexProperty,
old,
effective,
BindingPriority.LocalValue));
SelectedItem = ElementAt(Items, effective);
}
}, value);
} }
else else
{ {
@ -183,31 +191,41 @@ namespace Avalonia.Controls.Primitives
{ {
if (_updateCount == 0) if (_updateCount == 0)
{ {
var old = SelectedItem; SetAndRaise(SelectedItemProperty, ref _selectedItem, (object val, ref object backing, Action<Action> notifyWrapper) =>
var index = IndexOf(Items, value);
var effective = index != -1 ? value : null;
if (!object.Equals(effective, old))
{ {
_selectedItem = effective; var old = backing;
RaisePropertyChanged(SelectedItemProperty, old, effective, BindingPriority.LocalValue); var index = IndexOf(Items, val);
SelectedIndex = index; 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.Clear();
SelectedItems.Add(effective);
_syncingSelectedItems = false;
} }
} }
else if (SelectedItems.Count > 0) }, value);
{
SelectedItems.Clear();
}
}
} }
else else
{ {

12
src/Avalonia.Controls/Primitives/Track.cs

@ -154,7 +154,11 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null) if (increaseButton != null)
{ {
increaseButton.Arrange(new Rect(firstWidth + thumbWidth, 0, remaining - firstWidth, finalSize.Height)); increaseButton.Arrange(new Rect(
firstWidth + thumbWidth,
0,
Math.Max(0, remaining - firstWidth),
finalSize.Height));
} }
} }
else else
@ -185,7 +189,11 @@ namespace Avalonia.Controls.Primitives
if (increaseButton != null) if (increaseButton != null)
{ {
increaseButton.Arrange(new Rect(0, firstHeight + thumbHeight, finalSize.Width, Math.Max(remaining - firstHeight, 0))); increaseButton.Arrange(new Rect(
0,
firstHeight + thumbHeight,
finalSize.Width,
Math.Max(remaining - firstHeight, 0)));
} }
} }

2
src/Avalonia.Themes.Default/Button.xaml

@ -13,8 +13,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/CalendarButton.xaml

@ -29,8 +29,8 @@
<!--Focusable="False"--> <!--Focusable="False"-->
<ContentControl Name="Content" <ContentControl Name="Content"
Foreground="#FF333333" Foreground="#FF333333"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"

2
src/Avalonia.Themes.Default/CalendarDayButton.xaml

@ -30,8 +30,8 @@
Fill="{TemplateBinding Background}"/> Fill="{TemplateBinding Background}"/>
<ContentControl Name="Content" <ContentControl Name="Content"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
FontSize="{TemplateBinding FontSize}" FontSize="{TemplateBinding FontSize}"

2
src/Avalonia.Themes.Default/CheckBox.xaml

@ -31,8 +31,8 @@
</Panel> </Panel>
</Border> </Border>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0" Margin="4,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Grid.Column="1"/> Grid.Column="1"/>

2
src/Avalonia.Themes.Default/ContentControl.xaml

@ -5,8 +5,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/DropDownItem.xaml

@ -10,8 +10,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>

2
src/Avalonia.Themes.Default/EmbeddableControlRoot.xaml

@ -7,8 +7,8 @@
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<AdornerDecorator> <AdornerDecorator>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/> Margin="{TemplateBinding Padding}"/>
</AdornerDecorator> </AdornerDecorator>
</Border> </Border>

8
src/Avalonia.Themes.Default/Expander.xaml

@ -16,8 +16,8 @@
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Row="1" Grid.Row="1"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Grid> </Grid>
@ -34,8 +34,8 @@
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Row="0" Grid.Row="0"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Grid> </Grid>
@ -52,8 +52,8 @@
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Column="1" Grid.Column="1"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Grid> </Grid>
@ -70,8 +70,8 @@
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Grid.Column="0" Grid.Column="0"
IsVisible="{TemplateBinding IsExpanded}" IsVisible="{TemplateBinding IsExpanded}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" /> VerticalAlignment="Stretch" />
</Grid> </Grid>

2
src/Avalonia.Themes.Default/LayoutTransformControl.xaml

@ -5,8 +5,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/ListBoxItem.xaml

@ -7,8 +7,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/PopupRoot.xaml

@ -4,8 +4,8 @@
<ControlTemplate> <ControlTemplate>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/RadioButton.xaml

@ -29,8 +29,8 @@
HorizontalAlignment="Center" HorizontalAlignment="Center"
VerticalAlignment="Center"/> VerticalAlignment="Center"/>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0" Margin="4,0,0,0"
VerticalAlignment="Center" VerticalAlignment="Center"
Grid.Column="1"/> Grid.Column="1"/>

2
src/Avalonia.Themes.Default/RepeatButton.xaml

@ -20,8 +20,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/TabStripItem.xaml

@ -9,8 +9,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/ToggleButton.xaml

@ -13,8 +13,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}" TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"

2
src/Avalonia.Themes.Default/ToolTip.xaml

@ -9,8 +9,8 @@
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}"/> Padding="{TemplateBinding Padding}"/>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

2
src/Avalonia.Themes.Default/Window.xaml

@ -7,8 +7,8 @@
<Border Background="{TemplateBinding Background}"> <Border Background="{TemplateBinding Background}">
<AdornerDecorator> <AdornerDecorator>
<ContentPresenter Name="PART_ContentPresenter" <ContentPresenter Name="PART_ContentPresenter"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}" ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"/> Margin="{TemplateBinding Padding}"/>
</AdornerDecorator> </AdornerDecorator>
</Border> </Border>

99
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.Collections; using System.ComponentModel;
using System.Collections.Generic; using System.Reactive.Concurrency;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using Microsoft.Reactive.Testing; using System.Threading;
using System.Threading.Tasks;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.UnitTests; using Avalonia.Markup.Xaml.Data;
using Xunit;
using System.Threading.Tasks;
using Avalonia.Platform; using Avalonia.Platform;
using System.Threading;
using Moq;
using System.Reactive.Disposables;
using System.Reactive.Concurrency;
using Avalonia.Threading; using Avalonia.Threading;
using Avalonia.UnitTests;
using Avalonia.Diagnostics;
using Microsoft.Reactive.Testing;
using Moq;
using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
{ {
@ -363,7 +363,7 @@ namespace Avalonia.Base.UnitTests
Assert.True(called); Assert.True(called);
} }
} }
[Fact] [Fact]
public async Task Bind_With_Scheduler_Executes_On_Scheduler() public async Task Bind_With_Scheduler_Executes_On_Scheduler()
{ {
@ -387,6 +387,37 @@ 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] [Fact]
public void IsAnimating_On_Property_With_No_Value_Returns_False() public void IsAnimating_On_Property_With_No_Value_Returns_False()
{ {
@ -445,6 +476,15 @@ namespace Avalonia.Base.UnitTests
public static readonly StyledProperty<double> QuxProperty = public static readonly StyledProperty<double> QuxProperty =
AvaloniaProperty.Register<Class1, double>("Qux", 5.6); AvaloniaProperty.Register<Class1, double>("Qux", 5.6);
public static readonly StyledProperty<double> DoubleValueProperty =
AvaloniaProperty.Register<Class1, double>(nameof(DoubleValue));
public double DoubleValue
{
get { return GetValue(DoubleValueProperty); }
set { SetValue(DoubleValueProperty, value); }
}
} }
private class Class2 : Class1 private class Class2 : Class1
@ -471,5 +511,40 @@ namespace Avalonia.Base.UnitTests
return InstancedBinding.OneTime(_source); 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)));
}
}
}
}
} }
} }

129
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Direct.cs

@ -3,11 +3,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Reactive.Subjects; using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Avalonia; using Avalonia;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Logging; using Avalonia.Logging;
using Avalonia.Platform;
using Avalonia.Threading;
using Avalonia.Markup.Xaml.Data;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Moq;
using Xunit; using Xunit;
namespace Avalonia.Base.UnitTests namespace Avalonia.Base.UnitTests
@ -208,7 +215,7 @@ namespace Avalonia.Base.UnitTests
{ {
var target = new Class1(); var target = new Class1();
Assert.Throws<ArgumentException>(() => Assert.Throws<ArgumentException>(() =>
target.SetValue(Class1.BarProperty, "newvalue")); target.SetValue(Class1.BarProperty, "newvalue"));
} }
@ -217,7 +224,7 @@ namespace Avalonia.Base.UnitTests
{ {
var target = new Class1(); var target = new Class1();
Assert.Throws<ArgumentException>(() => Assert.Throws<ArgumentException>(() =>
target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue")); target.SetValue((AvaloniaProperty)Class1.BarProperty, "newvalue"));
} }
@ -227,7 +234,7 @@ namespace Avalonia.Base.UnitTests
var target = new Class1(); var target = new Class1();
var source = new Subject<string>(); var source = new Subject<string>();
Assert.Throws<ArgumentException>(() => Assert.Throws<ArgumentException>(() =>
target.Bind(Class1.BarProperty, source)); target.Bind(Class1.BarProperty, source));
} }
@ -411,6 +418,28 @@ namespace Avalonia.Base.UnitTests
Assert.True(called); Assert.True(called);
} }
[Fact]
public async Task Bind_Executes_On_UIThread()
{
var target = new Class1();
var source = new Subject<object>();
var currentThreadId = Thread.CurrentThread.ManagedThreadId;
var threadingInterfaceMock = new Mock<IPlatformThreadingInterface>();
threadingInterfaceMock.SetupGet(mock => mock.CurrentThreadIsLoopThread)
.Returns(() => Thread.CurrentThread.ManagedThreadId == currentThreadId);
var services = new TestServices(
threadingInterface: threadingInterfaceMock.Object);
using (UnitTestApplication.Start(services))
{
target.Bind(Class1.FooProperty, source);
await Task.Run(() => source.OnNext("foobar"));
}
}
[Fact] [Fact]
public void AddOwner_Should_Inherit_DefaultBindingMode() public void AddOwner_Should_Inherit_DefaultBindingMode()
{ {
@ -439,12 +468,46 @@ namespace Avalonia.Base.UnitTests
Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().DefaultBindingMode); Assert.Equal(BindingMode.OneWayToSource, bar.GetMetadata<Class2>().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 private class Class1 : AvaloniaObject
{ {
public static readonly DirectProperty<Class1, string> FooProperty = public static readonly DirectProperty<Class1, string> FooProperty =
AvaloniaProperty.RegisterDirect<Class1, string>( AvaloniaProperty.RegisterDirect<Class1, string>(
"Foo", "Foo",
o => o.Foo, o => o.Foo,
(o, v) => o.Foo = v, (o, v) => o.Foo = v,
unsetValue: "unset"); unsetValue: "unset");
@ -453,14 +516,21 @@ namespace Avalonia.Base.UnitTests
public static readonly DirectProperty<Class1, int> BazProperty = public static readonly DirectProperty<Class1, int> BazProperty =
AvaloniaProperty.RegisterDirect<Class1, int>( AvaloniaProperty.RegisterDirect<Class1, int>(
"Bar", "Bar",
o => o.Baz, o => o.Baz,
(o,v) => o.Baz = v, (o, v) => o.Baz = v,
unsetValue: -1); unsetValue: -1);
public static readonly DirectProperty<Class1, double> DoubleValueProperty =
AvaloniaProperty.RegisterDirect<Class1, double>(
nameof(DoubleValue),
o => o.DoubleValue,
(o, v) => o.DoubleValue = v);
private string _foo = "initial"; private string _foo = "initial";
private readonly string _bar = "bar"; private readonly string _bar = "bar";
private int _baz = 5; private int _baz = 5;
private double _doubleValue;
public string Foo public string Foo
{ {
@ -478,6 +548,12 @@ namespace Avalonia.Base.UnitTests
get { return _baz; } get { return _baz; }
set { SetAndRaise(BazProperty, ref _baz, value); } set { SetAndRaise(BazProperty, ref _baz, value); }
} }
public double DoubleValue
{
get { return _doubleValue; }
set { SetAndRaise(DoubleValueProperty, ref _doubleValue, value); }
}
} }
private class Class2 : AvaloniaObject private class Class2 : AvaloniaObject
@ -497,5 +573,40 @@ namespace Avalonia.Base.UnitTests
set { SetAndRaise(FooProperty, ref _foo, value); } 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)));
}
}
}
}
} }
} }

1
tests/Avalonia.Benchmarks/Avalonia.Benchmarks.csproj

@ -49,6 +49,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Base\Properties.cs" />
<Compile Include="Layout\Measure.cs" /> <Compile Include="Layout\Measure.cs" />
<Compile Include="Styling\ApplyStyling.cs" /> <Compile Include="Styling\ApplyStyling.cs" />
<Compile Include="Program.cs" /> <Compile Include="Program.cs" />

43
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<int> intBinding = new Subject<int>();
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<int> IntProperty =
AvaloniaProperty.Register<Class1, int>("Int");
}
}
}

58
tests/Avalonia.Controls.UnitTests/ContentControlTests.cs

@ -1,6 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.Linq; using System.Linq;
using Moq; using Moq;
@ -11,6 +12,9 @@ using Avalonia.Styling;
using Avalonia.UnitTests; using Avalonia.UnitTests;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Data;
using System.Collections.Generic;
namespace Avalonia.Controls.UnitTests namespace Avalonia.Controls.UnitTests
{ {
@ -273,6 +277,60 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(target.Presenter.Child.DataContext); Assert.Null(target.Presenter.Child.DataContext);
} }
[Fact]
public void Binding_ContentTemplate_After_Content_Does_Not_Leave_Orpaned_TextBlock()
{
// Test for #1271.
var children = new List<IControl>();
var presenter = new ContentPresenter();
// The content and then the content template property need to be bound with delayed bindings
// as they are in Avalonia.Markup.Xaml.
DelayedBinding.Add(presenter, ContentPresenter.ContentProperty, new Binding("Content")
{
Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
});
DelayedBinding.Add(presenter, ContentPresenter.ContentTemplateProperty, new Binding("ContentTemplate")
{
Priority = BindingPriority.TemplatedParent,
RelativeSource = new RelativeSource(RelativeSourceMode.TemplatedParent),
});
presenter.GetObservable(ContentPresenter.ChildProperty).Subscribe(children.Add);
var target = new ContentControl
{
Template = new FuncControlTemplate<ContentControl>(_ => presenter),
ContentTemplate = new FuncDataTemplate<string>(x => new Canvas()),
Content = "foo",
};
// The control must be rooted.
var root = new TestRoot
{
Child = target,
};
target.ApplyTemplate();
// When the template is applied, the Content property is bound before the ContentTemplate
// property, causing a TextBlock to be created by the default template before ContentTemplate
// is bound.
Assert.Collection(
children,
x => Assert.Null(x),
x => Assert.IsType<TextBlock>(x),
x => Assert.IsType<Canvas>(x));
var textBlock = (TextBlock)children[1];
// The leak in #1271 was caused by the TextBlock's logical parent not being cleared when
// it is replaced by the Canvas.
Assert.Null(textBlock.GetLogicalParent());
}
private FuncControlTemplate GetTemplate() private FuncControlTemplate GetTemplate()
{ {
return new FuncControlTemplate<ContentControl>(parent => return new FuncControlTemplate<ContentControl>(parent =>

4
tests/Avalonia.Controls.UnitTests/DockPanelTests.cs

@ -12,7 +12,7 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new DockPanel 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.Top },
new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom }, new Border { Width = 500, Height = 50, [DockPanel.DockProperty] = Dock.Bottom },
@ -38,7 +38,7 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new DockPanel 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.Left },
new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right }, new Border { Width = 50, Height = 400, [DockPanel.DockProperty] = Dock.Right },

2
tests/Avalonia.Controls.UnitTests/DropDownTests.cs

@ -89,7 +89,7 @@ namespace Avalonia.Controls.UnitTests
return new Panel return new Panel
{ {
Name = "container", Name = "container",
Children = new Controls Children =
{ {
new ContentControl new ContentControl
{ {

84
tests/Avalonia.Controls.UnitTests/GridSplitterTests.cs

@ -22,14 +22,14 @@ namespace Avalonia.Controls.UnitTests
{ {
var grid = new Grid() var grid = new Grid()
{ {
RowDefinitions = new RowDefinitions("*,Auto,*"), RowDefinitions = new RowDefinitions("*,Auto,*"),
ColumnDefinitions = new ColumnDefinitions("*,*"), ColumnDefinitions = new ColumnDefinitions("*,*"),
Children = new Controls() Children =
{ {
new Border { [Grid.RowProperty] = 0 }, new Border { [Grid.RowProperty] = 0 },
new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" }, new GridSplitter { [Grid.RowProperty] = 1, Name = "splitter" },
new Border { [Grid.RowProperty] = 2 } new Border { [Grid.RowProperty] = 2 }
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
@ -43,14 +43,14 @@ namespace Avalonia.Controls.UnitTests
{ {
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("*,Auto,*"), ColumnDefinitions = new ColumnDefinitions("*,Auto,*"),
RowDefinitions = new RowDefinitions("*,*"), RowDefinitions = new RowDefinitions("*,*"),
Children = new Controls() Children =
{ {
new Border { [Grid.ColumnProperty] = 0 }, new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
@ -64,14 +64,14 @@ namespace Avalonia.Controls.UnitTests
{ {
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"), ColumnDefinitions = new ColumnDefinitions("Auto,Auto,Auto"),
RowDefinitions = new RowDefinitions("Auto,Auto"), RowDefinitions = new RowDefinitions("Auto,Auto"),
Children = new Controls() Children =
{ {
new Border { [Grid.ColumnProperty] = 0 }, new Border { [Grid.ColumnProperty] = 0 },
new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" }, new GridSplitter { [Grid.ColumnProperty] = 1, Name = "splitter" },
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
@ -99,11 +99,11 @@ namespace Avalonia.Controls.UnitTests
var grid = new Grid() var grid = new Grid()
{ {
RowDefinitions = rowDefinitions, RowDefinitions = rowDefinitions,
Children = new Controls() Children =
{ {
control1, splitter, control2 control1, splitter, control2
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
@ -131,14 +131,14 @@ namespace Avalonia.Controls.UnitTests
{ {
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = new ColumnDefinitions("Auto,*,*"), ColumnDefinitions = new ColumnDefinitions("Auto,*,*"),
RowDefinitions = new RowDefinitions("*,*"), RowDefinitions = new RowDefinitions("*,*"),
Children = new Controls() Children =
{ {
new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" }, new GridSplitter { [Grid.ColumnProperty] = 0, Name = "splitter" },
new Border { [Grid.ColumnProperty] = 1 }, new Border { [Grid.ColumnProperty] = 1 },
new Border { [Grid.ColumnProperty] = 2 }, new Border { [Grid.ColumnProperty] = 2 },
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };
@ -171,11 +171,11 @@ namespace Avalonia.Controls.UnitTests
var grid = new Grid() var grid = new Grid()
{ {
ColumnDefinitions = columnDefinitions, ColumnDefinitions = columnDefinitions,
Children = new Controls() Children =
{ {
control1, splitter, control2 control1, splitter, control2
} }
}; };
var root = new TestRoot { Child = grid }; var root = new TestRoot { Child = grid };

2
tests/Avalonia.Controls.UnitTests/GridTests.cs

@ -24,7 +24,7 @@ namespace Avalonia.Controls.UnitTests
new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto),
new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto),
}, },
Children = new Controls Children =
{ {
new Border new Border
{ {

71
tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs

@ -1,11 +1,15 @@
// Copyright (c) The Avalonia Project. All rights reserved. // 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. // 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 System.Linq;
using Avalonia.Controls.Presenters; using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling; using Avalonia.Styling;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
@ -199,6 +203,71 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(1, target.SelectedIndex); Assert.Equal(1, target.SelectedIndex);
} }
[Fact]
public void SelectedItem_Should_Not_Cause_StackOverflow()
{
var viewModel = new TestStackOverflowViewModel()
{
Items = new List<string> { "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<string> 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) private Control CreateListBoxTemplate(ITemplatedControl parent)
{ {
return new ScrollViewer return new ScrollViewer
@ -237,4 +306,4 @@ namespace Avalonia.Controls.UnitTests
target.Presenter.ApplyTemplate(); target.Presenter.ApplyTemplate();
} }
} }
} }

45
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@ -2,7 +2,6 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq; using System.Linq;
using Avalonia.Collections;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.VisualTree; using Avalonia.VisualTree;
using Xunit; using Xunit;
@ -24,18 +23,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Same(child.GetVisualParent(), panel); Assert.Same(child.GetVisualParent(), 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);
}
[Fact] [Fact]
public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent() public void Removing_Control_From_Panel_Should_Clear_Child_Controls_Parent()
{ {
@ -69,25 +56,6 @@ namespace Avalonia.Controls.UnitTests
Assert.Null(child2.GetVisualParent()); Assert.Null(child2.GetVisualParent());
} }
[Fact]
public void Resetting_Panel_Children_Should_Clear_Child_Controls_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();
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] [Fact]
public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent() public void Replacing_Panel_Children_Should_Clear_And_Set_Control_Parent()
{ {
@ -147,18 +115,5 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren()); Assert.Equal(new[] { child2, child1 }, panel.GetLogicalChildren());
Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren()); Assert.Equal(new[] { child2, child1 }, panel.GetVisualChildren());
} }
[Fact]
public void Setting_Children_Should_Make_Controls_Appear_In_Logical_And_Visual_Children()
{
var panel = new Panel();
var child = new Control();
panel.Children = new Controls { child };
Assert.Equal(new[] { child }, panel.Children);
Assert.Equal(new[] { child }, panel.GetLogicalChildren());
Assert.Equal(new[] { child }, panel.GetVisualChildren());
}
} }
} }

108
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. // Licensed under the MIT license. See licence.md file in the project root for full license information.
using System; using System;
using System.ComponentModel;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Markup.Xaml.Data;
using Avalonia.Styling;
using Xunit; using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives namespace Avalonia.Controls.UnitTests.Primitives
@ -87,8 +92,111 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Throws<ArgumentException>(() => target.Value = double.NegativeInfinity); Assert.Throws<ArgumentException>(() => 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<RangeBase>(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 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)));
}
}
}
}
} }
} }

8
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -67,7 +67,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = new Panel Child = new Panel
{ {
Children = new Controls Children =
{ {
new TextBlock(), new TextBlock(),
new Border(), new Border(),
@ -101,7 +101,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = new Panel Child = new Panel
{ {
Children = new Controls Children =
{ {
new TextBlock(), new TextBlock(),
new Border(), new Border(),
@ -124,7 +124,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
Child = new Panel Child = new Panel
{ {
Children = new Controls Children =
{ {
new TextBlock(), new TextBlock(),
new Border(), new Border(),
@ -189,7 +189,7 @@ namespace Avalonia.Controls.UnitTests.Primitives
{ {
return new StackPanel return new StackPanel
{ {
Children = new Controls Children =
{ {
new TextBlock new TextBlock
{ {

26
tests/Avalonia.Controls.UnitTests/Primitives/TrackTests.cs

@ -140,5 +140,31 @@ namespace Avalonia.Controls.UnitTests.Primitives
Assert.Same(thumb.Parent, target); Assert.Same(thumb.Parent, target);
Assert.Equal(new[] { thumb }, ((ILogical)target).LogicalChildren); Assert.Equal(new[] { thumb }, ((ILogical)target).LogicalChildren);
} }
[Fact]
public void Should_Not_Pass_Invalid_Arrange_Rect()
{
var thumb = new Thumb { Width = 100.873106060606 };
var increaseButton = new Button { Width = 10 };
var decreaseButton = new Button { Width = 10 };
var target = new Track
{
Height = 12,
Thumb = thumb,
IncreaseButton = increaseButton,
DecreaseButton = decreaseButton,
Orientation = Orientation.Horizontal,
Minimum = 0,
Maximum = 287,
Value = 287,
ViewportSize = 241,
};
target.Measure(Size.Infinity);
// #1297 was occuring here.
target.Arrange(new Rect(0, 0, 221, 12));
}
} }
} }

2
tests/Avalonia.Controls.UnitTests/ScrollViewerTests.cs

@ -50,7 +50,7 @@ namespace Avalonia.Controls.UnitTests
new RowDefinition(1, GridUnitType.Star), new RowDefinition(1, GridUnitType.Star),
new RowDefinition(GridLength.Auto), new RowDefinition(GridLength.Auto),
}, },
Children = new Controls Children =
{ {
new ScrollContentPresenter new ScrollContentPresenter
{ {

12
tests/Avalonia.Controls.UnitTests/StackPanelTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new StackPanel var target = new StackPanel
{ {
Children = new Controls Children =
{ {
new Border { Height = 20, Width = 120 }, new Border { Height = 20, Width = 120 },
new Border { Height = 30 }, new Border { Height = 30 },
@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel var target = new StackPanel
{ {
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
Children = new Controls Children =
{ {
new Border { Width = 20, Height = 120 }, new Border { Width = 20, Height = 120 },
new Border { Width = 30 }, new Border { Width = 30 },
@ -59,7 +59,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel var target = new StackPanel
{ {
Gap = 10, Gap = 10,
Children = new Controls Children =
{ {
new Border { Height = 20, Width = 120 }, new Border { Height = 20, Width = 120 },
new Border { Height = 30 }, new Border { Height = 30 },
@ -83,7 +83,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Gap = 10, Gap = 10,
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
Children = new Controls Children =
{ {
new Border { Width = 20, Height = 120 }, new Border { Width = 20, Height = 120 },
new Border { Width = 30 }, new Border { Width = 30 },
@ -106,7 +106,7 @@ namespace Avalonia.Controls.UnitTests
var target = new StackPanel var target = new StackPanel
{ {
Height = 60, Height = 60,
Children = new Controls Children =
{ {
new Border { Height = 20, Width = 120 }, new Border { Height = 20, Width = 120 },
new Border { Height = 30 }, new Border { Height = 30 },
@ -130,7 +130,7 @@ namespace Avalonia.Controls.UnitTests
{ {
Width = 60, Width = 60,
Orientation = Orientation.Horizontal, Orientation = Orientation.Horizontal,
Children = new Controls Children =
{ {
new Border { Width = 20, Height = 120 }, new Border { Width = 20, Height = 120 },
new Border { Width = 30 }, new Border { Width = 30 },

2
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -272,7 +272,7 @@ namespace Avalonia.Controls.UnitTests
{ {
return new StackPanel return new StackPanel
{ {
Children = new Controls Children =
{ {
new TabStrip new TabStrip
{ {

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -432,7 +432,7 @@ namespace Avalonia.Controls.UnitTests
{ {
return new FuncControlTemplate<TreeViewItem>(parent => new Panel return new FuncControlTemplate<TreeViewItem>(parent => new Panel
{ {
Children = new Controls Children =
{ {
new ContentPresenter new ContentPresenter
{ {

52
tests/Avalonia.Controls.UnitTests/WrapPanelTests.cs

@ -12,12 +12,12 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Width = 100, Width = 100,
Children = new Controls Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
@ -33,12 +33,12 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Width = 200, Width = 200,
Children = new Controls Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
@ -54,13 +54,13 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Orientation = Orientation.Vertical, Orientation = Orientation.Vertical,
Height = 120, Height = 120,
Children = new Controls Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);
@ -76,13 +76,13 @@ namespace Avalonia.Controls.UnitTests
{ {
var target = new WrapPanel() var target = new WrapPanel()
{ {
Orientation = Orientation.Vertical, Orientation = Orientation.Vertical,
Height = 60, Height = 60,
Children = new Controls Children =
{ {
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
new Border { Height = 50, Width = 100 }, new Border { Height = 50, Width = 100 },
} }
}; };
target.Measure(Size.Infinity); target.Measure(Size.Infinity);

124
tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Arrows.cs

@ -6,8 +6,6 @@ using Xunit;
namespace Avalonia.Input.UnitTests namespace Avalonia.Input.UnitTests
{ {
using Controls = Controls.Controls;
public class KeyboardNavigationTests_Arrows public class KeyboardNavigationTests_Arrows
{ {
[Fact] [Fact]
@ -18,12 +16,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -33,7 +31,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -56,12 +54,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -71,7 +69,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -94,12 +92,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -123,16 +121,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -144,7 +142,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -167,7 +165,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
} }
@ -187,17 +185,17 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -209,7 +207,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -232,12 +230,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -246,7 +244,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -269,12 +267,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -283,7 +281,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -306,12 +304,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -321,7 +319,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -343,12 +341,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -358,7 +356,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -380,12 +378,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.None,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -395,7 +393,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -418,12 +416,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }), (next = new Button { Name = "Button2" }),
@ -433,7 +431,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -456,12 +454,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -471,7 +469,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -495,12 +493,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -524,16 +522,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -545,7 +543,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -568,16 +566,16 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -589,7 +587,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Continue,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -632,12 +630,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -647,7 +645,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -670,12 +668,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -685,7 +683,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -708,12 +706,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -723,7 +721,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -745,12 +743,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -760,7 +758,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -783,7 +781,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
[KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.DirectionalNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(current = new Decorator (current = new Decorator
{ {

168
tests/Avalonia.Input.UnitTests/KeyboardNavigationTests_Tab.cs

@ -6,8 +6,6 @@ using Xunit;
namespace Avalonia.Input.UnitTests namespace Avalonia.Input.UnitTests
{ {
using Controls = Controls.Controls;
public class KeyboardNavigationTests_Tab public class KeyboardNavigationTests_Tab
{ {
[Fact] [Fact]
@ -18,11 +16,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -31,7 +29,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -54,11 +52,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -67,7 +65,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -90,11 +88,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -104,11 +102,11 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -133,11 +131,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -161,15 +159,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -180,7 +178,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -202,7 +200,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
} }
@ -221,15 +219,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -240,7 +238,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -263,12 +261,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -277,7 +275,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -300,12 +298,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -314,7 +312,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -337,12 +335,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -351,7 +349,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -373,12 +371,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -387,7 +385,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -410,12 +408,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -424,7 +422,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -448,12 +446,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
(container = new StackPanel (container = new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }), (next = new Button { Name = "Button2" }),
@ -462,7 +460,7 @@ namespace Avalonia.Input.UnitTests
}), }),
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -487,12 +485,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -501,7 +499,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -525,12 +523,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
(container = new StackPanel (container = new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.None,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -539,7 +537,7 @@ namespace Avalonia.Input.UnitTests
}), }),
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(next = new Button { Name = "Button4" }), (next = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -564,11 +562,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }), (next = new Button { Name = "Button2" }),
@ -577,7 +575,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -600,11 +598,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -613,7 +611,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -636,11 +634,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -664,15 +662,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -683,7 +681,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -706,15 +704,15 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -725,7 +723,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -767,12 +765,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -781,7 +779,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -804,12 +802,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Cycle,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -818,7 +816,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -841,12 +839,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
(current = new Button { Name = "Button2" }), (current = new Button { Name = "Button2" }),
@ -855,7 +853,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -877,12 +875,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(current = new Button { Name = "Button1" }), (current = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -891,7 +889,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -914,11 +912,11 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -928,7 +926,7 @@ namespace Avalonia.Input.UnitTests
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
Children = new Controls Children =
{ {
new Button { Name = "Button4" }, new Button { Name = "Button4" },
(current = new Button { Name = "Button5" }), (current = new Button { Name = "Button5" }),
@ -952,12 +950,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
(container = new StackPanel (container = new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
Children = new Controls Children =
{ {
new Button { Name = "Button1" }, new Button { Name = "Button1" },
(next = new Button { Name = "Button2" }), (next = new Button { Name = "Button2" }),
@ -966,7 +964,7 @@ namespace Avalonia.Input.UnitTests
}), }),
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -991,12 +989,12 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
Children = new Controls Children =
{ {
new StackPanel new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Once,
Children = new Controls Children =
{ {
(next = new Button { Name = "Button1" }), (next = new Button { Name = "Button1" }),
new Button { Name = "Button2" }, new Button { Name = "Button2" },
@ -1005,7 +1003,7 @@ namespace Avalonia.Input.UnitTests
}, },
new StackPanel new StackPanel
{ {
Children = new Controls Children =
{ {
(current = new Button { Name = "Button4" }), (current = new Button { Name = "Button4" }),
new Button { Name = "Button5" }, new Button { Name = "Button5" },
@ -1028,7 +1026,7 @@ namespace Avalonia.Input.UnitTests
var top = new StackPanel var top = new StackPanel
{ {
[KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained, [KeyboardNavigation.TabNavigationProperty] = KeyboardNavigationMode.Contained,
Children = new Controls Children =
{ {
(current = new Decorator (current = new Decorator
{ {

8
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@ -275,10 +275,10 @@ namespace Avalonia.Layout.UnitTests
{ {
Child = panel = new StackPanel Child = panel = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(border = new Border()) (border = new Border())
} }
} }
}; };

10
tests/Avalonia.Markup.UnitTests/ControlLocatorTests.cs

@ -23,7 +23,7 @@ namespace Avalonia.Markup.UnitTests
{ {
Child = new StackPanel Child = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(target = new TextBlock { Name = "target" }), (target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }), (relativeTo = new TextBlock { Name = "start" }),
@ -49,7 +49,7 @@ namespace Avalonia.Markup.UnitTests
{ {
Child = (panel = new StackPanel Child = (panel = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(relativeTo = new TextBlock (relativeTo = new TextBlock
{ {
@ -84,7 +84,7 @@ namespace Avalonia.Markup.UnitTests
{ {
Child = panel = new StackPanel Child = panel = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(target = new TextBlock { Name = "target" }), (target = new TextBlock { Name = "target" }),
(relativeTo = new TextBlock { Name = "start" }), (relativeTo = new TextBlock { Name = "start" }),
@ -114,7 +114,7 @@ namespace Avalonia.Markup.UnitTests
{ {
Child = new StackPanel Child = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(relativeTo = new TextBlock (relativeTo = new TextBlock
{ {
@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests
{ {
Child = new StackPanel Child = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(target2 = new TextBlock { Name = "target" }), (target2 = new TextBlock { Name = "target" }),
} }

1
tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj

@ -21,6 +21,7 @@
<ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Visuals\Avalonia.Visuals.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Styling\Avalonia.Styling.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" /> <ProjectReference Include="..\..\src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj" />
<ProjectReference Include="..\Avalonia.Base.UnitTests\Avalonia.Base.UnitTests.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" /> <ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

139
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests.cs

@ -338,6 +338,145 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("foo", target.Content); 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<double> DoubleValueProperty =
AvaloniaProperty.Register<StyledPropertyClass, double>(nameof(DoubleValue));
public double DoubleValue
{
get { return GetValue(DoubleValueProperty); }
set { SetValue(DoubleValueProperty, value); }
}
}
private class DirectPropertyClass : AvaloniaObject
{
public static readonly DirectProperty<DirectPropertyClass, double> DoubleValueProperty =
AvaloniaProperty.RegisterDirect<DirectPropertyClass, double>(
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] [Fact]
public void Binding_With_Null_Path_Works() public void Binding_With_Null_Path_Works()
{ {

8
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_ElementName.cs

@ -18,7 +18,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{ {
Child = new StackPanel Child = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
new TextBlock new TextBlock
{ {
@ -54,7 +54,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{ {
Child = new StackPanel Child = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(source = new TextBlock (source = new TextBlock
{ {
@ -89,7 +89,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{ {
Child = stackPanel = new StackPanel Child = stackPanel = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(target = new TextBlock (target = new TextBlock
{ {
@ -126,7 +126,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
{ {
Child = stackPanel = new StackPanel Child = stackPanel = new StackPanel
{ {
Children = new Controls.Controls Children =
{ {
(target = new ContentControl (target = new ContentControl
{ {

2
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"), Clip = StreamGeometry.Parse("F1 M 0,0 H 76 V 76 Z"),
Width = 76, Width = 76,
Height = 76, Height = 76,
Children = new Avalonia.Controls.Controls Children =
{ {
new Path new Path
{ {

2
tests/Avalonia.RenderTests/Media/VisualBrushTests.cs

@ -33,7 +33,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
{ {
return new Panel return new Panel
{ {
Children = new Avalonia.Controls.Controls Children =
{ {
new Image new Image
{ {

2
tests/Avalonia.RenderTests/OpacityMaskTests.cs

@ -37,7 +37,7 @@ namespace Avalonia.Direct2D1.RenderTests
}, },
Width = 76, Width = 76,
Height = 76, Height = 76,
Children = new Avalonia.Controls.Controls Children =
{ {
new Path new Path
{ {

2
tests/Avalonia.RenderTests/SVGPathTests.cs

@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.RenderTests
Background = Brushes.Yellow, Background = Brushes.Yellow,
Width = 76, Width = 76,
Height = 76, Height = 76,
Children = new Avalonia.Controls.Controls Children =
{ {
new Path new Path
{ {

12
tests/Avalonia.Visuals.UnitTests/RenderTests_Culling.cs

@ -21,7 +21,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100, Width = 100,
Height = 100, Height = 100,
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
(target = new TestControl (target = new TestControl
{ {
@ -47,7 +47,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100, Width = 100,
Height = 100, Height = 100,
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
(target = new TestControl (target = new TestControl
{ {
@ -74,7 +74,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100, Width = 100,
Height = 100, Height = 100,
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
new Canvas new Canvas
{ {
@ -82,7 +82,7 @@ namespace Avalonia.Visuals.UnitTests
Height = 100, Height = 100,
[Canvas.LeftProperty] = 50, [Canvas.LeftProperty] = 50,
[Canvas.TopProperty] = 50, [Canvas.TopProperty] = 50,
Children = new Controls.Controls Children =
{ {
(target = new TestControl (target = new TestControl
{ {
@ -111,7 +111,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100, Width = 100,
Height = 100, Height = 100,
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
(target = new TestControl (target = new TestControl
{ {
@ -138,7 +138,7 @@ namespace Avalonia.Visuals.UnitTests
Width = 100, Width = 100,
Height = 100, Height = 100,
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {

14
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests_HitTesting.cs

@ -154,7 +154,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Width = 200, Width = 200,
Height = 200, Height = 200,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -198,7 +198,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Width = 200, Width = 200,
Height = 200, Height = 200,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -255,7 +255,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
ClipToBounds = false, ClipToBounds = false,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -303,7 +303,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100, Width = 100,
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
Children = new Controls.Controls Children =
{ {
new Panel() new Panel()
{ {
@ -312,7 +312,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Background = Brushes.Red, Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0), Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
(target = new Border() (target = new Border()
{ {
@ -354,7 +354,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100, Width = 100,
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
Children = new Controls.Controls Children =
{ {
(target = new Border() (target = new Border()
{ {
@ -374,7 +374,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Content = new StackPanel() Content = new StackPanel()
{ {
Children = new Controls.Controls Children =
{ {
(item1 = new Border() (item1 = new Border()
{ {

14
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests_HitTesting.cs

@ -126,7 +126,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Width = 200, Width = 200,
Height = 200, Height = 200,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -171,7 +171,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Width = 200, Width = 200,
Height = 200, Height = 200,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -238,7 +238,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
ClipToBounds = false, ClipToBounds = false,
Children = new Controls.Controls Children =
{ {
new Border new Border
{ {
@ -287,7 +287,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100, Width = 100,
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
Children = new Controls.Controls Children =
{ {
new Panel() new Panel()
{ {
@ -296,7 +296,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Background = Brushes.Red, Background = Brushes.Red,
Margin = new Thickness(0, 100, 0, 0), Margin = new Thickness(0, 100, 0, 0),
ClipToBounds = true, ClipToBounds = true,
Children = new Controls.Controls Children =
{ {
(target = new Border() (target = new Border()
{ {
@ -339,7 +339,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Width = 100, Width = 100,
Height = 200, Height = 200,
Background = Brushes.Red, Background = Brushes.Red,
Children = new Controls.Controls Children =
{ {
(target = new Border() (target = new Border()
{ {
@ -359,7 +359,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
{ {
Content = new StackPanel() Content = new StackPanel()
{ {
Children = new Controls.Controls Children =
{ {
(item1 = new Border() (item1 = new Border()
{ {

Loading…
Cancel
Save