Browse Source

Merge branch 'master' into fixes/2316-selectorparser-null-type

Jumar Macato 6 năm trước cách đây
mục cha
commit
67d934ee00
35 tập tin đã thay đổi với 949 bổ sung166 xóa
  1. 2 2
      src/Avalonia.Animation/Cue.cs
  2. 62 47
      src/Avalonia.Base/AvaloniaObject.cs
  3. 55 0
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  4. 6 1
      src/Avalonia.Base/IAvaloniaObject.cs
  5. 8 0
      src/Avalonia.Base/IPriorityValueOwner.cs
  6. 0 53
      src/Avalonia.Base/Logging/LoggerExtensions.cs
  7. 1 1
      src/Avalonia.Base/PriorityValue.cs
  8. 4 0
      src/Avalonia.Base/ValueStore.cs
  9. 13 1
      src/Avalonia.Controls/ContentControl.cs
  10. 23 6
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  11. 35 7
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  12. 14 1
      src/Avalonia.Controls/Presenters/IContentPresenter.cs
  13. 35 2
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  14. 16 5
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  15. 16 5
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  16. 4 4
      src/Avalonia.Controls/Primitives/Popup.cs
  17. 10 5
      src/Avalonia.Layout/LayoutManager.cs
  18. 79 0
      src/Avalonia.Layout/LayoutQueue.cs
  19. 4 0
      src/Avalonia.Layout/Properties/AssemblyInfo.cs
  20. 4 2
      src/Avalonia.Styling/Styling/Style.cs
  21. 26 11
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  22. 29 0
      src/Avalonia.Visuals/Visual.cs
  23. 16 0
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs
  24. 1 1
      tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs
  25. 68 0
      tests/Avalonia.Controls.UnitTests/ListBoxTests.cs
  26. 27 1
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs
  27. 31 1
      tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs
  28. 110 9
      tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs
  29. 196 0
      tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs
  30. 1 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  31. 1 0
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs
  32. 1 0
      tests/Avalonia.Styling.UnitTests/TestControlBase.cs
  33. 1 0
      tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs
  34. 4 1
      tests/Avalonia.UnitTests/TestTemplatedRoot.cs
  35. 46 0
      tests/Avalonia.Visuals.UnitTests/VisualTests.cs

+ 2 - 2
src/Avalonia.Animation/Cue.cs

@@ -30,7 +30,7 @@ namespace Avalonia.Animation
         /// <summary>
         /// Parses a string to a <see cref="Cue"/> object.
         /// </summary>
-        public static object Parse(string value, CultureInfo culture)
+        public static Cue Parse(string value, CultureInfo culture)
         {
             string v = value;
 
@@ -70,7 +70,7 @@ namespace Avalonia.Animation
         }
     }
 
-    public class CueTypeConverter : TypeConverter 
+    public class CueTypeConverter : TypeConverter
     {
         public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
         {

+ 62 - 47
src/Avalonia.Base/AvaloniaObject.cs

@@ -22,27 +22,11 @@ namespace Avalonia
     /// </remarks>
     public class AvaloniaObject : IAvaloniaObject, IAvaloniaObjectDebug, INotifyPropertyChanged
     {
-        /// <summary>
-        /// The parent object that inherited values are inherited from.
-        /// </summary>
         private IAvaloniaObject _inheritanceParent;
-
-        /// <summary>
-        /// Maintains a list of direct property binding subscriptions so that the binding source
-        /// doesn't get collected.
-        /// </summary>
         private List<DirectBindingSubscription> _directBindings;
-
-        /// <summary>
-        /// Event handler for <see cref="INotifyPropertyChanged"/> implementation.
-        /// </summary>
         private PropertyChangedEventHandler _inpcChanged;
-
-        /// <summary>
-        /// Event handler for <see cref="PropertyChanged"/> implementation.
-        /// </summary>
         private EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChanged;
-
+        private EventHandler<AvaloniaPropertyChangedEventArgs> _inheritablePropertyChanged;
         private ValueStore _values;
         private ValueStore Values => _values ?? (_values = new ValueStore(this));
 
@@ -52,32 +36,7 @@ namespace Avalonia
         public AvaloniaObject()
         {
             VerifyAccess();
-
-            void Notify(AvaloniaProperty property)
-            {
-                object value = property.IsDirect ?
-                    ((IDirectPropertyAccessor)property).GetValue(this) :
-                    ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
-
-                var e = new AvaloniaPropertyChangedEventArgs(
-                    this,
-                    property,
-                    AvaloniaProperty.UnsetValue,
-                    value,
-                    BindingPriority.Unset);
-
-                property.NotifyInitialized(e);
-            }
-
-            foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegistered(this))
-            {
-                Notify(property);
-            }
-
-            foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()))
-            {
-                Notify(property);
-            }
+            AvaloniaPropertyRegistry.Instance.NotifyInitialized(this);
         }
 
         /// <summary>
@@ -98,6 +57,15 @@ namespace Avalonia
             remove { _inpcChanged -= value; }
         }
 
+        /// <summary>
+        /// Raised when an inheritable <see cref="AvaloniaProperty"/> value changes on this object.
+        /// </summary>
+        event EventHandler<AvaloniaPropertyChangedEventArgs> IAvaloniaObject.InheritablePropertyChanged
+        {
+            add { _inheritablePropertyChanged += value; }
+            remove { _inheritablePropertyChanged -= value; }
+        }
+
         /// <summary>
         /// Gets or sets the parent object that inherited <see cref="AvaloniaProperty"/> values
         /// are inherited from.
@@ -118,8 +86,9 @@ namespace Avalonia
                 {
                     if (_inheritanceParent != null)
                     {
-                        _inheritanceParent.PropertyChanged -= ParentPropertyChanged;
+                        _inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
                     }
+
                     var properties = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
                         .Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()));
                     var inherited = (from property in properties
@@ -144,7 +113,7 @@ namespace Avalonia
 
                     if (_inheritanceParent != null)
                     {
-                        _inheritanceParent.PropertyChanged += ParentPropertyChanged;
+                        _inheritanceParent.InheritablePropertyChanged += ParentPropertyChanged;
                     }
                 }
             }
@@ -421,6 +390,7 @@ namespace Avalonia
         
         internal void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification)
         {
+            LogIfError(property, notification);
             UpdateDataValidation(property, notification);
         }
 
@@ -452,6 +422,23 @@ namespace Avalonia
             });
         }
 
+        /// <summary>
+        /// Logs a binding error for a property.
+        /// </summary>
+        /// <param name="property">The property that the error occurred on.</param>
+        /// <param name="e">The binding error.</param>
+        protected internal virtual void LogBindingError(AvaloniaProperty property, Exception e)
+        {
+            Logger.Log(
+                LogEventLevel.Warning,
+                LogArea.Binding,
+                this,
+                "Error in binding to {Target}.{Property}: {Message}",
+                this,
+                property,
+                e.Message);
+        }
+
         /// <summary>
         /// Called to update the validation state for properties for which data validation is
         /// enabled.
