Browse Source

Move the styling system up the stack to live right below Avalonia.Animation since it has no dependencies on anything further down the dependency chain

Jeremy Koritzinsky 7 years ago
parent
commit
da0ab8b681
36 changed files with 1028 additions and 962 deletions
  1. 1 1
      src/Avalonia.Base/AvaloniaObject.cs
  2. 4 750
      src/Avalonia.Controls/Control.cs
  3. 3 3
      src/Avalonia.Controls/HotkeyManager.cs
  4. 2 28
      src/Avalonia.Controls/IControl.cs
  5. 14 10
      src/Avalonia.Controls/Utils/AncestorFinder.cs
  6. 8 8
      src/Avalonia.Styling/Controls/Classes.cs
  7. 0 0
      src/Avalonia.Styling/Controls/IPseudoClasses.cs
  8. 1 1
      src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
  9. 1 1
      src/Avalonia.Styling/Controls/ISetLogicalParent.cs
  10. 18 18
      src/Avalonia.Styling/Controls/NameScope.cs
  11. 1 2
      src/Avalonia.Styling/Controls/NameScopeExtensions.cs
  12. 0 0
      src/Avalonia.Styling/INamed.cs
  13. 42 0
      src/Avalonia.Styling/IStyledElement.cs
  14. 1 52
      src/Avalonia.Styling/LogicalTree/ControlLocator.cs
  15. 783 0
      src/Avalonia.Styling/StyledElement.cs
  16. 14 0
      src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs
  17. 1 0
      src/Avalonia.Styling/Styling/IStyleable.cs
  18. 2 2
      src/Avalonia.Styling/Styling/Setter.cs
  19. 0 15
      src/Avalonia.Styling/app.config
  20. 2 1
      src/Avalonia.Visuals/Avalonia.Visuals.csproj
  21. 1 1
      src/Avalonia.Visuals/Visual.cs
  22. 46 0
      src/Avalonia.Visuals/VisualTree/VisualLocator.cs
  23. 1 1
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
  24. 0 2
      src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
  25. 17 24
      src/Markup/Avalonia.Markup/Data/Binding.cs
  26. 12 13
      src/Markup/Avalonia.Markup/Data/DelayedBinding.cs
  27. 16 1
      src/Markup/Avalonia.Markup/Data/RelativeSource.cs
  28. 2 2
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  29. 1 1
      tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
  30. 1 1
      tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
  31. 14 6
      tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs
  32. 1 1
      tests/Avalonia.Controls.UnitTests/Utils/AncestorFinderTests.cs
  33. 1 1
      tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
  34. 9 8
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
  35. 2 2
      tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs
  36. 6 6
      tests/Avalonia.Styling.UnitTests/StyledElementTests_Resources.cs

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

@@ -703,7 +703,7 @@ namespace Avalonia
         /// <returns>The default value.</returns>
         private object GetDefaultValue(AvaloniaProperty property)
         {
-            if (property.Inherits && _inheritanceParent is AvaloniaObject aobj)
+            if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
                 return aobj.GetValueInternal(property);
             return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
         }

+ 4 - 750
src/Avalonia.Controls/Control.cs

@@ -29,53 +29,23 @@ namespace Avalonia.Controls
     /// <remarks>
     /// The control class extends <see cref="InputElement"/> and adds the following features:
     ///
-    /// - An inherited <see cref="DataContext"/>.
     /// - A <see cref="Tag"/> property to allow user-defined data to be attached to the control.
-    /// - A collection of class strings for custom styling.
-    /// - Implements <see cref="IStyleable"/> to allow styling to work on the control.
-    /// - Implements <see cref="ILogical"/> to form part of a logical tree.
+
     /// </remarks>