@@ -509,6 +496,11 @@ namespace Avalonia
                     PropertyChangedEventArgs e2 = new PropertyChangedEventArgs(property.Name);
                     _inpcChanged(this, e2);
                 }
+
+                if (property.Inherits)
+                {
+                    _inheritablePropertyChanged?.Invoke(this, e);
+                }
             }
             finally
             {
@@ -628,7 +620,7 @@ namespace Avalonia
         /// </summary>
         /// <param name="property">The property.</param>
         /// <returns>The default value.</returns>
-        internal object GetDefaultValue(AvaloniaProperty property)
+        private object GetDefaultValue(AvaloniaProperty property)
         {
             if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
                 return aobj.GetValue(property);
@@ -648,7 +640,7 @@ namespace Avalonia
 
                 if (notification != null)
                 {
-                    notification.LogIfError(this, property);
+                    LogIfError(property, notification);
                     value = notification.Value;
                 }
 
@@ -780,6 +772,29 @@ namespace Avalonia
             return description?.Description ?? o.ToString();
         }
 
+        /// <summary>
+        /// Logs a mesage if the notification represents a binding error.
+        /// </summary>
+        /// <param name="property">The property being bound.</param>
+        /// <param name="notification">The binding notification.</param>
+        private void LogIfError(AvaloniaProperty property, BindingNotification notification)
+        {
+            if (notification.ErrorType == BindingErrorType.Error)
+            {
+                if (notification.Error is AggregateException aggregate)
+                {
+                    foreach (var inner in aggregate.InnerExceptions)
+                    {
+                        LogBindingError(property, inner);
+                    }
+                }
+                else
+                {
+                    LogBindingError(property, notification.Error);
+                }
+            }
+        }
+
         /// <summary>
         /// Logs a property set message.
         /// </summary>

+ 55 - 0
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using System.Runtime.CompilerServices;
+using Avalonia.Data;
 
 namespace Avalonia
 {
@@ -23,6 +24,8 @@ namespace Avalonia
             new Dictionary<Type, List<AvaloniaProperty>>();
         private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
             new Dictionary<Type, List<AvaloniaProperty>>();
+        private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
+            new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
 
         /// <summary>
         /// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@@ -226,6 +229,7 @@ namespace Avalonia
             }
             
             _registeredCache.Clear();
+            _initializedCache.Clear();
         }
 
         /// <summary>
@@ -261,6 +265,57 @@ namespace Avalonia
             }
             
             _attachedCache.Clear();
+            _initializedCache.Clear();
+        }
+
+        internal void NotifyInitialized(AvaloniaObject o)
+        {
+            Contract.Requires<ArgumentNullException>(o != null);
+
+            var type = o.GetType();
+
+            void Notify(AvaloniaProperty property, object value)
+            {
+                var e = new AvaloniaPropertyChangedEventArgs(
+                    o,
+                    property,
+                    AvaloniaProperty.UnsetValue,
+                    value,
+                    BindingPriority.Unset);
+
+                property.NotifyInitialized(e);
+            }
+
+            if (!_initializedCache.TryGetValue(type, out var items))
+            {
+                var build = new Dictionary<AvaloniaProperty, object>();
+
+                foreach (var property in GetRegistered(type))
+                {
+                    var value = !property.IsDirect ?
+                        ((IStyledPropertyAccessor)property).GetDefaultValue(type) :
+                        null;
+                    build.Add(property, value);
+                }
+
+                foreach (var property in GetRegisteredAttached(type))
+                {
+                    if (!build.ContainsKey(property))
+                    {
+                        var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
+                        build.Add(property, value);
+                    }
+                }
+
+                items = build.ToList();
+                _initializedCache.Add(type, items);
+            }
+
+            foreach (var i in items)
+            {
+                var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
+                Notify(i.Key, value);
+            }
         }
     }
 }

+ 6 - 1
src/Avalonia.Base/IAvaloniaObject.cs

@@ -16,6 +16,11 @@ namespace Avalonia
         /// </summary>
         event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
 
+        /// <summary>
+        /// Raised when an inheritable <see cref="AvaloniaProperty"/> value changes on this object.
+        /// </summary>
+        event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
+
         /// <summary>
         /// Gets a <see cref="AvaloniaProperty"/> value.
         /// </summary>
@@ -97,4 +102,4 @@ namespace Avalonia
             IObservable<T> source,
             BindingPriority priority = BindingPriority.LocalValue);
     }
-}
+}

+ 8 - 0
src/Avalonia.Base/IPriorityValueOwner.cs

@@ -1,6 +1,7 @@
 // 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 Avalonia.Data;
 using Avalonia.Utilities;
 
@@ -28,6 +29,13 @@ namespace Avalonia
         /// <param name="notification">The notification.</param>
         void BindingNotificationReceived(AvaloniaProperty property, BindingNotification notification);
 
+        /// <summary>
+        /// Logs a binding error.
+        /// </summary>
+        /// <param name="property">The property the error occurred on.</param>
+        /// <param name="e">The binding error.</param>
+        void LogError(AvaloniaProperty property, Exception e);
+
         /// <summary>
         /// Ensures that the current thread is the UI thread.
         /// </summary>

+ 0 - 53
src/Avalonia.Base/Logging/LoggerExtensions.cs

@@ -1,53 +0,0 @@
-using System;
-using Avalonia.Data;
-
-namespace Avalonia.Logging
-{
-    internal static class LoggerExtensions
-    {
-        public static void LogIfError(
-            this BindingNotification notification,
-            object source,
-            AvaloniaProperty property)
-        {
-            if (notification.ErrorType == BindingErrorType.Error)
-            {
-                if (notification.Error is AggregateException aggregate)
-                {
-                    foreach (var inner in aggregate.InnerExceptions)
-                    {
-                        LogError(source, property, inner);
-                    }
-                }
-                else
-                {
-                    LogError(source, property, notification.Error);
-                }
-            }
-        }
-
-        private static void LogError(object source, AvaloniaProperty property, Exception e)
-        {
-            var level = LogEventLevel.Warning;
-
-            if (e is BindingChainException b &&
-                !string.IsNullOrEmpty(b.Expression) &&
-                string.IsNullOrEmpty(b.ExpressionErrorPoint))
-            {
-                // The error occurred at the root of the binding chain: it's possible that the
-                // DataContext isn't set up yet, so log at Information level instead of Warning
-                // to prevent spewing hundreds of errors.
-                level = LogEventLevel.Information;
-            }
-
-            Logger.Log(
-                level,
-                LogArea.Binding,
-                source,
-                "Error in binding to {Target}.{Property}: {Message}",
-                source,
-                property,
-                e.Message);
-        }
-    }
-}

+ 1 - 1
src/Avalonia.Base/PriorityValue.cs

@@ -197,7 +197,7 @@ namespace Avalonia
         /// <param name="error">The binding error.</param>
         public void LevelError(PriorityLevel level, BindingNotification error)
         {
-            error.LogIfError(Owner, Property);
+            Owner.LogError(Property, error.Error);
         }
 
         /// <summary>

+ 4 - 0
src/Avalonia.Base/ValueStore.cs

@@ -118,6 +118,10 @@ namespace Avalonia
 
             return dict;
         }
+        public void LogError(AvaloniaProperty property, Exception e)
+        {
+            _owner.LogBindingError(property, e);
+        }
 
         public object GetValue(AvaloniaProperty property)
         {

+ 13 - 1
src/Avalonia.Controls/ContentControl.cs

@@ -97,7 +97,19 @@ namespace Avalonia.Controls
         /// <inheritdoc/>
         void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            Presenter = presenter;
+            RegisterContentPresenter(presenter);
+        }
+
+        /// <summary>
+        /// Called when an <see cref="IContentPresenter"/> is registered with the control.
+        /// </summary>
+        /// <param name="presenter">The presenter.</param>
+        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        {
+            if (presenter.Name == "PART_ContentPresenter")
+            {
+                Presenter = presenter;
+            }
         }
     }
 }

+ 23 - 6
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@@ -19,8 +19,8 @@ namespace Avalonia.Controls.Mixins
     /// <para>
     /// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
     /// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
-    /// updates keeps the control's logical children in sync with the content being displayed by
-    /// the control.
+    /// keeps the control's logical children in sync with the content being displayed by the
+    /// control.
     /// </para>
     public class ContentControlMixin
     {
@@ -49,25 +49,42 @@ namespace Avalonia.Controls.Mixins
             Contract.Requires<ArgumentNullException>(content != null);
             Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
 
+            void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
+            {
+                if (s is IControl sender && sender?.TemplatedParent is TControl parent)
+                {
+                    UpdateLogicalChild(
+                        sender,
+                        logicalChildrenSelector(parent),
+                        e.OldValue,
+                        null);
+                }
+            }
+
             void TemplateApplied(object s, RoutedEventArgs ev)
             {
                 if (s is TControl sender)
                 {
                     var e = (TemplateAppliedEventArgs)ev;
-                    var presenter = (IControl)e.NameScope.Find(presenterName);
+                    var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
 
                     if (presenter != null)
                     {
                         presenter.ApplyTemplate();
 
                         var logicalChildren = logicalChildrenSelector(sender);
-                        var subscription = presenter
+                        var subscription = new CompositeDisposable();
+
+                        presenter.ChildChanging += ChildChanging;
+                        subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
+
+                        subscription.Add(presenter
                             .GetPropertyChangedObservable(ContentPresenter.ChildProperty)
                             .Subscribe(c => UpdateLogicalChild(
                                 sender,
                                 logicalChildren,
-                                c.OldValue,
-                                c.NewValue));
+                                null,
+                                c.NewValue)));
 
                         UpdateLogicalChild(
                             sender,

+ 35 - 7
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@@ -5,6 +5,7 @@ using System;
 using Avalonia.Controls.Primitives;
 using Avalonia.Controls.Templates;
 using Avalonia.Controls.Utils;
+using Avalonia.Data;
 using Avalonia.Layout;
 using Avalonia.LogicalTree;
 using Avalonia.Media;
@@ -82,6 +83,7 @@ namespace Avalonia.Controls.Presenters
 
         private IControl _child;
         private bool _createdChild;
+        EventHandler<AvaloniaPropertyChangedEventArgs> _childChanging;
         private IDataTemplate _dataTemplate;
         private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
 
@@ -188,6 +190,13 @@ namespace Avalonia.Controls.Presenters
             set { SetValue(PaddingProperty, value); }
         }
 
+        /// <inheritdoc/>
+        event EventHandler<AvaloniaPropertyChangedEventArgs> IContentPresenter.ChildChanging
+        {
+            add => _childChanging += value;
+            remove => _childChanging -= value;
+        }
+
         /// <inheritdoc/>
         public sealed override void ApplyTemplate()
         {
@@ -215,9 +224,30 @@ namespace Avalonia.Controls.Presenters
             var newChild = CreateChild();
 
             // Remove the old child if we're not recycling it.
-            if (oldChild != null && newChild != oldChild)
+            if (newChild != oldChild)
             {
-                VisualChildren.Remove(oldChild);
+                if (oldChild != null)
+                {
+                    VisualChildren.Remove(oldChild);
+                }
+
+                if (oldChild?.Parent == this)
+                {
+                    // If we're the child's parent then the presenter isn't in a ContentControl's
+                    // template.
+                    LogicalChildren.Remove(oldChild);
+                }
+                else
+                {
+                    // If we're in a ContentControl's template then invoke ChildChanging to let
+                    // ContentControlMixin handle removing the logical child.
+                    _childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs(
+                        this,
+                        ChildProperty,
+                        oldChild,
+                        newChild,
+                        BindingPriority.LocalValue));
+                }
             }
 
             // Set the DataContext if the data isn't a control.
@@ -241,11 +271,9 @@ namespace Avalonia.Controls.Presenters
 
                 Child = newChild;
 
-                if (oldChild?.Parent == this)
-                {
-                    LogicalChildren.Remove(oldChild);
-                }
-
+                // If we're in a ContentControl's template then the child's parent will have been
+                // set by ContentControlMixin in response to Child changing. If not, then we're
+                // standalone and should make the control our own logical child.
                 if (newChild.Parent == null && TemplatedParent == null)
                 {
                     LogicalChildren.Add(newChild);

+ 14 - 1
src/Avalonia.Controls/Presenters/IContentPresenter.cs

@@ -1,6 +1,8 @@
 // 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 Avalonia.Controls.Mixins;
 using Avalonia.Controls.Primitives;
 
 namespace Avalonia.Controls.Presenters
@@ -20,5 +22,16 @@ namespace Avalonia.Controls.Presenters
         /// Gets or sets the content to be displayed by the presenter.
         /// </summary>
         object Content { get; set; }
+
+        /// <summary>
+        /// Raised when <see cref="Child"/> property is about to change.
+        /// </summary>
+        /// <remarks>
+        /// This event should be raised after the child has been removed from the visual tree,
+        /// but before the <see cref="Child"/> property has changed. It is intended for consumption
+        /// by <see cref="ContentControlMixin"/> in order to update the host control's logical
+        /// children.
+        /// </remarks>
+        event EventHandler<AvaloniaPropertyChangedEventArgs> ChildChanging;
     }
-}
+}

+ 35 - 2
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@@ -1,6 +1,8 @@
 // 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 Avalonia.Controls.Mixins;
+using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
 
 namespace Avalonia.Controls.Primitives
@@ -20,7 +22,18 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="HeaderTemplate"/> property.
         /// </summary>
         public static readonly StyledProperty<IDataTemplate> HeaderTemplateProperty =