-    public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize
+    public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInStyle
     {
-        /// <summary>
-        /// Defines the <see cref="DataContext"/> property.
-        /// </summary>
-        public static readonly StyledProperty<object> DataContextProperty =
-            AvaloniaProperty.Register<Control, object>(
-                nameof(DataContext), 
-                inherits: true,
-                notifying: DataContextNotifying);
-
         /// <summary>
         /// Defines the <see cref="FocusAdorner"/> property.
         /// </summary>
         public static readonly StyledProperty<ITemplate<IControl>> FocusAdornerProperty =
             AvaloniaProperty.Register<Control, ITemplate<IControl>>(nameof(FocusAdorner));
 
-        /// <summary>
-        /// Defines the <see cref="Name"/> property.
-        /// </summary>
-        public static readonly DirectProperty<Control, string> NameProperty =
-            AvaloniaProperty.RegisterDirect<Control, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
-
-        /// <summary>
-        /// Defines the <see cref="Parent"/> property.
-        /// </summary>
-        public static readonly DirectProperty<Control, IControl> ParentProperty =
-            AvaloniaProperty.RegisterDirect<Control, IControl>(nameof(Parent), o => o.Parent);
-
         /// <summary>
         /// Defines the <see cref="Tag"/> property.
         /// </summary>
         public static readonly StyledProperty<object> TagProperty =
             AvaloniaProperty.Register<Control, object>(nameof(Tag));
-
-        /// <summary>
-        /// Defines the <see cref="TemplatedParent"/> property.
-        /// </summary>
-        public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
-            AvaloniaProperty.Register<Control, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
-
+        
         /// <summary>
         /// Defines the <see cref="ContextMenu"/> property.
         /// </summary>
@@ -88,20 +58,8 @@ namespace Avalonia.Controls
         public static readonly RoutedEvent<RequestBringIntoViewEventArgs> RequestBringIntoViewEvent =
             RoutedEvent.Register<Control, RequestBringIntoViewEventArgs>("RequestBringIntoView", RoutingStrategies.Bubble);
 
-        private int _initCount;
-        private string _name;
-        private IControl _parent;
-        private readonly Classes _classes = new Classes();
         private DataTemplates _dataTemplates;
         private IControl _focusAdorner;
-        private bool _isAttachedToLogicalTree;
-        private IAvaloniaList<ILogical> _logicalChildren;
-        private INameScope _nameScope;
-        private IResourceDictionary _resources;
-        private Styles _styles;
-        private bool _styled;
-        private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
-        private bool _dataContextUpdating;
 
         /// <summary>
         /// Initializes static members of the <see cref="Control"/> class.
@@ -112,127 +70,6 @@ namespace Avalonia.Controls
             PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
             PseudoClass(IsFocusedProperty, ":focus");
             PseudoClass(IsPointerOverProperty, ":pointerover");
-            DataContextProperty.Changed.AddClassHandler<Control>(x => x.OnDataContextChangedCore);
-        }
-
-        /// <summary>
-        /// Initializes a new instance of the <see cref="Control"/> class.
-        /// </summary>
-        public Control()
-        {
-            _nameScope = this as INameScope;
-            _isAttachedToLogicalTree = this is IStyleRoot;
-        }
-
-        /// <summary>
-        /// Raised when the control is attached to a rooted logical tree.
-        /// </summary>
-        public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
-
-        /// <summary>
-        /// Raised when the control is detached from a rooted logical tree.
-        /// </summary>
-        public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
-
-        /// <summary>
-        /// Occurs when the <see cref="DataContext"/> property changes.
-        /// </summary>
-        /// <remarks>
-        /// This event will be raised when the <see cref="DataContext"/> property has changed and
-        /// all subscribers to that change have been notified.
-        /// </remarks>
-        public event EventHandler DataContextChanged;
-
-        /// <summary>
-        /// Occurs when the control has finished initialization.
-        /// </summary>
-        /// <remarks>
-        /// The Initialized event indicates that all property values on the control have been set.
-        /// When loading the control from markup, it occurs when 
-        /// <see cref="ISupportInitialize.EndInit"/> is called *and* the control
-        /// is attached to a rooted logical tree. When the control is created by code and
-        /// <see cref="ISupportInitialize"/> is not used, it is called when the control is attached
-        /// to the visual tree.
-        /// </remarks>
-        public event EventHandler Initialized;
-
-        /// <summary>
-        /// Occurs when a resource in this control or a parent control has changed.
-        /// </summary>
-        public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
-
-        /// <summary>
-        /// Gets or sets the name of the control.
-        /// </summary>
-        /// <remarks>
-        /// An element's name is used to uniquely identify a control within the control's name
-        /// scope. Once the element is added to a logical tree, its name cannot be changed.
-        /// </remarks>
-        public string Name
-        {
-            get
-            {
-                return _name;
-            }
-
-            set
-            {
-                if (String.IsNullOrWhiteSpace(value))
-                {
-                    throw new InvalidOperationException("Cannot set Name to null or empty string.");
-                }
-
-                if (_styled)
-                {
-                    throw new InvalidOperationException("Cannot set Name : control already styled.");
-                }
-
-                _name = value;
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the control's classes.
-        /// </summary>
-        /// <remarks>
-        /// <para>
-        /// Classes can be used to apply user-defined styling to controls, or to allow controls
-        /// that share a common purpose to be easily selected.
-        /// </para>
-        /// <para>
-        /// 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 addds the contents of the assigned
-        /// collection.
-        /// </para>
-        /// </remarks>
-        public Classes Classes
-        {
-            get
-            {
-                return _classes;
-            }
-
-            set
-            {
-                if (_classes != value)
-                {
-                    _classes.Replace(value);
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets or sets the control's data context.
-        /// </summary>
-        /// <remarks>
-        /// The data context is an inherited property that specifies the default object that will
-        /// be used for data binding.
-        /// </remarks>
-        public object DataContext
-        {
-            get { return GetValue(DataContextProperty); }
-            set { SetValue(DataContextProperty, value); }
         }
 
         /// <summary>
@@ -253,55 +90,6 @@ namespace Avalonia.Controls
         /// </remarks>
         public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
 
-        /// <summary>
-        /// Gets a value that indicates whether the element has finished initialization.
-        /// </summary>
-        /// <remarks>
-        /// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
-        /// event.
-        /// </remarks>
-        public bool IsInitialized { get; private set; }
-
-        /// <summary>
-        /// Gets the styles for the control.
-        /// </summary>
-        /// <remarks>
-        /// Styles for the entire application are added to the Application.Styles collection, but
-        /// each control may in addition define its own styles which are applied to the control
-        /// itself and its children.
-        /// </remarks>
-        public Styles Styles
-        {
-            get { return _styles ?? (Styles = new Styles()); }
-            set
-            {
-                Contract.Requires<ArgumentNullException>(value != null);
-
-                if (_styles != value)
-                {
-                    if (_styles != null)
-                    {
-                        (_styles as ISetStyleParent)?.SetParent(null);
-                        _styles.ResourcesChanged -= ThisResourcesChanged;
-                    }
-
-                    _styles = value;
-
-                    if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
-                    {
-                        setParent.SetParent(this);
-                    } 
-
-                    _styles.ResourcesChanged += ThisResourcesChanged;
-                }
-            }
-        }
-
-        /// <summary>
-        /// Gets the control's logical parent.
-        /// </summary>
-        public IControl Parent => _parent;
-
         /// <summary>
         /// Gets or sets a context menu to the control.
         /// </summary>
@@ -311,34 +99,6 @@ namespace Avalonia.Controls
             set { SetValue(ContextMenuProperty, value); }
         }
 
-        /// <summary>
-        /// Gets or sets the control's resource dictionary.
-        /// </summary>
-        public IResourceDictionary Resources
-        {
-            get => _resources ?? (Resources = new ResourceDictionary());
-            set
-            {
-                Contract.Requires<ArgumentNullException>(value != null);
-
-                var hadResources = false;
-
-                if (_resources != null)
-                {
-                    hadResources = _resources.Count > 0;
-                    _resources.ResourcesChanged -= ThisResourcesChanged;
-                }
-
-                _resources = value;
-                _resources.ResourcesChanged += ThisResourcesChanged;
-
-                if (hadResources || _resources.Count > 0)
-                {
-                    ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
-                }
-            }
-        }
-
         /// <summary>
         /// Gets or sets a user-defined object attached to the control.
         /// </summary>
@@ -348,226 +108,11 @@ namespace Avalonia.Controls
             set { SetValue(TagProperty, value); }
         }
 
-        /// <summary>
-        /// Gets the control whose lookless template this control is part of.
-        /// </summary>
-        public ITemplatedControl TemplatedParent
-        {
-            get { return GetValue(TemplatedParentProperty); }
-            internal set { SetValue(TemplatedParentProperty, value); }
-        }
-
-        /// <summary>
-        /// Gets the control's logical children.
-        /// </summary>
-        protected IAvaloniaList<ILogical> LogicalChildren
-        {
-            get
-            {
-                if (_logicalChildren == null)
-                {
-                    var list = new AvaloniaList<ILogical>();
-                    list.ResetBehavior = ResetBehavior.Remove;
-                    list.Validate = ValidateLogicalChild;
-                    list.CollectionChanged += LogicalChildrenCollectionChanged;
-                    _logicalChildren = list;
-                }
-
-                return _logicalChildren;
-            }
-        }
+        public new IControl Parent => (IControl)base.Parent;
 
         /// <inheritdoc/>
         bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
 
-        /// <summary>
-        /// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
-        /// pseudoclasses.
-        /// </summary>
-        protected IPseudoClasses PseudoClasses => Classes;
-
-        /// <summary>
-        /// Gets a value indicating whether the element is attached to a rooted logical tree.
-        /// </summary>
-        bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
-
-        /// <summary>
-        /// Gets the control's logical parent.
-        /// </summary>
-        ILogical ILogical.LogicalParent => Parent;
-
-        /// <summary>
-        /// Gets the control's logical children.
-        /// </summary>
-        IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
-
-        /// <inheritdoc/>
-        bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
-
-        /// <inheritdoc/>
-        IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
-
-        /// <inheritdoc/>
-        IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
-
-        /// <summary>
-        /// Gets the type by which the control is styled.
-        /// </summary>
-        /// <remarks>
-        /// Usually controls are styled by their own type, but there are instances where you want
-        /// a control to be styled by its base type, e.g. creating SpecialButton that
-        /// derives from Button and adds extra functionality but is still styled as a regular
-        /// Button.
-        /// </remarks>
-        Type IStyleable.StyleKey => GetType();
-
-        /// <inheritdoc/>
-        IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
-
-        /// <inheritdoc/>
-        bool IStyleHost.IsStylesInitialized => _styles != null;
-
-        /// <inheritdoc/>
-        IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
-
-        /// <inheritdoc/>
-        public virtual void BeginInit()
-        {
-            ++_initCount;
-        }
-
-        /// <inheritdoc/>
-        public virtual void EndInit()
-        {
-            if (_initCount == 0)
-            {
-                throw new InvalidOperationException("BeginInit was not called.");
-            }
-
-            if (--_initCount == 0 && _isAttachedToLogicalTree)
-            {
-                InitializeStylesIfNeeded();
-
-                InitializeIfNeeded();
-            }
-        }
-
-        private void InitializeStylesIfNeeded(bool force = false)
-        {
-            if (_initCount == 0 && (!_styled || force))
-            {
-                RegisterWithNameScope();
-                ApplyStyling();
-                _styled = true;
-            }
-        }
-
-        private void InitializeIfNeeded()
-        {
-            if (_initCount == 0 && !IsInitialized)
-            {
-                IsInitialized = true;
-                Initialized?.Invoke(this, EventArgs.Empty);
-            }
-        }
-
-        /// <inheritdoc/>
-        void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-            this.OnAttachedToLogicalTreeCore(e);
-        }
-
-        /// <inheritdoc/>
-        void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-            this.OnDetachedFromLogicalTreeCore(e);
-        }
-
-        /// <inheritdoc/>
-        void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
-        {
-            ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
-        }
-
-        /// <inheritdoc/>
-        bool IResourceProvider.TryGetResource(string key, out object value)
-        {
-            value = null;
-            return (_resources?.TryGetResource(key, out value) ?? false) ||
-                   (_styles?.TryGetResource(key, out value) ?? false);
-        }
-
-        /// <summary>
-        /// Sets the control's logical parent.
-        /// </summary>
-        /// <param name="parent">The parent.</param>
-        void ISetLogicalParent.SetParent(ILogical parent)
-        {
-            var old = Parent;
-
-            if (parent != old)
-            {
-                if (old != null && parent != null)
-                {
-                    throw new InvalidOperationException("The Control already has a parent.");
-                }
-
-                if (_isAttachedToLogicalTree)
-                {
-                    var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
-
-                    if (oldRoot == null)
-                    {
-                        throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
-                    }
-
-                    var e = new LogicalTreeAttachmentEventArgs(oldRoot);
-                    OnDetachedFromLogicalTreeCore(e);
-                }
-
-                if (InheritanceParent == null || parent == null)
-                {
-                    InheritanceParent = parent as AvaloniaObject;
-                }
-
-                _parent = (IControl)parent;
-
-                if (old != null)
-                {
-                    old.ResourcesChanged -= ThisResourcesChanged; 
-                }
-                if (_parent != null)
-                {
-                    _parent.ResourcesChanged += ThisResourcesChanged; 
-                }
-                ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
-
-                if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
-                {
-                    var newRoot = FindStyleRoot(this);
-
-                    if (newRoot == null)
-                    {
-                        throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root.");
-                    }
-
-                    var e = new LogicalTreeAttachmentEventArgs(newRoot);
-                    OnAttachedToLogicalTreeCore(e);
-                }
-
-                RaisePropertyChanged(ParentProperty, old, _parent, BindingPriority.LocalValue);
-            }
-        }
-
-        /// <summary>
-        /// Sets the control's inheritance parent.
-        /// </summary>
-        /// <param name="parent">The parent.</param>
-        void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
-        {
-            InheritanceParent = parent;
-        }
-
         /// <inheritdoc/>
         void IVisualBrushInitialize.EnsureInitialized()
         {
@@ -600,52 +145,6 @@ namespace Avalonia.Controls
             }
         }
 
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property is true.
-        /// </summary>
-        /// <param name="property">The property.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
-        {
-            PseudoClass(property, x => x, className);
-        }
-
-        /// <summary>
-        /// Adds a pseudo-class to be set when a property equals a certain value.
-        /// </summary>
-        /// <typeparam name="T">The type of the property.</typeparam>
-        /// <param name="property">The property.</param>
-        /// <param name="selector">Returns a boolean value based on the property value.</param>
-        /// <param name="className">The pseudo-class.</param>
-        protected static void PseudoClass<T>(
-            AvaloniaProperty<T> property,
-            Func<T, bool> selector,
-            string className)
-        {
-            Contract.Requires<ArgumentNullException>(property != null);
-            Contract.Requires<ArgumentNullException>(selector != null);
-            Contract.Requires<ArgumentNullException>(className != null);
-
-            if (string.IsNullOrWhiteSpace(className))
-            {
-                throw new ArgumentException("Cannot supply an empty className.");
-            }
-
-            property.Changed.Merge(property.Initialized)
-                .Where(e => e.Sender is Control)
-                .Subscribe(e =>
-                {
-                    if (selector((T)e.NewValue))
-                    {
-                        ((Control)e.Sender).PseudoClasses.Add(className);
-                    }
-                    else
-                    {
-                        ((Control)e.Sender).PseudoClasses.Remove(className);
-                    }
-                });
-        }
-
         /// <summary>
         /// Gets the element that recieves the focus adorner.
         /// </summary>
@@ -655,22 +154,6 @@ namespace Avalonia.Controls
             return this;
         }
 
-        /// <summary>
-        /// Called when the control is added to a rooted logical tree.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-        }
-
-        /// <summary>
-        /// Called when the control is removed from a rooted logical tree.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
-        {
-        }
-
         /// <inheritdoc/>
         protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
         {
@@ -685,29 +168,6 @@ namespace Avalonia.Controls
             base.OnDetachedFromVisualTreeCore(e);
         }
 
-        /// <summary>
-        /// Called when the <see cref="DataContext"/> property changes.
-        /// </summary>
-        /// <param name="e">The event args.</param>
-        protected virtual void OnDataContextChanged(EventArgs e)
-        {
-            DataContextChanged?.Invoke(this, EventArgs.Empty);
-        }
-
-        /// <summary>
-        /// Called when the <see cref="DataContext"/> begins updating.
-        /// </summary>
-        protected virtual void OnDataContextBeginUpdate()
-        {
-        }
-
-        /// <summary>
-        /// Called when the <see cref="DataContext"/> finishes updating.
-        /// </summary>
-        protected virtual void OnDataContextEndUpdate()
-        {
-        }
-
         /// <inheritdoc/>
         protected override void OnGotFocus(GotFocusEventArgs e)
         {
@@ -757,211 +217,5 @@ namespace Avalonia.Controls
                 _focusAdorner = null;
             }
         }