-            AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));          
+            AvaloniaProperty.Register<HeaderedContentControl, IDataTemplate>(nameof(HeaderTemplate));
+
+        /// <summary>
+        /// Initializes static members of the <see cref="ContentControl"/> class.
+        /// </summary>
+        static HeaderedContentControl()
+        {
+            ContentControlMixin.Attach<HeaderedContentControl>(
+                HeaderProperty,
+                x => x.LogicalChildren,
+                "PART_HeaderPresenter");
+        }
 
         /// <summary>
         /// Gets or sets the header content.
@@ -29,7 +42,16 @@ namespace Avalonia.Controls.Primitives
         {
             get { return GetValue(HeaderProperty); }
             set { SetValue(HeaderProperty, value); }
-        }     
+        }
+
+        /// <summary>
+        /// Gets the header presenter from the control's template.
+        /// </summary>
+        public IContentPresenter HeaderPresenter
+        {
+            get;
+            private set;
+        }
 
         /// <summary>
         /// Gets or sets the data template used to display the header content of the control.
@@ -39,5 +61,16 @@ namespace Avalonia.Controls.Primitives
             get { return GetValue(HeaderTemplateProperty); }
             set { SetValue(HeaderTemplateProperty, value); }
         }
+
+        /// <inheritdoc/>
+        protected override void RegisterContentPresenter(IContentPresenter presenter)
+        {
+            base.RegisterContentPresenter(presenter);
+
+            if (presenter.Name == "PART_HeaderPresenter")
+            {
+                HeaderPresenter = presenter;
+            }
+        }
     }
 }

+ 16 - 5
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Controls.Primitives
     /// <summary>
     /// Represents an <see cref="ItemsControl"/> with a related header.
     /// </summary>
-    public class HeaderedItemsControl : ItemsControl
+    public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
     {
         /// <summary>
         /// Defines the <see cref="Header"/> property.
@@ -40,17 +40,28 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the header presenter from the control's template.
         /// </summary>
-        public ContentPresenter HeaderPresenter
+        public IContentPresenter HeaderPresenter
         {
             get;
             private set;
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter");
-            base.OnTemplateApplied(e);
+            RegisterContentPresenter(presenter);
+        }
+
+        /// <summary>
+        /// Called when an <see cref="IContentPresenter"/> is registered with the control.
+        /// </summary>
+        /// <param name="presenter">The presenter.</param>
+        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        {
+            if (presenter.Name == "PART_HeaderPresenter")
+            {
+                HeaderPresenter = presenter;
+            }
         }
     }
 }

+ 16 - 5
src/Avalonia.Controls/Primitives/HeaderedSelectingControl.cs → src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs

@@ -9,7 +9,7 @@ namespace Avalonia.Controls.Primitives
     /// <summary>
     /// Represents a <see cref="SelectingItemsControl"/> with a related header.
     /// </summary>
-    public class HeaderedSelectingItemsControl : SelectingItemsControl
+    public class HeaderedSelectingItemsControl : SelectingItemsControl, IContentPresenterHost
     {
         /// <summary>
         /// Defines the <see cref="Header"/> property.
@@ -40,17 +40,28 @@ namespace Avalonia.Controls.Primitives
         /// <summary>
         /// Gets the header presenter from the control's template.
         /// </summary>
-        public ContentPresenter HeaderPresenter
+        public IContentPresenter HeaderPresenter
         {
             get;
             private set;
         }
 
         /// <inheritdoc/>
-        protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
+        void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
         {
-            base.OnTemplateApplied(e);
-            HeaderPresenter = e.NameScope.Find<ContentPresenter>("PART_HeaderPresenter");
+            RegisterContentPresenter(presenter);
+        }
+
+        /// <summary>
+        /// Called when an <see cref="IContentPresenter"/> is registered with the control.
+        /// </summary>
+        /// <param name="presenter">The presenter.</param>
+        protected virtual void RegisterContentPresenter(IContentPresenter presenter)
+        {
+            if (presenter.Name == "PART_HeaderPresenter")
+            {
+                HeaderPresenter = presenter;
+            }
         }
     }
 }

+ 4 - 4
src/Avalonia.Controls/Primitives/Popup.cs

@@ -252,9 +252,9 @@ namespace Avalonia.Controls.Primitives
                 else
                 {
                     var parentPopuproot = _topLevel as PopupRoot;
-                    if (parentPopuproot != null && parentPopuproot.Parent != null)
+                    if (parentPopuproot?.Parent is Popup popup)
                     {
-                        ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed;
+                        popup.Closed += ParentClosed;
                     }
                 }
                 _topLevel.AddHandler(PointerPressedEvent, PointerPressedOutside, RoutingStrategies.Tunnel);
@@ -293,9 +293,9 @@ namespace Avalonia.Controls.Primitives
                     else
                     {
                         var parentPopuproot = _topLevel as PopupRoot;
-                        if (parentPopuproot != null && parentPopuproot.Parent != null)
+                        if (parentPopuproot?.Parent is Popup popup)
                         {
-                            ((Popup)parentPopuproot.Parent).Closed -= ParentClosed;
+                            popup.Closed -= ParentClosed;
                         }
                     }
                     _nonClientListener?.Dispose();

+ 10 - 5
src/Avalonia.Layout/LayoutManager.cs

@@ -2,7 +2,6 @@
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 using System;
-using System.Collections.Generic;
 using Avalonia.Logging;
 using Avalonia.Threading;
 
@@ -13,8 +12,8 @@ namespace Avalonia.Layout
     /// </summary>
     public class LayoutManager : ILayoutManager
     {
-        private readonly Queue<ILayoutable> _toMeasure = new Queue<ILayoutable>();
-        private readonly Queue<ILayoutable> _toArrange = new Queue<ILayoutable>();
+        private readonly LayoutQueue<ILayoutable> _toMeasure = new LayoutQueue<ILayoutable>(v => !v.IsMeasureValid);
+        private readonly LayoutQueue<ILayoutable> _toArrange = new LayoutQueue<ILayoutable>(v => !v.IsArrangeValid);
         private bool _queued;
         private bool _running;
 
@@ -80,6 +79,9 @@ namespace Avalonia.Layout
                 var stopwatch = new System.Diagnostics.Stopwatch();
                 stopwatch.Start();
 
+                _toMeasure.BeginLoop(MaxPasses);
+                _toArrange.BeginLoop(MaxPasses);
+
                 try
                 {
                     for (var pass = 0; pass < MaxPasses; ++pass)
@@ -98,6 +100,9 @@ namespace Avalonia.Layout
                     _running = false;
                 }
 
+                _toMeasure.EndLoop();
+                _toArrange.EndLoop();
+
                 stopwatch.Stop();
                 Logger.Information(LogArea.Layout, this, "Layout pass finished in {Time}", stopwatch.Elapsed);
             }
@@ -112,7 +117,7 @@ namespace Avalonia.Layout
             Arrange(root);
 
             // Running the initial layout pass may have caused some control to be invalidated
-            // so run a full layout pass now (this usually due to scrollbars; its not known 
+            // so run a full layout pass now (this usually due to scrollbars; its not known
             // whether they will need to be shown until the layout pass has run and if the
             // first guess was incorrect the layout will need to be updated).
             ExecuteLayoutPass();
@@ -133,7 +138,7 @@ namespace Avalonia.Layout
 
         private void ExecuteArrangePass()
         {
-            while (_toArrange.Count > 0 && _toMeasure.Count == 0)
+            while (_toArrange.Count > 0)
             {
                 var control = _toArrange.Dequeue();
 

+ 79 - 0
src/Avalonia.Layout/LayoutQueue.cs

@@ -0,0 +1,79 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Avalonia.Layout
+{
+    internal class LayoutQueue<T> : IReadOnlyCollection<T>
+    {
+        private struct Info
+        {
+            public bool Active;
+            public int Count;
+        }
+
+        public LayoutQueue(Func<T, bool> shouldEnqueue)
+        {
+            _shouldEnqueue = shouldEnqueue;
+        }
+
+        private Func<T, bool> _shouldEnqueue;
+        private Queue<T> _inner = new Queue<T>();
+        private Dictionary<T, Info> _loopQueueInfo = new Dictionary<T, Info>();
+        private int _maxEnqueueCountPerLoop = 1;
+
+        public int Count => _inner.Count;
+
+        public IEnumerator<T> GetEnumerator() => (_inner as IEnumerable<T>).GetEnumerator();
+
+        IEnumerator IEnumerable.GetEnumerator() => _inner.GetEnumerator();
+
+        public T Dequeue()
+        {
+            var result = _inner.Dequeue();
+
+            if (_loopQueueInfo.TryGetValue(result, out var info))
+            {
+                info.Active = false;
+                _loopQueueInfo[result] = info;
+            }
+
+            return result;
+        }
+
+        public void Enqueue(T item)
+        {
+            _loopQueueInfo.TryGetValue(item, out var info);
+
+            if (!info.Active && info.Count < _maxEnqueueCountPerLoop)
+            {
+                _inner.Enqueue(item);
+                _loopQueueInfo[item] = new Info() { Active = true, Count = info.Count + 1 };
+            }
+        }
+
+        public void BeginLoop(int maxEnqueueCountPerLoop)
+        {
+            _maxEnqueueCountPerLoop = maxEnqueueCountPerLoop;
+        }
+
+        public void EndLoop()
+        {
+            var notfinalized = _loopQueueInfo.Where(v => v.Value.Count == _maxEnqueueCountPerLoop).ToArray();
+
+            _loopQueueInfo.Clear();
+
+            //prevent layout cycle but add to next layout the non arranged/measured items that might have caused cycle
+            //one more time as a final attempt
+            foreach (var item in notfinalized)
+            {
+                if (_shouldEnqueue(item.Key))
+                {
+                    _loopQueueInfo[item.Key] = new Info() { Active = true, Count = item.Value.Count + 1 };
+                    _inner.Enqueue(item.Key);
+                }
+            }
+        }
+    }
+}

+ 4 - 0
src/Avalonia.Layout/Properties/AssemblyInfo.cs

@@ -1,6 +1,10 @@
 // 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.Runtime.CompilerServices;
 using Avalonia.Metadata;
 
+[assembly: InternalsVisibleTo("Avalonia.Layout.UnitTests")]
+
 [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Layout")]
+

+ 4 - 2
src/Avalonia.Styling/Styling/Style.cs

@@ -143,6 +143,7 @@ namespace Avalonia.Styling
                     }
 
                     controlSubscriptions.Add(subs);
+                    controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
                     Subscriptions.Add(subs);
                 }
 
@@ -159,8 +160,9 @@ namespace Avalonia.Styling
                     var sub = setter.Apply(this, control, null);
                     subs.Add(sub);
                 }
-                
+
                 controlSubscriptions.Add(subs);
+                controlSubscriptions.Add(Disposable.Create(() => Subscriptions.Remove(subs)));
                 Subscriptions.Add(subs);
                 return true;
             }
@@ -223,7 +225,7 @@ namespace Avalonia.Styling
         {
             if (!_applied.TryGetValue(control, out var subscriptions))
             {
-                subscriptions = new CompositeDisposable(2);
+                subscriptions = new CompositeDisposable(3);
                 subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach));
                 _applied.Add(control, subscriptions);
             }

+ 26 - 11
src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs

@@ -322,36 +322,51 @@ namespace Avalonia.Rendering.SceneGraph
             }
         }
 
+        /// <summary>
+        /// Ensures that this node draw operations have been created and are mutable (in case we are using cloned operations).
+        /// </summary>
         private void EnsureDrawOperationsCreated()
         {
             if (_drawOperations == null)
             {
                 _drawOperations = new List<IRef<IDrawOperation>>();
-                _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
+                _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
                 _drawOperationsCloned = false;
             }
             else if (_drawOperationsCloned)
             {
                 _drawOperations = new List<IRef<IDrawOperation>>(_drawOperations.Select(op => op.Clone()));
                 _drawOperationsRefCounter.Dispose();
-                _drawOperationsRefCounter = RefCountable.Create(Disposable.Create(DisposeDrawOperations));
+                _drawOperationsRefCounter = RefCountable.Create(CreateDisposeDrawOperations(_drawOperations));
                 _drawOperationsCloned = false;
             }
         }
 
-        public bool Disposed { get; }
-        
-        public void Dispose()
+        /// <summary>
+        /// Creates disposable that will dispose all items in passed draw operations after being disposed.
+        /// It is crucial that we don't capture current <see cref="VisualNode"/> instance
+        /// as draw operations can be cloned and may persist across subsequent scenes.
+        /// </summary>
+        /// <param name="drawOperations">Draw operations that need to be disposed.</param>
+        /// <returns>Disposable for given draw operations.</returns>
+        private static IDisposable CreateDisposeDrawOperations(List<IRef<IDrawOperation>> drawOperations)
         {
-            _drawOperationsRefCounter?.Dispose();
+            return Disposable.Create(() =>
+            {
+                foreach (var operation in drawOperations)
+                {
+                    operation.Dispose();
+                }
+            });
         }
 
-        private void DisposeDrawOperations()
+        public bool Disposed { get; private set; }
+
+        public void Dispose()
         {
-            foreach (var operation in DrawOperations)
-            {
-                operation.Dispose();
-            }
+            _drawOperationsRefCounter?.Dispose();
+
+            Disposed = true;
         }
     }
 }

+ 29 - 0
src/Avalonia.Visuals/Visual.cs