-
-        private static void DataContextNotifying(IAvaloniaObject o, bool notifying)
-        {
-            if (o is Control control)
-            {
-                DataContextNotifying(control, notifying);
-            }
-        }
-
-        private static void DataContextNotifying(Control control, bool notifying)
-        {
-            if (notifying)
-            {
-                if (!control._dataContextUpdating)
-                {
-                    control._dataContextUpdating = true;
-                    control.OnDataContextBeginUpdate();
-
-                    foreach (var child in control.LogicalChildren)
-                    {
-                        if (child is Control c && 
-                            c.InheritanceParent == control &&
-                            !c.IsSet(DataContextProperty))
-                        {
-                            DataContextNotifying(c, notifying);
-                        }
-                    }
-                }
-            }
-            else
-            {
-                if (control._dataContextUpdating)
-                {
-                    control.OnDataContextEndUpdate();
-                    control._dataContextUpdating = false;
-                }
-            }
-        }
-
-        private static IStyleRoot FindStyleRoot(IStyleHost e)
-        {
-            while (e != null)
-            {
-                if (e is IRenderRoot root)
-                {
-                    return root as IStyleRoot;
-                }
-
-                e = e.StylingParent;
-            }
-
-            return null;
-        }
-
-        private void ApplyStyling()
-        {
-            AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
-        }
-
-        private void RegisterWithNameScope()
-        {
-            if (_nameScope == null)
-            {
-                _nameScope = NameScope.GetNameScope(this) ?? ((Control)Parent)?._nameScope;
-            }
-
-            if (Name != null)
-            {
-                _nameScope?.Register(Name, this);
-
-                var visualParent = Parent as Visual;
-
-                if (this is INameScope && visualParent != null)
-                {
-                    // If we have e.g. a named UserControl in a window then we want that control
-                    // to be findable by name from the Window, so register with both name scopes.
-                    // This differs from WPF's behavior in that XAML manually registers controls 
-                    // with name scopes based on the XAML file in which the name attribute appears,
-                    // but we're trying to avoid XAML magic in Avalonia in order to made code-
-                    // created UIs easy. This will cause problems if a UserControl declares a name
-                    // in its XAML and that control is included multiple times in a parent control
-                    // (as the name will be duplicated), however at the moment I'm fine with saying
-                    // "don't do that".
-                    var parentNameScope = NameScope.FindNameScope(visualParent);
-                    parentNameScope?.Register(Name, this);
-                }
-            }
-        }
-
-        private static void ValidateLogicalChild(ILogical c)
-        {
-            if (c == null)
-            {
-                throw new ArgumentException("Cannot add null to LogicalChildren.");
-            }
-        }
-
-        private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
-        {
-            // This method can be called when a control is already attached to the logical tree
-            // in the following scenario:
-            // - ListBox gets assigned Items containing ListBoxItem
-            // - ListBox makes ListBoxItem a logical child
-            // - ListBox template gets applied; making its Panel get attached to logical tree
-            // - That AttachedToLogicalTree signal travels down to the ListBoxItem
-            if (!_isAttachedToLogicalTree)
-            {
-                _isAttachedToLogicalTree = true;
-
-                InitializeStylesIfNeeded(true);
-
-                OnAttachedToLogicalTree(e);
-                AttachedToLogicalTree?.Invoke(this, e);
-            }
-
-            foreach (var child in LogicalChildren.OfType<Control>())
-            {
-                child.OnAttachedToLogicalTreeCore(e);
-            }
-        }
-
-        private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
-        {
-            if (_isAttachedToLogicalTree)
-            {
-                if (Name != null)
-                {
-                    _nameScope?.Unregister(Name);
-                }
-
-                _isAttachedToLogicalTree = false;
-                _styleDetach.OnNext(this);
-                OnDetachedFromLogicalTree(e);
-                DetachedFromLogicalTree?.Invoke(this, e);
-
-                foreach (var child in LogicalChildren.OfType<Control>())
-                {
-                    child.OnDetachedFromLogicalTreeCore(e);
-                }
-
-#if DEBUG
-                if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
-                {
-                    Logger.Warning(
-                        LogArea.Control,
-                        this,
-                        "{Type} detached from logical tree but still has class listeners",
-                        this.GetType());
-                }
-#endif
-            }
-        }
-
-        private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
-        {
-            OnDataContextChanged(EventArgs.Empty);
-        }
-
-        private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
-        {
-            switch (e.Action)
-            {
-                case NotifyCollectionChangedAction.Add:
-                    SetLogicalParent(e.NewItems.Cast<ILogical>());
-                    break;
-
-                case NotifyCollectionChangedAction.Remove:
-                    ClearLogicalParent(e.OldItems.Cast<ILogical>());
-                    break;
-
-                case NotifyCollectionChangedAction.Replace:
-                    ClearLogicalParent(e.OldItems.Cast<ILogical>());
-                    SetLogicalParent(e.NewItems.Cast<ILogical>());
-                    break;
-
-                case NotifyCollectionChangedAction.Reset:
-                    throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
-            }
-        }
-
-        private void SetLogicalParent(IEnumerable<ILogical> children)
-        {
-            foreach (var i in children)
-            {
-                if (i.LogicalParent == null)
-                {
-                    ((ISetLogicalParent)i).SetParent(this);
-                }
-            }
-        }
-
-        private void ClearLogicalParent(IEnumerable<ILogical> children)
-        {
-            foreach (var i in children)
-            {
-                if (i.LogicalParent == this)
-                {
-                    ((ISetLogicalParent)i).SetParent(null);
-                }
-            }
-        }
-
-        private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
-        {
-            ((ILogical)this).NotifyResourcesChanged(e);
-        }
     }
 }

+ 3 - 3
src/Avalonia.Controls/HotkeyManager.cs

@@ -55,13 +55,13 @@ namespace Avalonia.Controls
             public void Init()
             {
                 _hotkeySub = _control.GetObservable(HotKeyProperty).Subscribe(OnHotkeyChanged);
-                _parentSub = AncestorFinder.Create(_control, typeof (TopLevel)).Subscribe(OnParentChanged);
+                _parentSub = AncestorFinder.Create<TopLevel>(_control).Subscribe(OnParentChanged);
             }
 
-            private void OnParentChanged(IControl control)
+            private void OnParentChanged(TopLevel control)
             {
                 Unregister();
-                _root = (TopLevel) control;
+                _root = control;
                 Register();
             }
 

+ 2 - 28
src/Avalonia.Controls/IControl.cs

@@ -16,37 +16,11 @@ namespace Avalonia.Controls
     /// </summary>
     public interface IControl : IVisual,
         IDataTemplateHost,
-        ILogical,
         ILayoutable,
         IInputElement,
         INamed,
-        IResourceNode,
-        IStyleable,
-        IStyleHost
+        IStyledElement
     {
-        /// <summary>
-        /// Occurs when the control has finished initialization.
-        /// </summary>
-        event EventHandler Initialized;
-
-        /// <summary>
-        /// Gets or sets the control's styling classes.
-        /// </summary>
-        new Classes Classes { get; set; }
-
-        /// <summary>
-        /// Gets or sets the control's data context.
-        /// </summary>
-        object DataContext { get; set; }
-
-        /// <summary>
-        /// Gets a value that indicates whether the element has finished initialization.
-        /// </summary>
-        bool IsInitialized { get; }
-
-        /// <summary>
-        /// Gets the control's logical parent.
-        /// </summary>
-        IControl Parent { get; }
+        new IControl Parent { get; }
     }
 }

+ 14 - 10
src/Avalonia.Controls/Utils/AncestorFinder.cs

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Reactive;
 using System.Reactive.Disposables;
+using System.Reactive.Linq;
 using System.Reactive.Subjects;
 using System.Reflection;
 using System.Text;
@@ -14,15 +15,15 @@ namespace Avalonia.Controls.Utils
     {
         class FinderNode : IDisposable
         {
-            private readonly IControl _control;
+            private readonly IStyledElement _control;
             private readonly TypeInfo _ancestorType;
-            public IObservable<IControl> Observable => _subject;
-            private readonly Subject<IControl> _subject = new Subject<IControl>();
+            public IObservable<IStyledElement> Observable => _subject;
+            private readonly Subject<IStyledElement> _subject = new Subject<IStyledElement>();
 
             private FinderNode _child;
             private IDisposable _disposable;
 
-            public FinderNode(IControl control, TypeInfo ancestorType)
+            public FinderNode(IStyledElement control, TypeInfo ancestorType)
             {
                 _control = control;
                 _ancestorType = ancestorType;
@@ -33,7 +34,7 @@ namespace Avalonia.Controls.Utils
                 _disposable = _control.GetObservable(Control.ParentProperty).Subscribe(OnValueChanged);
             }
 
-            private void OnValueChanged(IControl next)
+            private void OnValueChanged(IStyledElement next)
             {
                 if (next == null || _ancestorType.IsAssignableFrom(next.GetType().GetTypeInfo()))
                     _subject.OnNext(next);
@@ -46,7 +47,7 @@ namespace Avalonia.Controls.Utils
                 }
             }
 
-            private void OnChildValueChanged(IControl control) => _subject.OnNext(control);
+            private void OnChildValueChanged(IStyledElement control) => _subject.OnNext(control);
 
 
             public void Dispose()
@@ -55,10 +56,15 @@ namespace Avalonia.Controls.Utils
             }
         }
 
+        public static IObservable<T> Create<T>(IStyledElement control)
+            where T : IStyledElement
+        {
+            return Create(control, typeof(T)).Cast<T>();
+        }
 
-        public static IObservable<IControl> Create(IControl control, Type ancestorType)
+        public static IObservable<IStyledElement> Create(IStyledElement control, Type ancestorType)
         {
-            return new AnonymousObservable<IControl>(observer =>
+            return new AnonymousObservable<IStyledElement>(observer =>
             {
                 var finder = new FinderNode(control, ancestorType.GetTypeInfo());
                 var subscription = finder.Observable.Subscribe(observer);
@@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils
                     finder.Dispose();
                 });
             });
-
-
         }
     }
 }

+ 8 - 8
src/Avalonia.Controls/Classes.cs → src/Avalonia.Styling/Controls/Classes.cs