@@ -8,6 +8,7 @@ using System.Reactive.Linq;
 using Avalonia.Collections;
 using Avalonia.Data;
 using Avalonia.Logging;
+using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.VisualTree;
@@ -448,6 +449,34 @@ namespace Avalonia
             RaisePropertyChanged(VisualParentProperty, oldParent, newParent, BindingPriority.LocalValue);
         }
 
+        protected override sealed void LogBindingError(AvaloniaProperty property, Exception e)
+        {
+            // Don't log a binding error unless the control is attached to a logical or visual tree.
+            // In theory this should only need to check for logical tree attachment, but in practise
+            // due to ContentControlMixin only taking effect when the template has finished being
+            // applied, some controls are attached to the visual tree before the logical tree.
+            if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree)
+            {
+                if (e is BindingChainException b &&
+                    string.IsNullOrEmpty(b.ExpressionErrorPoint) &&
+                    DataContext == null)
+                {
+                    // The error occurred at the root of the binding chain and DataContext is null;
+                    // don't log this - the DataContext probably hasn't been set up yet.
+                    return;
+                }
+
+                Logger.Log(
+                    LogEventLevel.Warning,
+                    LogArea.Binding,
+                    this,
+                    "Error in binding to {Target}.{Property}: {Message}",
+                    this,
+                    property,
+                    e.Message);
+            }
+        }
+
         /// <summary>
         /// Gets the visual offset from the specified ancestor.
         /// </summary>

+ 16 - 0
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_Inheritance.cs

@@ -1,6 +1,7 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System.Collections.Generic;
 using Xunit;
 
 namespace Avalonia.Base.UnitTests
@@ -115,6 +116,21 @@ namespace Avalonia.Base.UnitTests
             Assert.True(raised);
         }
 
+        [Fact]
+        public void PropertyChanged_Is_Raised_In_Parent_Before_Child()
+        {
+            var parent = new Class1();
+            var child = new Class2 { Parent = parent };
+            var result = new List<object>();
+
+            parent.PropertyChanged += (s, e) => result.Add(parent);
+            child.PropertyChanged += (s, e) => result.Add(child);
+
+            parent.SetValue(Class1.BazProperty, "changed");
+
+            Assert.Equal(new[] { parent, child }, result);
+        }
+
         private class Class1 : AvaloniaObject
         {
             public static readonly StyledProperty<string> FooProperty =

+ 1 - 1
tests/Avalonia.Controls.UnitTests/HeaderedItemsControlTests .cs

@@ -37,7 +37,7 @@ namespace Avalonia.Controls.UnitTests
 
             target.Header = "Foo";
             target.ApplyTemplate();
-            target.HeaderPresenter.UpdateChild();
+            ((ContentPresenter)target.HeaderPresenter).UpdateChild();
 
             var child = target.HeaderPresenter.Child;
 

+ 68 - 0
tests/Avalonia.Controls.UnitTests/ListBoxTests.cs

@@ -267,6 +267,74 @@ namespace Avalonia.Controls.UnitTests
             Assert.True(true);
         }
 
+        [Fact]
+        public void LayoutManager_Should_Measure_Arrange_All()
+        {
+            var virtualizationMode = ItemVirtualizationMode.Simple;
+            using (UnitTestApplication.Start(TestServices.StyledWindow))
+            {
+                var items = new AvaloniaList<string>(Enumerable.Range(1, 7).Select(v => v.ToString()));
+
+                var wnd = new Window() { SizeToContent = SizeToContent.WidthAndHeight };
+
+                wnd.IsVisible = true;
+
+                var target = new ListBox();
+
+                wnd.Content = target;
+
+                var lm = wnd.LayoutManager;
+
+                target.Height = 110;
+                target.Width = 50;
+                target.DataContext = items;
+                target.VirtualizationMode = virtualizationMode;
+
+                target.ItemTemplate = new FuncDataTemplate<object>(c =>
+                {
+                    var tb = new TextBlock() { Height = 10, Width = 30 };
+                    tb.Bind(TextBlock.TextProperty, new Data.Binding());
+                    return tb;
+                }, true);
+
+                lm.ExecuteInitialLayoutPass(wnd);
+
+                target.Items = items;
+
+                lm.ExecuteLayoutPass();
+
+                items.Insert(3, "3+");
+                lm.ExecuteLayoutPass();
+
+                items.Insert(4, "4+");
+                lm.ExecuteLayoutPass();
+
+                //RESET
+                items.Clear();
+                foreach (var i in Enumerable.Range(1, 7))
+                {
+                    items.Add(i.ToString());
+                }
+
+                //working bit better with this line no outof memory or remaining to arrange/measure ???
+                //lm.ExecuteLayoutPass();
+
+                items.Insert(2, "2+");
+
+                lm.ExecuteLayoutPass();
+                //after few more layout cycles layoutmanager shouldn't hold any more visual for measure/arrange
+                lm.ExecuteLayoutPass();
+                lm.ExecuteLayoutPass();
+
+                var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
+                var toMeasure = lm.GetType().GetField("_toMeasure", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
+                var toArrange = lm.GetType().GetField("_toArrange", flags).GetValue(lm) as System.Collections.Generic.IEnumerable<Layout.ILayoutable>;
+
+                Assert.Equal(0, toMeasure.Count());
+                Assert.Equal(0, toArrange.Count());
+            }
+        }
+
         private FuncControlTemplate ListBoxTemplate()
         {
             return new FuncControlTemplate<ListBox>(parent =>

+ 27 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_InTemplate.cs

@@ -4,6 +4,7 @@
 using System.Linq;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Templates;
+using Avalonia.Data;
 using Avalonia.LogicalTree;
 using Avalonia.UnitTests;
 using Avalonia.VisualTree;
@@ -266,6 +267,31 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.IsType<Canvas>(target.Child);
         }
 
+
+        [Fact]
+        public void Should_Not_Bind_Old_Child_To_New_DataContext()
+        {
+            // Test for issue #1099.
+            var textBlock = new TextBlock
+            {
+                [!TextBlock.TextProperty] = new Binding(),
+            };
+
+            var (target, host) = CreateTarget();
+            host.DataTemplates.Add(new FuncDataTemplate<string>(x => textBlock));
+            host.DataTemplates.Add(new FuncDataTemplate<int>(x => new Canvas()));
+
+            target.Content = "foo";
+            Assert.Same(textBlock, target.Child);
+
+            textBlock.PropertyChanged += (s, e) =>
+            {
+                Assert.NotEqual(e.NewValue, "42");
+            };
+
+            target.Content = 42;
+        }
+
         (ContentPresenter presenter, ContentControl templatedParent) CreateTarget()
         {
             var templatedParent = new ContentControl
@@ -288,4 +314,4 @@ namespace Avalonia.Controls.UnitTests.Presenters
             public IControl Child { get; set; }
         }
     }
-}
+}

+ 31 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ContentPresenterTests_Standalone.cs

@@ -14,6 +14,7 @@ using System.Linq;
 using Xunit;
 using Avalonia.Rendering;
 using Avalonia.Media;
+using Avalonia.Data;
 
 namespace Avalonia.Controls.UnitTests.Presenters
 {
@@ -204,7 +205,6 @@ namespace Avalonia.Controls.UnitTests.Presenters
             Assert.NotEqual(foo, logicalChildren.First());
         }
 
-
         [Fact]
         public void Changing_Background_Brush_Color_Should_Invalidate_Visual()
         {
@@ -221,5 +221,35 @@ namespace Avalonia.Controls.UnitTests.Presenters
 
             renderer.Verify(x => x.AddDirty(target), Times.Once);
         }
+
+        [Fact]
+        public void Should_Not_Bind_Old_Child_To_New_DataContext()
+        {
+            // Test for issue #1099.
+            var textBlock = new TextBlock
+            {
+                [!TextBlock.TextProperty] = new Binding(),
+            };
+
+            var target = new ContentPresenter()
+            {
+                DataTemplates =
+                {
+                    new FuncDataTemplate<string>(x => textBlock),
+                    new FuncDataTemplate<int>(x => new Canvas()),
+                },
+            };
+
+            var root = new TestRoot(target);
+            target.Content = "foo";
+            Assert.Same(textBlock, target.Child);
+
+            textBlock.PropertyChanged += (s, e) =>
+            {
+                Assert.NotEqual(e.NewValue, "42");
+            };
+
+            target.Content = 42;
+        }
     }
 }

+ 110 - 9
tests/Avalonia.Layout.UnitTests/LayoutManagerTests.cs

@@ -1,11 +1,10 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
+using System.Collections.Generic;
+using System.Linq;
 using Avalonia.Controls;
-using Avalonia.UnitTests;
-using System;
 using Xunit;
-using System.Collections.Generic;
 
 namespace Avalonia.Layout.UnitTests
 {
@@ -74,7 +73,6 @@ namespace Avalonia.Layout.UnitTests
                 }
             };
 
-
             var order = new List<ILayoutable>();
             Size MeasureOverride(ILayoutable control, Size size)
             {
@@ -110,7 +108,6 @@ namespace Avalonia.Layout.UnitTests
                 }
             };
 
-
             var order = new List<ILayoutable>();
             Size MeasureOverride(ILayoutable control, Size size)
             {
@@ -196,9 +193,9 @@ namespace Avalonia.Layout.UnitTests
                 Width = 100,
                 Height = 100,
             };
- 
+
             var arrangeSize = default(Size);
- 
+
             root.DoArrangeOverride = (_, s) =>
             {
                 arrangeSize = s;
@@ -207,7 +204,7 @@ namespace Avalonia.Layout.UnitTests
 
             root.LayoutManager.ExecuteInitialLayoutPass(root);
             Assert.Equal(new Size(100, 100), arrangeSize);
- 
+
             root.Width = 120;
 
             root.LayoutManager.ExecuteLayoutPass();
@@ -238,7 +235,111 @@ namespace Avalonia.Layout.UnitTests
             border.Height = 100;
 
             root.LayoutManager.ExecuteLayoutPass();
-            Assert.Equal(new Size(100, 100), panel.DesiredSize);             
+            Assert.Equal(new Size(100, 100), panel.DesiredSize);
+        }
+
+        [Fact]
+        public void LayoutManager_Should_Prevent_Infinite_Loop_On_Measure()
+        {
+            var control = new LayoutTestControl();
+            var root = new LayoutTestRoot { Child = control };
+
+            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            control.Measured = false;
+
+            int cnt = 0;
+            int maxcnt = 100;
+            control.DoMeasureOverride = (l, s) =>
+            {
+                //emulate a problem in the logic of a control that triggers
+                //invalidate measure during measure
+                //it can lead to an infinite loop in layoutmanager
+                if (++cnt < maxcnt)
+                {
+                    control.InvalidateMeasure();
+                }
+
+                return new Size(100, 100);
+            };
+
+            control.InvalidateMeasure();
+
+            root.LayoutManager.ExecuteLayoutPass();
+
+            Assert.True(cnt < 100);
+        }
+
+        [Fact]
+        public void LayoutManager_Should_Prevent_Infinite_Loop_On_Arrange()
+        {
+            var control = new LayoutTestControl();
+            var root = new LayoutTestRoot { Child = control };
+
+            root.LayoutManager.ExecuteInitialLayoutPass(root);
+            control.Arranged = false;
+
+            int cnt = 0;
+            int maxcnt = 100;
+            control.DoArrangeOverride = (l, s) =>
+            {
+                //emulate a problem in the logic of a control that triggers
+                //invalidate measure during arrange
+                //it can lead to infinity loop in layoutmanager
+                if (++cnt < maxcnt)
+                {
+                    control.InvalidateArrange();
+                }
+
+                return new Size(100, 100);
+            };
+
+            control.InvalidateArrange();
+
+            root.LayoutManager.ExecuteLayoutPass();
+
+            Assert.True(cnt < 100);
+        }
+
+        [Fact]
+        public void LayoutManager_Should_Properly_Arrange_Visuals_Even_When_There_Are_Issues_With_Previous_Arranged()
+        {
+            var nonArrageableTargets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
+            var targets = Enumerable.Range(1, 10).Select(_ => new LayoutTestControl()).ToArray();
+
+            StackPanel panel;
+
+            var root = new LayoutTestRoot
+            {
+                Child = panel = new StackPanel()
+            };
+
+            panel.Children.AddRange(nonArrageableTargets);
+            panel.Children.AddRange(targets);
+
+            root.LayoutManager.ExecuteInitialLayoutPass(root);
+
+            foreach (var c in panel.Children.OfType<LayoutTestControl>())
+            {
+                c.Measured = c.Arranged = false;
+                c.InvalidateMeasure();
+            }
+
+            foreach (var c in nonArrageableTargets)
+            {
+                c.DoArrangeOverride = (l, s) =>
+                {
+                    //emulate a problem in the logic of a control that triggers
+                    //invalidate measure during arrange
+                    c.InvalidateMeasure();
+                    return new Size(100, 100);
+                };
+            }
+
+            root.LayoutManager.ExecuteLayoutPass();
+
+            //altough nonArrageableTargets has rubbish logic and can't be measured/arranged properly
+            //layoutmanager should process properly other visuals
+            Assert.All(targets, c => Assert.True(c.Arranged));
         }
     }
 }

+ 196 - 0
tests/Avalonia.Layout.UnitTests/LayoutQueueTests.cs