@@ -9,7 +9,7 @@ using Avalonia.Collections;
 namespace Avalonia.Controls
 {
     /// <summary>
-    /// Holds a collection of style classes for an <see cref="IControl"/>.
+    /// Holds a collection of style classes for an <see cref="IStyledElement"/>.
     /// </summary>
     /// <remarks>
     /// Similar to CSS, each control may have any number of styling classes applied.
@@ -54,7 +54,7 @@ namespace Avalonia.Controls
         /// <param name="name">The class name.</param>
         /// <remarks>
         /// Only standard classes may be added via this method. To add pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void Add(string name)
@@ -73,7 +73,7 @@ namespace Avalonia.Controls
         /// <param name="names">The class names.</param>
         /// <remarks>
         /// Only standard classes may be added via this method. To add pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void AddRange(IEnumerable<string> names)
@@ -114,7 +114,7 @@ namespace Avalonia.Controls
         /// <param name="name">The class name.</param>
         /// <remarks>
         /// Only standard classes may be added via this method. To add pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void Insert(int index, string name)
@@ -134,7 +134,7 @@ namespace Avalonia.Controls
         /// <param name="names">The class names.</param>
         /// <remarks>
         /// Only standard classes may be added via this method. To add pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void InsertRange(int index, IEnumerable<string> names)
@@ -160,7 +160,7 @@ namespace Avalonia.Controls
         /// <param name="name">The class name.</param>
         /// <remarks>
         /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override bool Remove(string name)
@@ -175,7 +175,7 @@ namespace Avalonia.Controls
         /// <param name="names">The class name.</param>
         /// <remarks>
         /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void RemoveAll(IEnumerable<string> names)
@@ -201,7 +201,7 @@ namespace Avalonia.Controls
         /// <param name="index">The index of the class in the collection.</param>
         /// <remarks>
         /// Only standard classes may be removed via this method. To remove pseudoclasses (classes
-        /// beginning with a ':' character) use the protected <see cref="Control.PseudoClasses"/>
+        /// beginning with a ':' character) use the protected <see cref="StyledElement.PseudoClasses"/>
         /// property.
         /// </remarks>
         public override void RemoveAt(int index)

+ 0 - 0
src/Avalonia.Controls/IPseudoClasses.cs → src/Avalonia.Styling/Controls/IPseudoClasses.cs


+ 1 - 1
src/Avalonia.Controls/ISetInheritanceParent.cs → src/Avalonia.Styling/Controls/ISetInheritanceParent.cs

@@ -4,7 +4,7 @@
 namespace Avalonia.Controls
 {
     /// <summary>
-    /// Defines an interface through which a <see cref="Control"/>'s inheritance parent can be set.
+    /// Defines an interface through which a <see cref="StyledElement"/>'s inheritance parent can be set.
     /// </summary>
     /// <remarks>
     /// You should not usually need to use this interface - it is for advanced scenarios only.

+ 1 - 1
src/Avalonia.Controls/ISetLogicalParent.cs → src/Avalonia.Styling/Controls/ISetLogicalParent.cs

@@ -6,7 +6,7 @@ using Avalonia.LogicalTree;
 namespace Avalonia.Controls
 {
     /// <summary>
-    /// Defines an interface through which a <see cref="Control"/>'s logical parent can be set.
+    /// Defines an interface through which a <see cref="StyledElement"/>'s logical parent can be set.
     /// </summary>
     /// <remarks>
     /// You should not usually need to use this interface - it is for advanced scenarios only.

+ 18 - 18
src/Avalonia.Styling/Controls/NameScope.cs

@@ -16,7 +16,7 @@ namespace Avalonia.Controls
         /// Defines the NameScope attached property.
         /// </summary>
         public static readonly AttachedProperty<INameScope> NameScopeProperty =
-            AvaloniaProperty.RegisterAttached<NameScope, Visual, INameScope>("NameScope");
+            AvaloniaProperty.RegisterAttached<NameScope, StyledElement, INameScope>("NameScope");
 
         private readonly Dictionary<string, object> _inner = new Dictionary<string, object>();
 
@@ -31,53 +31,53 @@ namespace Avalonia.Controls
         public event EventHandler<NameScopeEventArgs> Unregistered;
 
         /// <summary>
-        /// Finds the containing name scope for a visual.
+        /// Finds the containing name scope for a styled element.
         /// </summary>
-        /// <param name="visual">The visual.</param>
+        /// <param name="styled">The styled element.</param>
         /// <returns>The containing name scope.</returns>
-        public static INameScope FindNameScope(Visual visual)
+        public static INameScope FindNameScope(StyledElement styled)
         {
-            Contract.Requires<ArgumentNullException>(visual != null);
+            Contract.Requires<ArgumentNullException>(styled != null);
 
             INameScope result;
 
-            while (visual != null)
+            while (styled != null)
             {
-                result = visual as INameScope ?? GetNameScope(visual);
+                result = styled as INameScope ?? GetNameScope(styled);
 
                 if (result != null)
                 {
                     return result;
                 }
 
-                visual = (visual as ILogical)?.LogicalParent as Visual;
+                styled = (styled as ILogical)?.LogicalParent as StyledElement;
             }
 
             return null;
         }
 
         /// <summary>
-        /// Gets the value of the attached <see cref="NameScopeProperty"/> on a visual.
+        /// Gets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
         /// </summary>
-        /// <param name="visual">The visual.</param>
+        /// <param name="styled">The styled element.</param>
         /// <returns>The value of the NameScope attached property.</returns>
-        public static INameScope GetNameScope(Visual visual)
+        public static INameScope GetNameScope(StyledElement styled)
         {
-            Contract.Requires<ArgumentNullException>(visual != null);
+            Contract.Requires<ArgumentNullException>(styled != null);
 
-            return visual.GetValue(NameScopeProperty);
+            return styled.GetValue(NameScopeProperty);
         }
 
         /// <summary>
-        /// Sets the value of the attached <see cref="NameScopeProperty"/> on a visual.
+        /// Sets the value of the attached <see cref="NameScopeProperty"/> on a styled element.
         /// </summary>
-        /// <param name="visual">The visual.</param>
+        /// <param name="styled">The styled element.</param>
         /// <param name="value">The value to set.</param>
-        public static void SetNameScope(Visual visual, INameScope value)
+        public static void SetNameScope(StyledElement styled, INameScope value)
         {
-            Contract.Requires<ArgumentNullException>(visual != null);
+            Contract.Requires<ArgumentNullException>(styled != null);
 
-            visual.SetValue(NameScopeProperty, value);
+            styled.SetValue(NameScopeProperty, value);
         }
 
         /// <summary>

+ 1 - 2
src/Avalonia.Styling/Controls/NameScopeExtensions.cs

@@ -5,7 +5,6 @@ using System;
 using System.Collections.Generic;
 using System.Linq;
 using Avalonia.LogicalTree;
-using Avalonia.VisualTree;
 
 namespace Avalonia.Controls
 {
@@ -73,7 +72,7 @@ namespace Avalonia.Controls
             Contract.Requires<ArgumentNullException>(control != null);
 
             return control.GetSelfAndLogicalAncestors()
-                .OfType<Visual>()
+                .OfType<StyledElement>()
                 .Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
                 .FirstOrDefault(x => x != null);
         }

+ 0 - 0
src/Avalonia.Visuals/INamed.cs → src/Avalonia.Styling/INamed.cs


+ 42 - 0
src/Avalonia.Styling/IStyledElement.cs

@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Avalonia.Controls;
+using Avalonia.LogicalTree;
+using Avalonia.Styling;
+
+namespace Avalonia
+{
+    public interface IStyledElement :
+        IStyleable,
+        IStyleHost,
+        ILogical,
+        IResourceProvider,
+        IResourceNode
+    {
+        /// <summary>
+        /// Occurs when the control has finished initialization.
+        /// </summary>
+        event EventHandler Initialized;
+
+        /// <summary>
+        /// Gets a value that indicates whether the element has finished initialization.
+        /// </summary>
+        bool IsInitialized { get; }
+
+        /// <summary>
+        /// Gets or sets the control's styling classes.
+        /// </summary>
+        new Classes Classes { get; set; }
+
+        /// <summary>
+        /// Gets or sets the control's data context.
+        /// </summary>
+        object DataContext { get; set; }
+
+        /// <summary>
+        /// Gets the control's logical parent.
+        /// </summary>
+        IStyledElement Parent { get; }
+    }
+}

+ 1 - 52
src/Avalonia.Styling/Controls/ControlLocator.cs → src/Avalonia.Styling/LogicalTree/ControlLocator.cs

@@ -6,26 +6,9 @@ using System.Linq;
 using System.Reactive.Linq;
 using System.Reflection;
 using Avalonia.Controls;
-using Avalonia.LogicalTree;
-using Avalonia.VisualTree;
 
-namespace Avalonia.Controls
+namespace Avalonia.LogicalTree
 {
-    /// <summary>
-    /// The type of tree via which to track a control.
-    /// </summary>
-    public enum TreeType
-    {
-        /// <summary>
-        /// The visual tree.
-        /// </summary>
-        Visual,
-        /// <summary>
-        /// The logical tree.
-        /// </summary>
-        Logical,
-    }
-
     /// <summary>
     /// Locates controls relative to other controls.
     /// </summary>
@@ -94,40 +77,6 @@ namespace Avalonia.Controls
             });
         }
 
-        public static IObservable<IVisual> Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null)
-        {
-            return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree =>
-            {
-                if (isAttachedToTree)
-                {
-                    return relativeTo.GetVisualAncestors()
-                        .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
-                        .ElementAtOrDefault(ancestorLevel);
-                }
-                else
-                {
-                    return null;
-                }
-            });
-        }
-
-        private static IObservable<bool> TrackAttachmentToTree(IVisual relativeTo)
-        {
-            var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
-                x => relativeTo.AttachedToVisualTree += x,
-                x => relativeTo.AttachedToVisualTree -= x)
-                .Select(x => true)
-                .StartWith(relativeTo.IsAttachedToVisualTree);
-
-            var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
-                x => relativeTo.DetachedFromVisualTree += x,
-                x => relativeTo.DetachedFromVisualTree -= x)
-                .Select(x => false);
-
-            var attachmentStatus = attached.Merge(detached);
-            return attachmentStatus;
-        }
-
         private static IObservable<bool> TrackAttachmentToTree(ILogical relativeTo)
         {
             var attached = Observable.FromEventPattern<LogicalTreeAttachmentEventArgs>(

+ 783 - 0
src/Avalonia.Styling/StyledElement.cs

@@ -0,0 +1,783 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Text;
+using Avalonia.Animation;
+using Avalonia.Collections;
+using Avalonia.Controls;
+using Avalonia.Data;
+using Avalonia.Diagnostics;
+using Avalonia.Logging;
+using Avalonia.LogicalTree;
+using Avalonia.Styling;
+
+namespace Avalonia
+{
+    /// <summary>
+    /// Extends an <see cref="Animatable"/> with the following features:
+    /// 
+    /// - An inherited <see cref="DataContext"/>.
+    /// - Implements <see cref="IStyleable"/> to allow styling to work on the styled element.
+    /// - Implements <see cref="ILogical"/> to form part of a logical tree.
+    /// - A collection of class strings for custom styling.
+    /// </summary>
+    public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
+    {
+        /// <summary>
+        /// Defines the <see cref="DataContext"/> property.
+        /// </summary>
+        public static readonly StyledProperty<object> DataContextProperty =
+            AvaloniaProperty.Register<StyledElement, object>(
+                nameof(DataContext),
+                inherits: true,
+                notifying: DataContextNotifying);
+
+        /// <summary>
+        /// Defines the <see cref="Name"/> property.
+        /// </summary>
+        public static readonly DirectProperty<StyledElement, string> NameProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, string>(nameof(Name), o => o.Name, (o, v) => o.Name = v);
+        
+        /// <summary>
+        /// Defines the <see cref="Parent"/> property.
+        /// </summary>
+        public static readonly DirectProperty<StyledElement, IStyledElement> ParentProperty =
+            AvaloniaProperty.RegisterDirect<StyledElement, IStyledElement>(nameof(Parent), o => o.Parent);
+
+        /// <summary>
+        /// Defines the <see cref="TemplatedParent"/> property.
+        /// </summary>
+        public static readonly StyledProperty<ITemplatedControl> TemplatedParentProperty =
+            AvaloniaProperty.Register<StyledElement, ITemplatedControl>(nameof(TemplatedParent), inherits: true);
+
+        private int _initCount;
+        private string _name;
+        private readonly Classes _classes = new Classes();
+        private bool _isAttachedToLogicalTree;
+        private IAvaloniaList<ILogical> _logicalChildren;
+        private INameScope _nameScope;
+        private IResourceDictionary _resources;
+        private Styles _styles;
+        private bool _styled;
+        private Subject<IStyleable> _styleDetach = new Subject<IStyleable>();
+        private bool _dataContextUpdating;
+
+        /// <summary>
+        /// Initializes static members of the <see cref="StyledElement"/> class.
+        /// </summary>
+        static StyledElement()
+        {
+            DataContextProperty.Changed.AddClassHandler<StyledElement>(x => x.OnDataContextChangedCore);
+        }
+
+        /// <summary>
+        /// Initializes a new instance of the <see cref="StyledElement"/> class.
+        /// </summary>
+        public StyledElement()
+        {
+            _nameScope = this as INameScope;
+            _isAttachedToLogicalTree = this is IStyleRoot;
+        }
+
+        /// <summary>
+        /// Raised when the styled element is attached to a rooted logical tree.
+        /// </summary>
+        public event EventHandler<LogicalTreeAttachmentEventArgs> AttachedToLogicalTree;
+
+        /// <summary>
+        /// Raised when the styled element is detached from a rooted logical tree.
+        /// </summary>
+        public event EventHandler<LogicalTreeAttachmentEventArgs> DetachedFromLogicalTree;
+
+        /// <summary>
+        /// Occurs when the <see cref="DataContext"/> property changes.
+        /// </summary>
+        /// <remarks>
+        /// This event will be raised when the <see cref="DataContext"/> property has changed and
+        /// all subscribers to that change have been notified.
+        /// </remarks>
+        public event EventHandler DataContextChanged;
+
+        /// <summary>
+        /// Occurs when the styled element has finished initialization.
+        /// </summary>
+        /// <remarks>
+        /// The Initialized event indicates that all property values on the styled element have been set.
+        /// When loading the styled element from markup, it occurs when 
+        /// <see cref="ISupportInitialize.EndInit"/> is called *and* the styled element
+        /// is attached to a rooted logical tree. When the styled element is created by code and
+        /// <see cref="ISupportInitialize"/> is not used, it is called when the styled element is attached
+        /// to the visual tree.
+        /// </remarks>
+        public event EventHandler Initialized;
+
+        /// <summary>
+        /// Occurs when a resource in this styled element or a parent styled element has changed.
+        /// </summary>
+        public event EventHandler<ResourcesChangedEventArgs> ResourcesChanged;
+
+        /// <summary>
+        /// Gets or sets the name of the styled element.
+        /// </summary>
+        /// <remarks>
+        /// An element's name is used to uniquely identify an element within the element's name
+        /// scope. Once the element is added to a logical tree, its name cannot be changed.
+        /// </remarks>
+        public string Name
+        {
+            get
+            {
+                return _name;
+            }
+
+            set
+            {
+                if (String.IsNullOrWhiteSpace(value))
+                {
+                    throw new InvalidOperationException("Cannot set Name to null or empty string.");
+                }
+
+                if (_styled)
+                {
+                    throw new InvalidOperationException("Cannot set Name : styled element already styled.");
+                }
+
+                _name = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the styled element's classes.
+        /// </summary>
+        /// <remarks>
+        /// <para>
+        /// Classes can be used to apply user-defined styling to styled elements, or to allow styled elements
+        /// that share a common purpose to be easily selected.
+        /// </para>
+        /// <para>
+        /// 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 addds the contents of the assigned
+        /// collection.
+        /// </para>
+        /// </remarks>
+        public Classes Classes
+        {
+            get
+            {
+                return _classes;
+            }
+
+            set
+            {
+                if (_classes != value)
+                {
+                    _classes.Replace(value);
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the control's data context.
+        /// </summary>
+        /// <remarks>
+        /// The data context is an inherited property that specifies the default object that will
+        /// be used for data binding.
+        /// </remarks>
+        public object DataContext
+        {
+            get { return GetValue(DataContextProperty); }
+            set { SetValue(DataContextProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets a value that indicates whether the element has finished initialization.
+        /// </summary>
+        /// <remarks>
+        /// For more information about when IsInitialized is set, see the <see cref="Initialized"/>
+        /// event.
+        /// </remarks>
+        public bool IsInitialized { get; private set; }
+
+        /// <summary>
+        /// Gets the styles for the styled element.
+        /// </summary>
+        /// <remarks>
+        /// Styles for the entire application are added to the Application.Styles collection, but
+        /// each styled element may in addition define its own styles which are applied to the styled element
+        /// itself and its children.
+        /// </remarks>
+        public Styles Styles
+        {
+            get { return _styles ?? (Styles = new Styles()); }
+            set
+            {
+                Contract.Requires<ArgumentNullException>(value != null);
+
+                if (_styles != value)
+                {
+                    if (_styles != null)
+                    {
+                        (_styles as ISetStyleParent)?.SetParent(null);
+                        _styles.ResourcesChanged -= ThisResourcesChanged;
+                    }
+
+                    _styles = value;
+
+                    if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
+                    {
+                        setParent.SetParent(this);
+                    }
+
+                    _styles.ResourcesChanged += ThisResourcesChanged;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the styled element's resource dictionary.
+        /// </summary>
+        public IResourceDictionary Resources
+        {
+            get => _resources ?? (Resources = new ResourceDictionary());
+            set
+            {
+                Contract.Requires<ArgumentNullException>(value != null);
+
+                var hadResources = false;
+
+                if (_resources != null)
+                {
+                    hadResources = _resources.Count > 0;
+                    _resources.ResourcesChanged -= ThisResourcesChanged;
+                }
+
+                _resources = value;
+                _resources.ResourcesChanged += ThisResourcesChanged;
+
+                if (hadResources || _resources.Count > 0)
+                {
+                    ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets the styled element whose lookless template this styled element is part of.
+        /// </summary>
+        public ITemplatedControl TemplatedParent
+        {
+            get { return GetValue(TemplatedParentProperty); }
+            internal set { SetValue(TemplatedParentProperty, value); }
+        }
+
+        /// <summary>
+        /// Gets the styled element's logical children.
+        /// </summary>
+        protected IAvaloniaList<ILogical> LogicalChildren
+        {
+            get
+            {
+                if (_logicalChildren == null)
+                {
+                    var list = new AvaloniaList<ILogical>
+                    {
+                        ResetBehavior = ResetBehavior.Remove,
+                        Validate = ValidateLogicalChild
+                    };
+                    list.CollectionChanged += LogicalChildrenCollectionChanged;
+                    _logicalChildren = list;
+                }
+
+                return _logicalChildren;
+            }
+        }
+
+        /// <summary>
+        /// Gets the <see cref="Classes"/> collection in a form that allows adding and removing
+        /// pseudoclasses.
+        /// </summary>
+        protected IPseudoClasses PseudoClasses => Classes;
+
+        /// <summary>
+        /// Gets a value indicating whether the element is attached to a rooted logical tree.
+        /// </summary>
+        bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
+
+        /// <summary>
+        /// Gets the styled element's logical parent.
+        /// </summary>
+        public IStyledElement Parent { get; private set; }
+
+        /// <summary>
+        /// Gets the styled element's logical parent.
+        /// </summary>
+        ILogical ILogical.LogicalParent => Parent;
+
+        /// <summary>
+        /// Gets the styled element's logical children.
+        /// </summary>
+        IAvaloniaReadOnlyList<ILogical> ILogical.LogicalChildren => LogicalChildren;
+
+        /// <inheritdoc/>
+        bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
+
+        /// <inheritdoc/>
+        IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
+
+        /// <inheritdoc/>
+        IAvaloniaReadOnlyList<string> IStyleable.Classes => Classes;
+
+        /// <summary>
+        /// Gets the type by which the styled element is styled.
+        /// </summary>
+        /// <remarks>
+        /// Usually controls are styled by their own type, but there are instances where you want
+        /// a styled element to be styled by its base type, e.g. creating SpecialButton that
+        /// derives from Button and adds extra functionality but is still styled as a regular
+        /// Button.
+        /// </remarks>
+        Type IStyleable.StyleKey => GetType();
+
+        /// <inheritdoc/>
+        IObservable<IStyleable> IStyleable.StyleDetach => _styleDetach;
+
+        /// <inheritdoc/>
+        bool IStyleHost.IsStylesInitialized => _styles != null;
+
+        /// <inheritdoc/>
+        IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
+
+        /// <inheritdoc/>
+        public virtual void BeginInit()
+        {
+            ++_initCount;
+        }
+
+        /// <inheritdoc/>
+        public virtual void EndInit()
+        {
+            if (_initCount == 0)
+            {
+                throw new InvalidOperationException("BeginInit was not called.");
+            }
+
+            if (--_initCount == 0 && _isAttachedToLogicalTree)
+            {
+                InitializeStylesIfNeeded();
+
+                InitializeIfNeeded();
+            }
+        }
+
+        private void InitializeStylesIfNeeded(bool force = false)
+        {
+            if (_initCount == 0 && (!_styled || force))
+            {
+                RegisterWithNameScope();
+                ApplyStyling();
+                _styled = true;
+            }
+        }
+
+        protected void InitializeIfNeeded()
+        {
+            if (_initCount == 0 && !IsInitialized)
+            {
+                IsInitialized = true;
+                Initialized?.Invoke(this, EventArgs.Empty);
+            }
+        }
+
+        /// <inheritdoc/>
+        void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            this.OnAttachedToLogicalTreeCore(e);
+        }
+
+        /// <inheritdoc/>
+        void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+            this.OnDetachedFromLogicalTreeCore(e);
+        }
+
+        /// <inheritdoc/>
+        void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
+        {
+            ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+        }
+
+        /// <inheritdoc/>
+        bool IResourceProvider.TryGetResource(string key, out object value)
+        {
+            value = null;
+            return (_resources?.TryGetResource(key, out value) ?? false) ||
+                   (_styles?.TryGetResource(key, out value) ?? false);
+        }
+
+        /// <summary>
+        /// Sets the styled element's logical parent.
+        /// </summary>
+        /// <param name="parent">The parent.</param>
+        void ISetLogicalParent.SetParent(ILogical parent)
+        {
+            var old = Parent;
+
+            if (parent != old)
+            {
+                if (old != null && parent != null)
+                {
+                    throw new InvalidOperationException("The Control already has a parent.");
+                }
+
+                if (_isAttachedToLogicalTree)
+                {
+                    var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
+
+                    if (oldRoot == null)
+                    {
+                        throw new AvaloniaInternalException("Was attached to logical tree but cannot find root.");
+                    }
+
+                    var e = new LogicalTreeAttachmentEventArgs(oldRoot);
+                    OnDetachedFromLogicalTreeCore(e);
+                }
+
+                if (InheritanceParent == null || parent == null)
+                {
+                    InheritanceParent = parent as AvaloniaObject;
+                }
+
+                Parent = (IStyledElement)parent;
+
+                if (old != null)
+                {
+                    old.ResourcesChanged -= ThisResourcesChanged;
+                }
+                if (Parent != null)
+                {
+                    Parent.ResourcesChanged += ThisResourcesChanged;
+                }
+                ((ILogical)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
+
+                if (Parent is IStyleRoot || Parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
+                {
+                    var newRoot = FindStyleRoot(this);
+
+                    if (newRoot == null)
+                    {
+                        throw new AvaloniaInternalException("Parent is atttached to logical tree but cannot find root.");
+                    }
+
+                    var e = new LogicalTreeAttachmentEventArgs(newRoot);
+                    OnAttachedToLogicalTreeCore(e);
+                }
+
+                RaisePropertyChanged(ParentProperty, old, Parent, BindingPriority.LocalValue);
+            }
+        }
+
+        /// <summary>
+        /// Sets the styled element's inheritance parent.
+        /// </summary>
+        /// <param name="parent">The parent.</param>
+        void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
+        {
+            InheritanceParent = parent;
+        }
+
+        /// <summary>
+        /// Adds a pseudo-class to be set when a property is true.
+        /// </summary>
+        /// <param name="property">The property.</param>
+        /// <param name="className">The pseudo-class.</param>
+        protected static void PseudoClass(AvaloniaProperty<bool> property, string className)
+        {
+            PseudoClass(property, x => x, className);
+        }
+
+        /// <summary>
+        /// Adds a pseudo-class to be set when a property equals a certain value.
+        /// </summary>
+        /// <typeparam name="T">The type of the property.</typeparam>
+        /// <param name="property">The property.</param>
+        /// <param name="selector">Returns a boolean value based on the property value.</param>
+        /// <param name="className">The pseudo-class.</param>
+        protected static void PseudoClass<T>(
+            AvaloniaProperty<T> property,
+            Func<T, bool> selector,
+            string className)
+        {
+            Contract.Requires<ArgumentNullException>(property != null);
+            Contract.Requires<ArgumentNullException>(selector != null);
+            Contract.Requires<ArgumentNullException>(className != null);
+
+            if (string.IsNullOrWhiteSpace(className))
+            {
+                throw new ArgumentException("Cannot supply an empty className.");
+            }
+
+            property.Changed.Merge(property.Initialized)
+                .Where(e => e.Sender is StyledElement)
+                .Subscribe(e =>
+                {
+                    if (selector((T)e.NewValue))
+                    {
+                        ((StyledElement)e.Sender).PseudoClasses.Add(className);
+                    }
+                    else
+                    {
+                        ((StyledElement)e.Sender).PseudoClasses.Remove(className);
+                    }
+                });
+        }
+
+        /// <summary>
+        /// Called when the styled element is added to a rooted logical tree.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+        }
+
+        /// <summary>
+        /// Called when the styled element is removed from a rooted logical tree.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+        {
+        }
+
+        /// <summary>
+        /// Called when the <see cref="DataContext"/> property changes.
+        /// </summary>
+        /// <param name="e">The event args.</param>
+        protected virtual void OnDataContextChanged(EventArgs e)
+        {
+            DataContextChanged?.Invoke(this, EventArgs.Empty);
+        }
+
+        /// <summary>
+        /// Called when the <see cref="DataContext"/> begins updating.
+        /// </summary>
+        protected virtual void OnDataContextBeginUpdate()
+        {
+        }
+
+        /// <summary>
+        /// Called when the <see cref="DataContext"/> finishes updating.
+        /// </summary>
+        protected virtual void OnDataContextEndUpdate()
+        {
+        }
+        
+        private static void DataContextNotifying(IAvaloniaObject o, bool updateStarted)
+        {
+            if (o is StyledElement element)
+            {
+                DataContextNotifying(element, updateStarted);
+            }
+        }
+
+        private static void DataContextNotifying(StyledElement element, bool updateStarted)
+        {
+            if (updateStarted)
+            {
+                if (!element._dataContextUpdating)
+                {
+                    element._dataContextUpdating = true;
+                    element.OnDataContextBeginUpdate();
+
+                    foreach (var child in element.LogicalChildren)
+                    {
+                        if (child is StyledElement s &&
+                            s.InheritanceParent == element &&
+                            !s.IsSet(DataContextProperty))
+                        {
+                            DataContextNotifying(s, updateStarted);
+                        }
+                    }
+                }
+            }
+            else
+            {
+                if (element._dataContextUpdating)
+                {
+                    element.OnDataContextEndUpdate();
+                    element._dataContextUpdating = false;
+                }
+            }
+        }
+
+        private static IStyleRoot FindStyleRoot(IStyleHost e)
+        {
+            while (e != null)
+            {
+                if (e is IStyleRoot root)
+                {
+                    return root;
+                }
+
+                e = e.StylingParent;
+            }
+
+            return null;
+        }
+
+        private void ApplyStyling()
+        {
+            AvaloniaLocator.Current.GetService<IStyler>()?.ApplyStyles(this);
+        }
+
+        private void RegisterWithNameScope()
+        {
+            if (_nameScope == null)
+            {
+                _nameScope = NameScope.GetNameScope(this) ?? ((StyledElement)Parent)?._nameScope;
+            }
+
+            if (Name != null)
+            {
+                _nameScope?.Register(Name, this);
+
+                var visualParent = Parent as StyledElement;
+
+                if (this is INameScope && visualParent != null)
+                {
+                    // If we have e.g. a named UserControl in a window then we want that control
+                    // to be findable by name from the Window, so register with both name scopes.
+                    // This differs from WPF's behavior in that XAML manually registers controls 
+                    // with name scopes based on the XAML file in which the name attribute appears,
+                    // but we're trying to avoid XAML magic in Avalonia in order to made code-
+                    // created UIs easy. This will cause problems if a UserControl declares a name
+                    // in its XAML and that control is included multiple times in a parent control
+                    // (as the name will be duplicated), however at the moment I'm fine with saying
+                    // "don't do that".
+                    var parentNameScope = NameScope.FindNameScope(visualParent);
+                    parentNameScope?.Register(Name, this);
+                }
+            }
+        }
+
+        private static void ValidateLogicalChild(ILogical c)
+        {
+            if (c == null)
+            {
+                throw new ArgumentException("Cannot add null to LogicalChildren.");
+            }
+        }
+
+        private void OnAttachedToLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
+        {
+            // This method can be called when a control is already attached to the logical tree
+            // in the following scenario:
+            // - ListBox gets assigned Items containing ListBoxItem
+            // - ListBox makes ListBoxItem a logical child
+            // - ListBox template gets applied; making its Panel get attached to logical tree
+            // - That AttachedToLogicalTree signal travels down to the ListBoxItem
+            if (!_isAttachedToLogicalTree)
+            {
+                _isAttachedToLogicalTree = true;
+
+                InitializeStylesIfNeeded(true);
+
+                OnAttachedToLogicalTree(e);
+                AttachedToLogicalTree?.Invoke(this, e);
+            }
+
+            foreach (var child in LogicalChildren.OfType<StyledElement>())
+            {
+                child.OnAttachedToLogicalTreeCore(e);
+            }
+        }
+
+        private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
+        {
+            if (_isAttachedToLogicalTree)
+            {
+                if (Name != null)
+                {
+                    _nameScope?.Unregister(Name);
+                }
+
+                _isAttachedToLogicalTree = false;
+                _styleDetach.OnNext(this);
+                OnDetachedFromLogicalTree(e);
+                DetachedFromLogicalTree?.Invoke(this, e);
+
+                foreach (var child in LogicalChildren.OfType<StyledElement>())
+                {
+                    child.OnDetachedFromLogicalTreeCore(e);
+                }
+
+#if DEBUG
+                if (((INotifyCollectionChangedDebug)_classes).GetCollectionChangedSubscribers()?.Length > 0)
+                {
+                    Logger.Warning(
+                        LogArea.Control,
+                        this,
+                        "{Type} detached from logical tree but still has class listeners",
+                        this.GetType());
+                }
+#endif
+            }
+        }
+
+        private void OnDataContextChangedCore(AvaloniaPropertyChangedEventArgs e)
+        {
+            OnDataContextChanged(EventArgs.Empty);
+        }
+
+        private void LogicalChildrenCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
+        {
+            switch (e.Action)
+            {
+                case NotifyCollectionChangedAction.Add:
+                    SetLogicalParent(e.NewItems.Cast<ILogical>());
+                    break;
+
+                case NotifyCollectionChangedAction.Remove:
+                    ClearLogicalParent(e.OldItems.Cast<ILogical>());
+                    break;
+
+                case NotifyCollectionChangedAction.Replace:
+                    ClearLogicalParent(e.OldItems.Cast<ILogical>());
+                    SetLogicalParent(e.NewItems.Cast<ILogical>());
+                    break;
+
+                case NotifyCollectionChangedAction.Reset:
+                    throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
+            }
+        }
+
+        private void SetLogicalParent(IEnumerable<ILogical> children)
+        {
+            foreach (var i in children)
+            {
+                if (i.LogicalParent == null)
+                {
+                    ((ISetLogicalParent)i).SetParent(this);
+                }
+            }
+        }
+
+        private void ClearLogicalParent(IEnumerable<ILogical> children)
+        {
+            foreach (var i in children)
+            {
+                if (i.LogicalParent == this)
+                {
+                    ((ISetLogicalParent)i).SetParent(null);
+                }
+            }
+        }
+
+        private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
+        {
+            ((ILogical)this).NotifyResourcesChanged(e);
+        }
+    }
+}

+ 14 - 0
src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Styling
+{
+    /// <summary>
+    /// This is an interface for advanced scenarios to assist users in correct style development.
+    /// You as a user will not need to use this interface directly.
+    /// </summary>
+    public interface IRequiresTemplateInStyle
+    {
+    }
+}

+ 1 - 0
src/Avalonia.Styling/Styling/IStyleable.cs

@@ -3,6 +3,7 @@
 
 using System;
 using Avalonia.Collections;
+using Avalonia.LogicalTree;
 
 namespace Avalonia.Styling
 {

+ 2 - 2
src/Avalonia.Styling/Styling/Setter.cs

@@ -65,7 +65,7 @@ namespace Avalonia.Styling
 
             set
             {
-                if (value is IStyleable)
+                if (value is IRequiresTemplateInStyle)
                 {
                     throw new ArgumentException(
                         "Cannot assign a control to Style.Value. Wrap the control in a <Template>.",
@@ -105,7 +105,7 @@ namespace Avalonia.Styling
                 if (template != null && !isPropertyOfTypeITemplate)
                 {
                     var materialized = template.Build();
-                    NameScope.SetNameScope((Visual)materialized, new NameScope());
+                    NameScope.SetNameScope((StyledElement)materialized, new NameScope());
                     value = materialized;
                 }
 

+ 0 - 15
src/Avalonia.Styling/app.config

@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<configuration>
-  <runtime>
-    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
-      <dependentAssembly>
-        <assemblyIdentity name="System.Reactive.Interfaces" publicKeyToken="31bf3856ad364e35" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" />
-      </dependentAssembly>
-      <dependentAssembly>
-        <assemblyIdentity name="System.Reactive.Core" publicKeyToken="31bf3856ad364e35" culture="neutral" />
-        <bindingRedirect oldVersion="0.0.0.0-2.2.5.0" newVersion="2.2.5.0" />
-      </dependentAssembly>
-    </assemblyBinding>
-  </runtime>
-</configuration>

+ 2 - 1
src/Avalonia.Visuals/Avalonia.Visuals.csproj

@@ -5,6 +5,7 @@
   <ItemGroup> 
     <ProjectReference Include="..\Avalonia.Animation\Avalonia.Animation.csproj" />
     <ProjectReference Include="..\Avalonia.Base\Avalonia.Base.csproj" />
-  </ItemGroup>  
+    <ProjectReference Include="..\Avalonia.Styling\Avalonia.Styling.csproj" />
+  </ItemGroup>
   <Import Project="..\..\build\Rx.props" />
 </Project>

+ 1 - 1
src/Avalonia.Visuals/Visual.cs

@@ -24,7 +24,7 @@ namespace Avalonia
     /// <see cref="IRenderer"/> to render the control. To traverse the visual tree, use the
     /// extension methods defined in <see cref="VisualExtensions"/>.
     /// </remarks>
-    public class Visual : Animatable, IVisual
+    public class Visual : StyledElement, IVisual
     {
         /// <summary>
         /// Defines the <see cref="Bounds"/> property.

+ 46 - 0
src/Avalonia.Visuals/VisualTree/VisualLocator.cs

@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reactive.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace Avalonia.VisualTree
+{
+    public class VisualLocator
+    {
+        public static IObservable<IVisual> Track(IVisual relativeTo, int ancestorLevel, Type ancestorType = null)
+        {
+            return TrackAttachmentToTree(relativeTo).Select(isAttachedToTree =>
+            {
+                if (isAttachedToTree)
+                {
+                    return relativeTo.GetVisualAncestors()
+                        .Where(x => ancestorType?.GetTypeInfo().IsAssignableFrom(x.GetType().GetTypeInfo()) ?? true)
+                        .ElementAtOrDefault(ancestorLevel);
+                }
+                else
+                {
+                    return null;
+                }
+            });
+        }
+
+        private static IObservable<bool> TrackAttachmentToTree(IVisual relativeTo)
+        {
+            var attached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
+                x => relativeTo.AttachedToVisualTree += x,
+                x => relativeTo.AttachedToVisualTree -= x)
+                .Select(x => true)
+                .StartWith(relativeTo.IsAttachedToVisualTree);
+
+            var detached = Observable.FromEventPattern<VisualTreeAttachmentEventArgs>(
+                x => relativeTo.DetachedFromVisualTree += x,
+                x => relativeTo.DetachedFromVisualTree -= x)
+                .Select(x => false);
+
+            var attachmentStatus = attached.Merge(detached);
+            return attachmentStatus;
+        }
+    }
+}

+ 1 - 1
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
             throw new KeyNotFoundException($"Static resource '{ResourceKey}' not found.");
         }
 
-        private object GetValue(IControl control)
+        private object GetValue(IStyledElement control)
         {
             return control.FindResource(ResourceKey);
         }

+ 0 - 2
src/Markup/Avalonia.Markup/Avalonia.Markup.csproj

@@ -4,8 +4,6 @@
   </PropertyGroup>
   <ItemGroup>
     <ProjectReference Include="..\..\Avalonia.Base\Avalonia.Base.csproj" />
-    <ProjectReference Include="..\..\Avalonia.Controls\Avalonia.Controls.csproj" />
-    <ProjectReference Include="..\..\Avalonia.Input\Avalonia.Input.csproj" />
     <ProjectReference Include="..\..\Avalonia.Styling\Avalonia.Styling.csproj" />
     <ProjectReference Include="..\..\Avalonia.Visuals\Avalonia.Visuals.csproj" />
   </ItemGroup>

+ 17 - 24
src/Markup/Avalonia.Markup/Data/Binding.cs

@@ -104,7 +104,7 @@ namespace Avalonia.Markup.Data
             if (ElementName != null)
             {
                 observer = CreateElementObserver(
-                    (target as IControl) ?? (anchor as IControl),
+                    (target as IStyledElement) ?? (anchor as IStyledElement),
                     ElementName,
                     Path,
                     enableDataValidation);
@@ -115,10 +115,10 @@ namespace Avalonia.Markup.Data
             }
             else if (RelativeSource == null || RelativeSource.Mode == RelativeSourceMode.DataContext)
             {
-                observer = CreateDataContexObserver(
+                observer = CreateDataContextObserver(
                     target,
                     Path,
-                    targetProperty == Control.DataContextProperty,
+                    targetProperty == StyledElement.DataContextProperty,
                     anchor,
                     enableDataValidation);
             }
@@ -138,7 +138,7 @@ namespace Avalonia.Markup.Data
                 }
 
                 observer = CreateFindAncestorObserver(
-                    (target as IControl) ?? (anchor as IControl),
+                    (target as IStyledElement) ?? (anchor as IStyledElement),
                     RelativeSource,
                     Path,
                     enableDataValidation);
@@ -153,8 +153,8 @@ namespace Avalonia.Markup.Data
             // If we're binding to DataContext and our fallback is UnsetValue then override
             // the fallback value to null, as broken bindings to DataContext must reset the
             // DataContext in order to not propagate incorrect DataContexts to child controls.
-            // See Avalonia.Markup.Xaml.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
-            if (targetProperty == Control.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
+            // See Avalonia.Markup.UnitTests.Data.DataContext_Binding_Should_Produce_Correct_Results.
+            if (targetProperty == StyledElement.DataContextProperty && fallback == AvaloniaProperty.UnsetValue)
             {
                 fallback = null;
             }
@@ -170,7 +170,7 @@ namespace Avalonia.Markup.Data
             return new InstancedBinding(subject, Mode, Priority);
         }
 
-        private ExpressionObserver CreateDataContexObserver(
+        private ExpressionObserver CreateDataContextObserver(
             IAvaloniaObject target,
             string path,
             bool targetIsDataContext,
@@ -179,9 +179,9 @@ namespace Avalonia.Markup.Data
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
-            if (!(target is IControl))
+            if (!(target is IStyledElement))
             {
-                target = anchor as IControl;
+                target = anchor as IStyledElement;
 
                 if (target == null)
                 {
@@ -191,11 +191,11 @@ namespace Avalonia.Markup.Data
 
             if (!targetIsDataContext)
             {
-                var update = target.GetObservable(Control.DataContextProperty)
+                var update = target.GetObservable(StyledElement.DataContextProperty)
                     .Skip(1)
                     .Select(_ => Unit.Default);
                 var result = new ExpressionObserver(
-                    () => target.GetValue(Control.DataContextProperty),
+                    () => target.GetValue(StyledElement.DataContextProperty),
                     path,
                     update,
                     enableDataValidation);
@@ -212,7 +212,7 @@ namespace Avalonia.Markup.Data
         }
 
         private ExpressionObserver CreateElementObserver(
-            IControl target,
+            IStyledElement target,
             string elementName,
             string path,
             bool enableDataValidation)
@@ -229,7 +229,7 @@ namespace Avalonia.Markup.Data
         }
 
         private ExpressionObserver CreateFindAncestorObserver(
-            IControl target,
+            IStyledElement target,
             RelativeSource relativeSource,
             string path,
             bool enableDataValidation)
@@ -247,7 +247,7 @@ namespace Avalonia.Markup.Data
                         relativeSource.AncestorType);
                     break;
                 case TreeType.Visual:
-                    controlLocator = ControlLocator.Track(
+                    controlLocator = VisualLocator.Track(
                         (IVisual)target,
                         relativeSource.AncestorLevel - 1,
                         relativeSource.AncestorType);
@@ -279,12 +279,12 @@ namespace Avalonia.Markup.Data
         {
             Contract.Requires<ArgumentNullException>(target != null);
 
-            var update = target.GetObservable(Control.TemplatedParentProperty)
+            var update = target.GetObservable(StyledElement.TemplatedParentProperty)
                 .Skip(1)
                 .Select(_ => Unit.Default);
 
             var result = new ExpressionObserver(
-                () => target.GetValue(Control.TemplatedParentProperty),
+                () => target.GetValue(StyledElement.TemplatedParentProperty),
                 path,
                 update,
                 enableDataValidation);
@@ -303,16 +303,9 @@ namespace Avalonia.Markup.Data
             return target.GetObservable(Visual.VisualParentProperty)
                 .Select(x =>
                 {
-                    return (x as IAvaloniaObject)?.GetObservable(Control.DataContextProperty) ?? 
+                    return (x as IAvaloniaObject)?.GetObservable(StyledElement.DataContextProperty) ?? 
                            Observable.Return((object)null);
                 }).Switch();
         }
-
-        private class PathInfo
-        {
-            public string Path { get; set; }
-            public string ElementName { get; set; }
-            public RelativeSource RelativeSource { get; set; }
-        }
     }
 }

+ 12 - 13
src/Markup/Avalonia.Markup/Data/DelayedBinding.cs

@@ -24,8 +24,8 @@ namespace Avalonia.Markup.Data
     /// </remarks>
     public static class DelayedBinding
     {
-        private static ConditionalWeakTable<IControl, List<Entry>> _entries = 
-            new ConditionalWeakTable<IControl, List<Entry>>();
+        private static ConditionalWeakTable<IStyledElement, List<Entry>> _entries = 
+            new ConditionalWeakTable<IStyledElement, List<Entry>>();
 
         /// <summary>
         /// Adds a delayed binding to a control.
@@ -33,7 +33,7 @@ namespace Avalonia.Markup.Data
         /// <param name="target">The control.</param>
         /// <param name="property">The property on the control to bind to.</param>
         /// <param name="binding">The binding.</param>
-        public static void Add(IControl target, AvaloniaProperty property, IBinding binding)
+        public static void Add(IStyledElement target, AvaloniaProperty property, IBinding binding)
         {
             if (target.IsInitialized)
             {
@@ -41,9 +41,8 @@ namespace Avalonia.Markup.Data
             }
             else
             {
-                List<Entry> bindings;
 
-                if (!_entries.TryGetValue(target, out bindings))
+                if (!_entries.TryGetValue(target, out List<Entry> bindings))
                 {
                     bindings = new List<Entry>();
                     _entries.Add(target, bindings);
@@ -62,7 +61,7 @@ namespace Avalonia.Markup.Data
         /// <param name="target">The control.</param>
         /// <param name="property">The property on the control to bind to.</param>
         /// <param name="value">A function which returns the value.</param>
-        public static void Add(IControl target, PropertyInfo property, Func<IControl, object> value)
+        public static void Add(IStyledElement target, PropertyInfo property, Func<IStyledElement, object> value)
         {
             if (target.IsInitialized)
             {
@@ -89,7 +88,7 @@ namespace Avalonia.Markup.Data
         /// Applies any delayed bindings to a control.
         /// </summary>
         /// <param name="control">The control.</param>
-        public static void ApplyBindings(IControl control)
+        public static void ApplyBindings(IStyledElement control)
         {
             List<Entry> entries;
 
@@ -106,14 +105,14 @@ namespace Avalonia.Markup.Data
 
         private static void ApplyBindings(object sender, EventArgs e)
         {
-            var target = (IControl)sender;
+            var target = (IStyledElement)sender;
             ApplyBindings(target);
             target.Initialized -= ApplyBindings;
         }
 
         private abstract class Entry
         {
-            public abstract void Apply(IControl control);
+            public abstract void Apply(IStyledElement control);
         }
 
         private class BindingEntry : Entry
@@ -127,7 +126,7 @@ namespace Avalonia.Markup.Data
             public IBinding Binding { get; }
             public AvaloniaProperty Property { get; }
 
-            public override void Apply(IControl control)
+            public override void Apply(IStyledElement control)
             {
                 control.Bind(Property, Binding);
             }
@@ -135,16 +134,16 @@ namespace Avalonia.Markup.Data
 
         private class ClrPropertyValueEntry : Entry
         {
-            public ClrPropertyValueEntry(PropertyInfo property, Func<IControl, object> value)
+            public ClrPropertyValueEntry(PropertyInfo property, Func<IStyledElement, object> value)
             {
                 Property = property;
                 Value = value;
             }
 
             public PropertyInfo Property { get; }
-            public Func<IControl, object> Value { get; }
+            public Func<IStyledElement, object> Value { get; }
 
-            public override void Apply(IControl control)
+            public override void Apply(IStyledElement control)
             {
                 try
                 {

+ 16 - 1
src/Markup/Avalonia.Markup/Data/RelativeSource.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 Avalonia.Controls;
 
 namespace Avalonia.Markup.Data
 {
@@ -32,6 +31,22 @@ namespace Avalonia.Markup.Data
         FindAncestor,
     }
 
+
+    /// <summary>
+    /// The type of tree via which to track a control.
+    /// </summary>
+    public enum TreeType
+    {
+        /// <summary>
+        /// The visual tree.
+        /// </summary>
+        Visual,
+        /// <summary>
+        /// The logical tree.
+        /// </summary>
+        Logical,
+    }
+
     /// <summary>
     /// Describes the the location of a binding source, relative to the binding target.
     /// </summary>

+ 2 - 2
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@@ -74,7 +74,7 @@ namespace Avalonia.Controls.UnitTests
                 root.Content = target;
 
                 var templatedParent = new Button();
-                target.TemplatedParent = templatedParent;
+                target.SetValue(StyledElement.TemplatedParentProperty, templatedParent);
                 target.Template = GetTemplate();
 
                 target.Items = new[] { "Foo" };
@@ -360,7 +360,7 @@ namespace Avalonia.Controls.UnitTests
 
             var presenter = new ItemsPresenter
             {
-                TemplatedParent = target,
+                [StyledElement.TemplatedParentProperty] = target,
                 [~ItemsPresenter.ItemsProperty] = target[~ItemsControl.ItemsProperty],
             };
 

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs

@@ -43,7 +43,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             var parent = new TestItemsControl();
             var target = new CarouselPresenter
             {
-                TemplatedParent = parent,
+                [StyledElement.TemplatedParentProperty] = parent,
             };
 
             Assert.IsType<ItemContainerGenerator<TestItem>>(target.ItemContainerGenerator);

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs

@@ -69,7 +69,7 @@ namespace Avalonia.Controls.UnitTests.Presenters
             var parent = new TestItemsControl();
             var target = new ItemsPresenter
             {
-                TemplatedParent = parent,
+                [StyledElement.TemplatedParentProperty] = parent,
             };
 
             Assert.IsType<ItemContainerGenerator<TestItem>>(target.ItemContainerGenerator);

+ 14 - 6
tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs

@@ -18,12 +18,20 @@ namespace Avalonia.Controls.Templates.UnitTests
         public void GetTemplateChildren_Should_Not_Return_Nested_Template_Controls()
         {
             var target = new TestTemplatedControl();
-            var border1 = new Border { Name = "border1", TemplatedParent = target };
-            var inner = new TestTemplatedControl { Name = "inner", TemplatedParent = target };
-            var border2 = new Border { Name = "border2", TemplatedParent = inner };
-            var border3 = new Border { Name = "border3", TemplatedParent = inner };
-            var border4 = new Border { Name = "border4", TemplatedParent = target };
-            var border5 = new Border { Name = "border5", TemplatedParent = null };
+            var border1 = new Border
+            {
+                Name = "border1",
+                [StyledElement.TemplatedParentProperty] = target,
+            };
+            var inner = new TestTemplatedControl
+            {
+                Name = "inner",
+                [StyledElement.TemplatedParentProperty] = target,
+            };
+            var border2 = new Border { Name = "border2", [StyledElement.TemplatedParentProperty] = inner };
+            var border3 = new Border { Name = "border3", [StyledElement.TemplatedParentProperty] = inner };
+            var border4 = new Border { Name = "border4", [StyledElement.TemplatedParentProperty] = target };
+            var border5 = new Border { Name = "border5", [StyledElement.TemplatedParentProperty] = null };
 
             target.AddVisualChild(border1);
             border1.Child = inner;

+ 1 - 1
tests/Avalonia.Controls.UnitTests/Utils/AncestorFinderTests.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Controls.UnitTests.Utils
             var grandParent = new Border();
             var grandParent2 = new Border();
 
-            IVisual currentParent = null;
+            IStyledElement currentParent = null;
             var subscription = AncestorFinder.Create(child, typeof (Border)).Subscribe(s => currentParent = s);
 
             Assert.Null(currentParent);

+ 1 - 1
tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs

@@ -184,7 +184,7 @@ namespace Avalonia.Markup.UnitTests.Data
             // When binding to DataContext and the target isn't found, the binding should produce
             // null rather than UnsetValue in order to not propagate incorrect DataContexts from
             // parent controls while things are being set up. This logic is implemented in 
-            // `Avalonia.Markup.Xaml.Binding.Initiate`.
+            // `Avalonia.Markup.Data.Binding.Initiate`.
             Assert.True(child.IsSet(Control.DataContextProperty));
 
             root.Child = child;

+ 9 - 8
tests/Avalonia.Controls.UnitTests/ControlTests.cs → tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@@ -9,15 +9,16 @@ using Avalonia.Styling;
 using Avalonia.UnitTests;
 using Xunit;
 using Avalonia.LogicalTree;
+using Avalonia.Controls;
 
-namespace Avalonia.Controls.UnitTests
+namespace Avalonia.Styling.UnitTests
 {
-    public class ControlTests
+    public class StyledElementTests
     {
         [Fact]
         public void Classes_Should_Initially_Be_Empty()
         {
-            var target = new Control();
+            var target = new StyledElement();
 
             Assert.Empty(target.Classes);
         }
@@ -116,7 +117,7 @@ namespace Avalonia.Controls.UnitTests
                 raised.Add("attached");
             };
 
-            child.GetObservable(Control.ParentProperty).Skip(1).Subscribe(_ => raised.Add("parent"));
+            child.GetObservable(StyledElement.ParentProperty).Skip(1).Subscribe(_ => raised.Add("parent"));
 
             root.Child = child;
 
@@ -354,7 +355,7 @@ namespace Avalonia.Controls.UnitTests
             };
 
             var called = new List<string>();
-            void Record(object sender, EventArgs e) => called.Add(((Control)sender).Name);
+            void Record(object sender, EventArgs e) => called.Add(((StyledElement)sender).Name);
 
             root.DataContextChanged += Record;
 
@@ -396,9 +397,9 @@ namespace Avalonia.Controls.UnitTests
 
             foreach (IDataContextEvents c in root.GetSelfAndLogicalDescendants())
             {
-                c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((Control)s).Name);
-                c.DataContextChanged += (s, e) => called.Add("changed " + ((Control)s).Name);
-                c.DataContextEndUpdate += (s, e) => called.Add("end " + ((Control)s).Name);
+                c.DataContextBeginUpdate += (s, e) => called.Add("begin " + ((StyledElement)s).Name);
+                c.DataContextChanged += (s, e) => called.Add("changed " + ((StyledElement)s).Name);
+                c.DataContextEndUpdate += (s, e) => called.Add("end " + ((StyledElement)s).Name);
             }
 
             root.DataContext = "foo";

+ 2 - 2
tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs → tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs

@@ -11,7 +11,7 @@ using Xunit;
 
 namespace Avalonia.Controls.UnitTests
 {
-    public class ControlTests_NameScope
+    public class StyledElementTests_NameScope
     {
         [Fact]
         public void Controls_Should_Register_With_NameScope()
@@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests
 
             root.ApplyTemplate();
 
-            Assert.Null(NameScope.GetNameScope((Control)root.Presenter).Find("foo"));
+            Assert.Null(NameScope.GetNameScope((StyledElement)root.Presenter).Find("foo"));
         }
 
         [Fact]

+ 6 - 6
tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs → tests/Avalonia.Styling.UnitTests/StyledElementTests_Resources.cs

@@ -10,12 +10,12 @@ using Xunit;
 
 namespace Avalonia.Controls.UnitTests
 {
-    public class ControlTests_Resources
+    public class StyledElementTests_Resources
     {
         [Fact]
         public void FindResource_Should_Find_Control_Resource()
         {
-            var target = new Control
+            var target = new StyledElement
             {
                 Resources =
                 {
@@ -68,7 +68,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void FindResource_Should_Find_Style_Resource()
         {
-            var target = new Control
+            var target = new StyledElement
             {
                 Styles =
                 {
@@ -92,7 +92,7 @@ namespace Avalonia.Controls.UnitTests
         [Fact]
         public void FindResource_Should_Find_Styles_Resource()
         {
-            var target = new Control
+            var target = new StyledElement
             {
                 Styles =
                 {
@@ -188,7 +188,7 @@ namespace Avalonia.Controls.UnitTests
         public void Adding_Resource_To_Nested_Style_Should_Raise_ResourceChanged()
         {
             Style style;
-            var target = new Decorator
+            var target = new StyledElement
             {
                 Styles =
                 {
@@ -208,7 +208,7 @@ namespace Avalonia.Controls.UnitTests
         public void Setting_Logical_Parent_Subscribes_To_Parents_ResourceChanged_Event()
         {
             var parent = new ContentControl();
-            var child = new Border();
+            var child = new StyledElement();
 
             ((ISetLogicalParent)child).SetParent(parent);
             var raisedOnChild = false;