@@ -0,0 +1,196 @@
+using System.Collections.Generic;
+using System.Linq;
+using Xunit;
+
+namespace Avalonia.Layout.UnitTests
+{
+    public class LayoutQueueTests
+    {
+        [Fact]
+        public void Should_Enqueue()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+            var refQueue = new Queue<string>();
+            var items = new[] { "1", "2", "3" };
+
+            foreach (var item in items)
+            {
+                target.Enqueue(item);
+                refQueue.Enqueue(item);
+            }
+
+            Assert.Equal(refQueue, target);
+        }
+
+        [Fact]
+        public void Should_Dequeue()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+            var refQueue = new Queue<string>();
+            var items = new[] { "1", "2", "3" };
+
+            foreach (var item in items)
+            {
+                target.Enqueue(item);
+                refQueue.Enqueue(item);
+            }
+
+            while (refQueue.Count > 0)
+            {
+                Assert.Equal(refQueue.Dequeue(), target.Dequeue());
+            }
+        }
+
+        [Fact]
+        public void Should_Enqueue_UniqueElements()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+
+            var items = new[] { "1", "2", "3", "1" };
+
+            foreach (var item in items)
+            {
+                target.Enqueue(item);
+            }
+
+            Assert.Equal(3, target.Count);
+            Assert.Equal(items.Take(3), target);
+        }
+
+        [Fact]
+        public void Shouldnt_Enqueue_More_Than_Limit_In_Loop()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+
+            //1
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.BeginLoop(3);
+
+            target.Dequeue();
+
+            //2
+            target.Enqueue("Foo");
+            target.Dequeue();
+
+            //3
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.Dequeue();
+
+            //4 more than limit shouldn't be added
+            target.Enqueue("Foo");
+
+            Assert.Equal(0, target.Count);
+        }
+
+        [Fact]
+        public void Shouldnt_Count_Unique_Enqueue_For_Limit_In_Loop()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+
+            //1
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.BeginLoop(3);
+
+            target.Dequeue();
+
+            //2
+            target.Enqueue("Foo");
+            target.Enqueue("Foo");
+            target.Dequeue();
+
+            //3
+            target.Enqueue("Foo");
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.Dequeue();
+
+            //4 more than limit shouldn't be added
+            target.Enqueue("Foo");
+
+            Assert.Equal(0, target.Count);
+        }
+
+        [Fact]
+        public void Should_Enqueue_When_Condition_True_After_Loop_When_Limit_Met()
+        {
+            var target = new LayoutQueue<string>(_ => true);
+
+            //1
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.BeginLoop(3);
+
+            target.Dequeue();
+
+            //2
+            target.Enqueue("Foo");
+            target.Dequeue();
+
+            //3
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.Dequeue();
+
+            //4 more than limit shouldn't be added to queue
+            target.Enqueue("Foo");
+
+            Assert.Equal(0, target.Count);
+
+            target.EndLoop();
+
+            //after loop should be added once
+            Assert.Equal(1, target.Count);
+            Assert.Equal("Foo", target.First());
+        }
+
+        [Fact]
+        public void Shouldnt_Enqueue_When_Condition_False_After_Loop_When_Limit_Met()
+        {
+            var target = new LayoutQueue<string>(_ => false);
+
+            //1
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.BeginLoop(3);
+
+            target.Dequeue();
+
+            //2
+            target.Enqueue("Foo");
+            target.Dequeue();
+
+            //3
+            target.Enqueue("Foo");
+
+            Assert.Equal(1, target.Count);
+
+            target.Dequeue();
+
+            //4 more than limit shouldn't be added
+            target.Enqueue("Foo");
+
+            Assert.Equal(0, target.Count);
+
+            target.EndLoop();
+
+            Assert.Equal(0, target.Count);
+        }
+    }
+}

+ 1 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@@ -89,6 +89,7 @@ namespace Avalonia.Styling.UnitTests
             }
 
             public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
+            public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
             public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
             public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
 

+ 1 - 0
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@@ -119,6 +119,7 @@ namespace Avalonia.Styling.UnitTests
             }
 
             public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
+            public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
             public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
             public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
 

+ 1 - 0
tests/Avalonia.Styling.UnitTests/TestControlBase.cs

@@ -19,6 +19,7 @@ namespace Avalonia.Styling.UnitTests
 
 #pragma warning disable CS0067 // Event not used
         public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
+        public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
 #pragma warning restore CS0067
 
         public string Name { get; set; }

+ 1 - 0
tests/Avalonia.Styling.UnitTests/TestTemplatedControl.cs

@@ -12,6 +12,7 @@ namespace Avalonia.Styling.UnitTests
     public abstract class TestTemplatedControl : ITemplatedControl, IStyleable
     {
         public event EventHandler<AvaloniaPropertyChangedEventArgs> PropertyChanged;
+        public event EventHandler<AvaloniaPropertyChangedEventArgs> InheritablePropertyChanged;
 
         public abstract Classes Classes
         {

+ 4 - 1
tests/Avalonia.UnitTests/TestTemplatedRoot.cs

@@ -18,7 +18,10 @@ namespace Avalonia.UnitTests
 
         public TestTemplatedRoot()
         {
-            Template = new FuncControlTemplate<TestTemplatedRoot>(x => new ContentPresenter());
+            Template = new FuncControlTemplate<TestTemplatedRoot>(x => new ContentPresenter
+            {
+                Name = "PART_ContentPresenter",
+            });
         }
 
         public event EventHandler<NameScopeEventArgs> Registered

+ 46 - 0
tests/Avalonia.Visuals.UnitTests/VisualTests.cs

@@ -5,6 +5,7 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.Controls;
+using Avalonia.Data;
 using Avalonia.Media;
 using Avalonia.Rendering;
 using Avalonia.UnitTests;
@@ -236,5 +237,50 @@ namespace Avalonia.Visuals.UnitTests
             //child is centered (400 - 100*2 scale)/2
             Assert.Equal(new Point(100, 100), point);
         }
+
+        [Fact]
+        public void Should_Not_Log_Binding_Error_When_Not_Attached_To_Logical_Tree()
+        {
+            var target = new Decorator { DataContext = "foo" };
+            var called = false;
+
+            LogCallback checkLogMessage = (level, area, src, mt, pv) =>
+            {
+                if (level >= Logging.LogEventLevel.Warning)
+                {
+                    called = true;
+                }
+            };
+
+            using (TestLogSink.Start(checkLogMessage))
+            {
+                target.Bind(Decorator.TagProperty, new Binding("Foo"));
+            }
+
+            Assert.False(called);
+        }
+
+        [Fact]
+        public void Should_Log_Binding_Error_When_Attached_To_Logical_Tree()
+        {
+            var target = new Decorator();
+            var root = new TestRoot { Child = target, DataContext = "foo" };
+            var called = false;
+
+            LogCallback checkLogMessage = (level, area, src, mt, pv) =>
+            {
+                if (level >= Logging.LogEventLevel.Warning)
+                {
+                    called = true;
+                }
+            };
+
+            using (TestLogSink.Start(checkLogMessage))
+            {
+                target.Bind(Decorator.TagProperty, new Binding("Foo"));
+            }
+
+            Assert.True(called);
+        }
     }
 }