diff --git a/src/Avalonia.Base/AvaloniaObject.cs b/src/Avalonia.Base/AvaloniaObject.cs
index 1a2db9fc3d..48e72db126 100644
--- a/src/Avalonia.Base/AvaloniaObject.cs
+++ b/src/Avalonia.Base/AvaloniaObject.cs
@@ -703,7 +703,7 @@ namespace Avalonia
/// The default value.
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());
}
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 89721e2e05..2bcd540264 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -29,53 +29,23 @@ namespace Avalonia.Controls
///
/// The control class extends and adds the following features:
///
- /// - An inherited .
/// - A property to allow user-defined data to be attached to the control.
- /// - A collection of class strings for custom styling.
- /// - Implements to allow styling to work on the control.
- /// - Implements to form part of a logical tree.
+
///
- public class Control : InputElement, IControl, INamed, ISetInheritanceParent, ISetLogicalParent, ISupportInitialize, IVisualBrushInitialize
+ public class Control : InputElement, IControl, INamed, ISupportInitialize, IVisualBrushInitialize, IRequiresTemplateInStyle
{
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty DataContextProperty =
- AvaloniaProperty.Register(
- nameof(DataContext),
- inherits: true,
- notifying: DataContextNotifying);
-
///
/// Defines the property.
///
public static readonly StyledProperty> FocusAdornerProperty =
AvaloniaProperty.Register>(nameof(FocusAdorner));
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty NameProperty =
- AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v);
-
- ///
- /// Defines the property.
- ///
- public static readonly DirectProperty ParentProperty =
- AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent);
-
///
/// Defines the property.
///
public static readonly StyledProperty TagProperty =
AvaloniaProperty.Register(nameof(Tag));
-
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty TemplatedParentProperty =
- AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true);
-
+
///
/// Defines the property.
///
@@ -88,20 +58,8 @@ namespace Avalonia.Controls
public static readonly RoutedEvent RequestBringIntoViewEvent =
RoutedEvent.Register("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 _logicalChildren;
- private INameScope _nameScope;
- private IResourceDictionary _resources;
- private Styles _styles;
- private bool _styled;
- private Subject _styleDetach = new Subject();
- private bool _dataContextUpdating;
///
/// Initializes static members of the class.
@@ -112,127 +70,6 @@ namespace Avalonia.Controls
PseudoClass(IsEnabledCoreProperty, x => !x, ":disabled");
PseudoClass(IsFocusedProperty, ":focus");
PseudoClass(IsPointerOverProperty, ":pointerover");
- DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore);
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- public Control()
- {
- _nameScope = this as INameScope;
- _isAttachedToLogicalTree = this is IStyleRoot;
- }
-
- ///
- /// Raised when the control is attached to a rooted logical tree.
- ///
- public event EventHandler AttachedToLogicalTree;
-
- ///
- /// Raised when the control is detached from a rooted logical tree.
- ///
- public event EventHandler DetachedFromLogicalTree;
-
- ///
- /// Occurs when the property changes.
- ///
- ///
- /// This event will be raised when the property has changed and
- /// all subscribers to that change have been notified.
- ///
- public event EventHandler DataContextChanged;
-
- ///
- /// Occurs when the control has finished initialization.
- ///
- ///
- /// The Initialized event indicates that all property values on the control have been set.
- /// When loading the control from markup, it occurs when
- /// is called *and* the control
- /// is attached to a rooted logical tree. When the control is created by code and
- /// is not used, it is called when the control is attached
- /// to the visual tree.
- ///
- public event EventHandler Initialized;
-
- ///
- /// Occurs when a resource in this control or a parent control has changed.
- ///
- public event EventHandler ResourcesChanged;
-
- ///
- /// Gets or sets the name of the control.
- ///
- ///
- /// 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.
- ///
- 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;
- }
- }
-
- ///
- /// Gets or sets the control's classes.
- ///
- ///
- ///
- /// Classes can be used to apply user-defined styling to controls, or to allow controls
- /// that share a common purpose to be easily selected.
- ///
- ///
- /// 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.
- ///
- ///
- public Classes Classes
- {
- get
- {
- return _classes;
- }
-
- set
- {
- if (_classes != value)
- {
- _classes.Replace(value);
- }
- }
- }
-
- ///
- /// Gets or sets the control's data context.
- ///
- ///
- /// The data context is an inherited property that specifies the default object that will
- /// be used for data binding.
- ///
- public object DataContext
- {
- get { return GetValue(DataContextProperty); }
- set { SetValue(DataContextProperty, value); }
}
///
@@ -253,55 +90,6 @@ namespace Avalonia.Controls
///
public DataTemplates DataTemplates => _dataTemplates ?? (_dataTemplates = new DataTemplates());
- ///
- /// Gets a value that indicates whether the element has finished initialization.
- ///
- ///
- /// For more information about when IsInitialized is set, see the
- /// event.
- ///
- public bool IsInitialized { get; private set; }
-
- ///
- /// Gets the styles for the control.
- ///
- ///
- /// 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.
- ///
- public Styles Styles
- {
- get { return _styles ?? (Styles = new Styles()); }
- set
- {
- Contract.Requires(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;
- }
- }
- }
-
- ///
- /// Gets the control's logical parent.
- ///
- public IControl Parent => _parent;
-
///
/// Gets or sets a context menu to the control.
///
@@ -311,34 +99,6 @@ namespace Avalonia.Controls
set { SetValue(ContextMenuProperty, value); }
}
- ///
- /// Gets or sets the control's resource dictionary.
- ///
- public IResourceDictionary Resources
- {
- get => _resources ?? (Resources = new ResourceDictionary());
- set
- {
- Contract.Requires(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());
- }
- }
- }
-
///
/// Gets or sets a user-defined object attached to the control.
///
@@ -348,226 +108,11 @@ namespace Avalonia.Controls
set { SetValue(TagProperty, value); }
}
- ///
- /// Gets the control whose lookless template this control is part of.
- ///
- public ITemplatedControl TemplatedParent
- {
- get { return GetValue(TemplatedParentProperty); }
- internal set { SetValue(TemplatedParentProperty, value); }
- }
-
- ///
- /// Gets the control's logical children.
- ///
- protected IAvaloniaList LogicalChildren
- {
- get
- {
- if (_logicalChildren == null)
- {
- var list = new AvaloniaList();
- list.ResetBehavior = ResetBehavior.Remove;
- list.Validate = ValidateLogicalChild;
- list.CollectionChanged += LogicalChildrenCollectionChanged;
- _logicalChildren = list;
- }
-
- return _logicalChildren;
- }
- }
+ public new IControl Parent => (IControl)base.Parent;
///
bool IDataTemplateHost.IsDataTemplatesInitialized => _dataTemplates != null;
- ///
- /// Gets the collection in a form that allows adding and removing
- /// pseudoclasses.
- ///
- protected IPseudoClasses PseudoClasses => Classes;
-
- ///
- /// Gets a value indicating whether the element is attached to a rooted logical tree.
- ///
- bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
-
- ///
- /// Gets the control's logical parent.
- ///
- ILogical ILogical.LogicalParent => Parent;
-
- ///
- /// Gets the control's logical children.
- ///
- IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren;
-
- ///
- bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
-
- ///
- IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
-
- ///
- IAvaloniaReadOnlyList IStyleable.Classes => Classes;
-
- ///
- /// Gets the type by which the control is styled.
- ///
- ///
- /// 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.
- ///
- Type IStyleable.StyleKey => GetType();
-
- ///
- IObservable IStyleable.StyleDetach => _styleDetach;
-
- ///
- bool IStyleHost.IsStylesInitialized => _styles != null;
-
- ///
- IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
-
- ///
- public virtual void BeginInit()
- {
- ++_initCount;
- }
-
- ///
- 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);
- }
- }
-
- ///
- void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- this.OnAttachedToLogicalTreeCore(e);
- }
-
- ///
- void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- this.OnDetachedFromLogicalTreeCore(e);
- }
-
- ///
- void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
- {
- ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
- }
-
- ///
- bool IResourceProvider.TryGetResource(string key, out object value)
- {
- value = null;
- return (_resources?.TryGetResource(key, out value) ?? false) ||
- (_styles?.TryGetResource(key, out value) ?? false);
- }
-
- ///
- /// Sets the control's logical parent.
- ///
- /// The parent.
- 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);
- }
- }
-
- ///
- /// Sets the control's inheritance parent.
- ///
- /// The parent.
- void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
- {
- InheritanceParent = parent;
- }
-
///
void IVisualBrushInitialize.EnsureInitialized()
{
@@ -600,52 +145,6 @@ namespace Avalonia.Controls
}
}
- ///
- /// Adds a pseudo-class to be set when a property is true.
- ///
- /// The property.
- /// The pseudo-class.
- protected static void PseudoClass(AvaloniaProperty property, string className)
- {
- PseudoClass(property, x => x, className);
- }
-
- ///
- /// Adds a pseudo-class to be set when a property equals a certain value.
- ///
- /// The type of the property.
- /// The property.
- /// Returns a boolean value based on the property value.
- /// The pseudo-class.
- protected static void PseudoClass(
- AvaloniaProperty property,
- Func selector,
- string className)
- {
- Contract.Requires(property != null);
- Contract.Requires(selector != null);
- Contract.Requires(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);
- }
- });
- }
-
///
/// Gets the element that recieves the focus adorner.
///
@@ -655,22 +154,6 @@ namespace Avalonia.Controls
return this;
}
- ///
- /// Called when the control is added to a rooted logical tree.
- ///
- /// The event args.
- protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- }
-
- ///
- /// Called when the control is removed from a rooted logical tree.
- ///
- /// The event args.
- protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
- {
- }
-
///
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
@@ -685,29 +168,6 @@ namespace Avalonia.Controls
base.OnDetachedFromVisualTreeCore(e);
}
- ///
- /// Called when the property changes.
- ///
- /// The event args.
- protected virtual void OnDataContextChanged(EventArgs e)
- {
- DataContextChanged?.Invoke(this, EventArgs.Empty);
- }
-
- ///
- /// Called when the begins updating.
- ///
- protected virtual void OnDataContextBeginUpdate()
- {
- }
-
- ///
- /// Called when the finishes updating.
- ///
- protected virtual void OnDataContextEndUpdate()
- {
- }
-
///
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()?.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())
- {
- 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())
- {
- 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());
- break;
-
- case NotifyCollectionChangedAction.Remove:
- ClearLogicalParent(e.OldItems.Cast());
- break;
-
- case NotifyCollectionChangedAction.Replace:
- ClearLogicalParent(e.OldItems.Cast());
- SetLogicalParent(e.NewItems.Cast());
- break;
-
- case NotifyCollectionChangedAction.Reset:
- throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
- }
- }
-
- private void SetLogicalParent(IEnumerable children)
- {
- foreach (var i in children)
- {
- if (i.LogicalParent == null)
- {
- ((ISetLogicalParent)i).SetParent(this);
- }
- }
- }
-
- private void ClearLogicalParent(IEnumerable 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);
- }
}
}
diff --git a/src/Avalonia.Controls/HotkeyManager.cs b/src/Avalonia.Controls/HotkeyManager.cs
index 35c9d6a9c5..a59fb86fb7 100644
--- a/src/Avalonia.Controls/HotkeyManager.cs
+++ b/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(_control).Subscribe(OnParentChanged);
}
- private void OnParentChanged(IControl control)
+ private void OnParentChanged(TopLevel control)
{
Unregister();
- _root = (TopLevel) control;
+ _root = control;
Register();
}
diff --git a/src/Avalonia.Controls/IControl.cs b/src/Avalonia.Controls/IControl.cs
index 36e09b2ea1..e7f2903249 100644
--- a/src/Avalonia.Controls/IControl.cs
+++ b/src/Avalonia.Controls/IControl.cs
@@ -16,37 +16,11 @@ namespace Avalonia.Controls
///
public interface IControl : IVisual,
IDataTemplateHost,
- ILogical,
ILayoutable,
IInputElement,
INamed,
- IResourceNode,
- IStyleable,
- IStyleHost
+ IStyledElement
{
- ///
- /// Occurs when the control has finished initialization.
- ///
- event EventHandler Initialized;
-
- ///
- /// Gets or sets the control's styling classes.
- ///
- new Classes Classes { get; set; }
-
- ///
- /// Gets or sets the control's data context.
- ///
- object DataContext { get; set; }
-
- ///
- /// Gets a value that indicates whether the element has finished initialization.
- ///
- bool IsInitialized { get; }
-
- ///
- /// Gets the control's logical parent.
- ///
- IControl Parent { get; }
+ new IControl Parent { get; }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Utils/AncestorFinder.cs b/src/Avalonia.Controls/Utils/AncestorFinder.cs
index 7e6523261a..6ee284e05d 100644
--- a/src/Avalonia.Controls/Utils/AncestorFinder.cs
+++ b/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 Observable => _subject;
- private readonly Subject _subject = new Subject();
+ public IObservable Observable => _subject;
+ private readonly Subject _subject = new Subject();
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 Create(IStyledElement control)
+ where T : IStyledElement
+ {
+ return Create(control, typeof(T)).Cast();
+ }
- public static IObservable Create(IControl control, Type ancestorType)
+ public static IObservable Create(IStyledElement control, Type ancestorType)
{
- return new AnonymousObservable(observer =>
+ return new AnonymousObservable(observer =>
{
var finder = new FinderNode(control, ancestorType.GetTypeInfo());
var subscription = finder.Observable.Subscribe(observer);
@@ -70,8 +76,6 @@ namespace Avalonia.Controls.Utils
finder.Dispose();
});
});
-
-
}
}
}
diff --git a/src/Avalonia.Controls/Classes.cs b/src/Avalonia.Styling/Controls/Classes.cs
similarity index 95%
rename from src/Avalonia.Controls/Classes.cs
rename to src/Avalonia.Styling/Controls/Classes.cs
index 48ce69f0c9..85935b030b 100644
--- a/src/Avalonia.Controls/Classes.cs
+++ b/src/Avalonia.Styling/Controls/Classes.cs
@@ -9,7 +9,7 @@ using Avalonia.Collections;
namespace Avalonia.Controls
{
///
- /// Holds a collection of style classes for an .
+ /// Holds a collection of style classes for an .
///
///
/// Similar to CSS, each control may have any number of styling classes applied.
@@ -54,7 +54,7 @@ namespace Avalonia.Controls
/// The class name.
///
/// Only standard classes may be added via this method. To add pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void Add(string name)
@@ -73,7 +73,7 @@ namespace Avalonia.Controls
/// The class names.
///
/// Only standard classes may be added via this method. To add pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void AddRange(IEnumerable names)
@@ -114,7 +114,7 @@ namespace Avalonia.Controls
/// The class name.
///
/// Only standard classes may be added via this method. To add pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void Insert(int index, string name)
@@ -134,7 +134,7 @@ namespace Avalonia.Controls
/// The class names.
///
/// Only standard classes may be added via this method. To add pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void InsertRange(int index, IEnumerable names)
@@ -160,7 +160,7 @@ namespace Avalonia.Controls
/// The class name.
///
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override bool Remove(string name)
@@ -175,7 +175,7 @@ namespace Avalonia.Controls
/// The class name.
///
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void RemoveAll(IEnumerable names)
@@ -201,7 +201,7 @@ namespace Avalonia.Controls
/// The index of the class in the collection.
///
/// Only standard classes may be removed via this method. To remove pseudoclasses (classes
- /// beginning with a ':' character) use the protected
+ /// beginning with a ':' character) use the protected
/// property.
///
public override void RemoveAt(int index)
diff --git a/src/Avalonia.Controls/IPseudoClasses.cs b/src/Avalonia.Styling/Controls/IPseudoClasses.cs
similarity index 100%
rename from src/Avalonia.Controls/IPseudoClasses.cs
rename to src/Avalonia.Styling/Controls/IPseudoClasses.cs
diff --git a/src/Avalonia.Controls/ISetInheritanceParent.cs b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
similarity index 88%
rename from src/Avalonia.Controls/ISetInheritanceParent.cs
rename to src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
index 788ab77246..1a778fa0f9 100644
--- a/src/Avalonia.Controls/ISetInheritanceParent.cs
+++ b/src/Avalonia.Styling/Controls/ISetInheritanceParent.cs
@@ -4,7 +4,7 @@
namespace Avalonia.Controls
{
///
- /// Defines an interface through which a 's inheritance parent can be set.
+ /// Defines an interface through which a 's inheritance parent can be set.
///
///
/// You should not usually need to use this interface - it is for advanced scenarios only.
diff --git a/src/Avalonia.Controls/ISetLogicalParent.cs b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs
similarity index 85%
rename from src/Avalonia.Controls/ISetLogicalParent.cs
rename to src/Avalonia.Styling/Controls/ISetLogicalParent.cs
index 4422033634..5fb450c896 100644
--- a/src/Avalonia.Controls/ISetLogicalParent.cs
+++ b/src/Avalonia.Styling/Controls/ISetLogicalParent.cs
@@ -6,7 +6,7 @@ using Avalonia.LogicalTree;
namespace Avalonia.Controls
{
///
- /// Defines an interface through which a 's logical parent can be set.
+ /// Defines an interface through which a 's logical parent can be set.
///
///
/// You should not usually need to use this interface - it is for advanced scenarios only.
diff --git a/src/Avalonia.Styling/Controls/NameScope.cs b/src/Avalonia.Styling/Controls/NameScope.cs
index 8b5bd81d3c..e3a29af541 100644
--- a/src/Avalonia.Styling/Controls/NameScope.cs
+++ b/src/Avalonia.Styling/Controls/NameScope.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Controls
/// Defines the NameScope attached property.
///
public static readonly AttachedProperty NameScopeProperty =
- AvaloniaProperty.RegisterAttached("NameScope");
+ AvaloniaProperty.RegisterAttached("NameScope");
private readonly Dictionary _inner = new Dictionary();
@@ -31,53 +31,53 @@ namespace Avalonia.Controls
public event EventHandler Unregistered;
///
- /// Finds the containing name scope for a visual.
+ /// Finds the containing name scope for a styled element.
///
- /// The visual.
+ /// The styled element.
/// The containing name scope.
- public static INameScope FindNameScope(Visual visual)
+ public static INameScope FindNameScope(StyledElement styled)
{
- Contract.Requires(visual != null);
+ Contract.Requires(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;
}
///
- /// Gets the value of the attached on a visual.
+ /// Gets the value of the attached on a styled element.
///
- /// The visual.
+ /// The styled element.
/// The value of the NameScope attached property.
- public static INameScope GetNameScope(Visual visual)
+ public static INameScope GetNameScope(StyledElement styled)
{
- Contract.Requires(visual != null);
+ Contract.Requires(styled != null);
- return visual.GetValue(NameScopeProperty);
+ return styled.GetValue(NameScopeProperty);
}
///
- /// Sets the value of the attached on a visual.
+ /// Sets the value of the attached on a styled element.
///
- /// The visual.
+ /// The styled element.
/// The value to set.
- public static void SetNameScope(Visual visual, INameScope value)
+ public static void SetNameScope(StyledElement styled, INameScope value)
{
- Contract.Requires(visual != null);
+ Contract.Requires(styled != null);
- visual.SetValue(NameScopeProperty, value);
+ styled.SetValue(NameScopeProperty, value);
}
///
diff --git a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs b/src/Avalonia.Styling/Controls/NameScopeExtensions.cs
index 49be842ffc..491e4d71a7 100644
--- a/src/Avalonia.Styling/Controls/NameScopeExtensions.cs
+++ b/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(control != null);
return control.GetSelfAndLogicalAncestors()
- .OfType()
+ .OfType()
.Select(x => (x as INameScope) ?? NameScope.GetNameScope(x))
.FirstOrDefault(x => x != null);
}
diff --git a/src/Avalonia.Visuals/INamed.cs b/src/Avalonia.Styling/INamed.cs
similarity index 100%
rename from src/Avalonia.Visuals/INamed.cs
rename to src/Avalonia.Styling/INamed.cs
diff --git a/src/Avalonia.Styling/IStyledElement.cs b/src/Avalonia.Styling/IStyledElement.cs
new file mode 100644
index 0000000000..8369717233
--- /dev/null
+++ b/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
+ {
+ ///
+ /// Occurs when the control has finished initialization.
+ ///
+ event EventHandler Initialized;
+
+ ///
+ /// Gets a value that indicates whether the element has finished initialization.
+ ///
+ bool IsInitialized { get; }
+
+ ///
+ /// Gets or sets the control's styling classes.
+ ///
+ new Classes Classes { get; set; }
+
+ ///
+ /// Gets or sets the control's data context.
+ ///
+ object DataContext { get; set; }
+
+ ///
+ /// Gets the control's logical parent.
+ ///
+ IStyledElement Parent { get; }
+ }
+}
diff --git a/src/Avalonia.Styling/Controls/ControlLocator.cs b/src/Avalonia.Styling/LogicalTree/ControlLocator.cs
similarity index 68%
rename from src/Avalonia.Styling/Controls/ControlLocator.cs
rename to src/Avalonia.Styling/LogicalTree/ControlLocator.cs
index e03ec722ad..2858d11d9d 100644
--- a/src/Avalonia.Styling/Controls/ControlLocator.cs
+++ b/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
{
- ///
- /// The type of tree via which to track a control.
- ///
- public enum TreeType
- {
- ///
- /// The visual tree.
- ///
- Visual,
- ///
- /// The logical tree.
- ///
- Logical,
- }
-
///
/// Locates controls relative to other controls.
///
@@ -94,40 +77,6 @@ namespace Avalonia.Controls
});
}
- public static IObservable 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 TrackAttachmentToTree(IVisual relativeTo)
- {
- var attached = Observable.FromEventPattern(
- x => relativeTo.AttachedToVisualTree += x,
- x => relativeTo.AttachedToVisualTree -= x)
- .Select(x => true)
- .StartWith(relativeTo.IsAttachedToVisualTree);
-
- var detached = Observable.FromEventPattern(
- x => relativeTo.DetachedFromVisualTree += x,
- x => relativeTo.DetachedFromVisualTree -= x)
- .Select(x => false);
-
- var attachmentStatus = attached.Merge(detached);
- return attachmentStatus;
- }
-
private static IObservable TrackAttachmentToTree(ILogical relativeTo)
{
var attached = Observable.FromEventPattern(
diff --git a/src/Avalonia.Styling/StyledElement.cs b/src/Avalonia.Styling/StyledElement.cs
new file mode 100644
index 0000000000..bd0d0b8c1f
--- /dev/null
+++ b/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
+{
+ ///
+ /// Extends an with the following features:
+ ///
+ /// - An inherited .
+ /// - Implements to allow styling to work on the styled element.
+ /// - Implements to form part of a logical tree.
+ /// - A collection of class strings for custom styling.
+ ///
+ public class StyledElement : Animatable, IStyledElement, ISetLogicalParent, ISetInheritanceParent
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty DataContextProperty =
+ AvaloniaProperty.Register(
+ nameof(DataContext),
+ inherits: true,
+ notifying: DataContextNotifying);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty NameProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Name), o => o.Name, (o, v) => o.Name = v);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty ParentProperty =
+ AvaloniaProperty.RegisterDirect(nameof(Parent), o => o.Parent);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty TemplatedParentProperty =
+ AvaloniaProperty.Register(nameof(TemplatedParent), inherits: true);
+
+ private int _initCount;
+ private string _name;
+ private readonly Classes _classes = new Classes();
+ private bool _isAttachedToLogicalTree;
+ private IAvaloniaList _logicalChildren;
+ private INameScope _nameScope;
+ private IResourceDictionary _resources;
+ private Styles _styles;
+ private bool _styled;
+ private Subject _styleDetach = new Subject();
+ private bool _dataContextUpdating;
+
+ ///
+ /// Initializes static members of the class.
+ ///
+ static StyledElement()
+ {
+ DataContextProperty.Changed.AddClassHandler(x => x.OnDataContextChangedCore);
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public StyledElement()
+ {
+ _nameScope = this as INameScope;
+ _isAttachedToLogicalTree = this is IStyleRoot;
+ }
+
+ ///
+ /// Raised when the styled element is attached to a rooted logical tree.
+ ///
+ public event EventHandler AttachedToLogicalTree;
+
+ ///
+ /// Raised when the styled element is detached from a rooted logical tree.
+ ///
+ public event EventHandler DetachedFromLogicalTree;
+
+ ///
+ /// Occurs when the property changes.
+ ///
+ ///
+ /// This event will be raised when the property has changed and
+ /// all subscribers to that change have been notified.
+ ///
+ public event EventHandler DataContextChanged;
+
+ ///
+ /// Occurs when the styled element has finished initialization.
+ ///
+ ///
+ /// 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
+ /// is called *and* the styled element
+ /// is attached to a rooted logical tree. When the styled element is created by code and
+ /// is not used, it is called when the styled element is attached
+ /// to the visual tree.
+ ///
+ public event EventHandler Initialized;
+
+ ///
+ /// Occurs when a resource in this styled element or a parent styled element has changed.
+ ///
+ public event EventHandler ResourcesChanged;
+
+ ///
+ /// Gets or sets the name of the styled element.
+ ///
+ ///
+ /// 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.
+ ///
+ 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;
+ }
+ }
+
+ ///
+ /// Gets or sets the styled element's classes.
+ ///
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ ///
+ ///
+ public Classes Classes
+ {
+ get
+ {
+ return _classes;
+ }
+
+ set
+ {
+ if (_classes != value)
+ {
+ _classes.Replace(value);
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the control's data context.
+ ///
+ ///
+ /// The data context is an inherited property that specifies the default object that will
+ /// be used for data binding.
+ ///
+ public object DataContext
+ {
+ get { return GetValue(DataContextProperty); }
+ set { SetValue(DataContextProperty, value); }
+ }
+
+ ///
+ /// Gets a value that indicates whether the element has finished initialization.
+ ///
+ ///
+ /// For more information about when IsInitialized is set, see the
+ /// event.
+ ///
+ public bool IsInitialized { get; private set; }
+
+ ///
+ /// Gets the styles for the styled element.
+ ///
+ ///
+ /// 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.
+ ///
+ public Styles Styles
+ {
+ get { return _styles ?? (Styles = new Styles()); }
+ set
+ {
+ Contract.Requires(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;
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the styled element's resource dictionary.
+ ///
+ public IResourceDictionary Resources
+ {
+ get => _resources ?? (Resources = new ResourceDictionary());
+ set
+ {
+ Contract.Requires(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());
+ }
+ }
+ }
+
+ ///
+ /// Gets the styled element whose lookless template this styled element is part of.
+ ///
+ public ITemplatedControl TemplatedParent
+ {
+ get { return GetValue(TemplatedParentProperty); }
+ internal set { SetValue(TemplatedParentProperty, value); }
+ }
+
+ ///
+ /// Gets the styled element's logical children.
+ ///
+ protected IAvaloniaList LogicalChildren
+ {
+ get
+ {
+ if (_logicalChildren == null)
+ {
+ var list = new AvaloniaList
+ {
+ ResetBehavior = ResetBehavior.Remove,
+ Validate = ValidateLogicalChild
+ };
+ list.CollectionChanged += LogicalChildrenCollectionChanged;
+ _logicalChildren = list;
+ }
+
+ return _logicalChildren;
+ }
+ }
+
+ ///
+ /// Gets the collection in a form that allows adding and removing
+ /// pseudoclasses.
+ ///
+ protected IPseudoClasses PseudoClasses => Classes;
+
+ ///
+ /// Gets a value indicating whether the element is attached to a rooted logical tree.
+ ///
+ bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree;
+
+ ///
+ /// Gets the styled element's logical parent.
+ ///
+ public IStyledElement Parent { get; private set; }
+
+ ///
+ /// Gets the styled element's logical parent.
+ ///
+ ILogical ILogical.LogicalParent => Parent;
+
+ ///
+ /// Gets the styled element's logical children.
+ ///
+ IAvaloniaReadOnlyList ILogical.LogicalChildren => LogicalChildren;
+
+ ///
+ bool IResourceProvider.HasResources => _resources?.Count > 0 || Styles.HasResources;
+
+ ///
+ IResourceNode IResourceNode.ResourceParent => ((IStyleHost)this).StylingParent as IResourceNode;
+
+ ///
+ IAvaloniaReadOnlyList IStyleable.Classes => Classes;
+
+ ///
+ /// Gets the type by which the styled element is styled.
+ ///
+ ///
+ /// 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.
+ ///
+ Type IStyleable.StyleKey => GetType();
+
+ ///
+ IObservable IStyleable.StyleDetach => _styleDetach;
+
+ ///
+ bool IStyleHost.IsStylesInitialized => _styles != null;
+
+ ///
+ IStyleHost IStyleHost.StylingParent => (IStyleHost)InheritanceParent;
+
+ ///
+ public virtual void BeginInit()
+ {
+ ++_initCount;
+ }
+
+ ///
+ 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);
+ }
+ }
+
+ ///
+ void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ this.OnAttachedToLogicalTreeCore(e);
+ }
+
+ ///
+ void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ this.OnDetachedFromLogicalTreeCore(e);
+ }
+
+ ///
+ void ILogical.NotifyResourcesChanged(ResourcesChangedEventArgs e)
+ {
+ ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
+ }
+
+ ///
+ bool IResourceProvider.TryGetResource(string key, out object value)
+ {
+ value = null;
+ return (_resources?.TryGetResource(key, out value) ?? false) ||
+ (_styles?.TryGetResource(key, out value) ?? false);
+ }
+
+ ///
+ /// Sets the styled element's logical parent.
+ ///
+ /// The parent.
+ 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);
+ }
+ }
+
+ ///
+ /// Sets the styled element's inheritance parent.
+ ///
+ /// The parent.
+ void ISetInheritanceParent.SetParent(IAvaloniaObject parent)
+ {
+ InheritanceParent = parent;
+ }
+
+ ///
+ /// Adds a pseudo-class to be set when a property is true.
+ ///
+ /// The property.
+ /// The pseudo-class.
+ protected static void PseudoClass(AvaloniaProperty property, string className)
+ {
+ PseudoClass(property, x => x, className);
+ }
+
+ ///
+ /// Adds a pseudo-class to be set when a property equals a certain value.
+ ///
+ /// The type of the property.
+ /// The property.
+ /// Returns a boolean value based on the property value.
+ /// The pseudo-class.
+ protected static void PseudoClass(
+ AvaloniaProperty property,
+ Func selector,
+ string className)
+ {
+ Contract.Requires(property != null);
+ Contract.Requires(selector != null);
+ Contract.Requires(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);
+ }
+ });
+ }
+
+ ///
+ /// Called when the styled element is added to a rooted logical tree.
+ ///
+ /// The event args.
+ protected virtual void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ }
+
+ ///
+ /// Called when the styled element is removed from a rooted logical tree.
+ ///
+ /// The event args.
+ protected virtual void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ }
+
+ ///
+ /// Called when the property changes.
+ ///
+ /// The event args.
+ protected virtual void OnDataContextChanged(EventArgs e)
+ {
+ DataContextChanged?.Invoke(this, EventArgs.Empty);
+ }
+
+ ///
+ /// Called when the begins updating.
+ ///
+ protected virtual void OnDataContextBeginUpdate()
+ {
+ }
+
+ ///
+ /// Called when the finishes updating.
+ ///
+ 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()?.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())
+ {
+ 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())
+ {
+ 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());
+ break;
+
+ case NotifyCollectionChangedAction.Remove:
+ ClearLogicalParent(e.OldItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Replace:
+ ClearLogicalParent(e.OldItems.Cast());
+ SetLogicalParent(e.NewItems.Cast());
+ break;
+
+ case NotifyCollectionChangedAction.Reset:
+ throw new NotSupportedException("Reset should not be signalled on LogicalChildren collection");
+ }
+ }
+
+ private void SetLogicalParent(IEnumerable children)
+ {
+ foreach (var i in children)
+ {
+ if (i.LogicalParent == null)
+ {
+ ((ISetLogicalParent)i).SetParent(this);
+ }
+ }
+ }
+
+ private void ClearLogicalParent(IEnumerable 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);
+ }
+ }
+}
diff --git a/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs b/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs
new file mode 100644
index 0000000000..819d783061
--- /dev/null
+++ b/src/Avalonia.Styling/Styling/IRequiresTemplateInStyle.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Avalonia.Styling
+{
+ ///
+ /// 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.
+ ///
+ public interface IRequiresTemplateInStyle
+ {
+ }
+}
diff --git a/src/Avalonia.Styling/Styling/IStyleable.cs b/src/Avalonia.Styling/Styling/IStyleable.cs
index e03231e4ac..98c8501681 100644
--- a/src/Avalonia.Styling/Styling/IStyleable.cs
+++ b/src/Avalonia.Styling/Styling/IStyleable.cs
@@ -3,6 +3,7 @@
using System;
using Avalonia.Collections;
+using Avalonia.LogicalTree;
namespace Avalonia.Styling
{
diff --git a/src/Avalonia.Styling/Styling/Setter.cs b/src/Avalonia.Styling/Styling/Setter.cs
index e4d0ce5fab..337e01c0b5 100644
--- a/src/Avalonia.Styling/Styling/Setter.cs
+++ b/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 .",
@@ -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;
}
diff --git a/src/Avalonia.Styling/app.config b/src/Avalonia.Styling/app.config
deleted file mode 100644
index 517d060521..0000000000
--- a/src/Avalonia.Styling/app.config
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Avalonia.Visuals.csproj b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
index 557cfdbf37..0f003a4018 100644
--- a/src/Avalonia.Visuals/Avalonia.Visuals.csproj
+++ b/src/Avalonia.Visuals/Avalonia.Visuals.csproj
@@ -5,6 +5,7 @@
-
+
+
\ No newline at end of file
diff --git a/src/Avalonia.Visuals/Visual.cs b/src/Avalonia.Visuals/Visual.cs
index 5f3861a51a..2be39d4522 100644
--- a/src/Avalonia.Visuals/Visual.cs
+++ b/src/Avalonia.Visuals/Visual.cs
@@ -24,7 +24,7 @@ namespace Avalonia
/// to render the control. To traverse the visual tree, use the
/// extension methods defined in .
///
- public class Visual : Animatable, IVisual
+ public class Visual : StyledElement, IVisual
{
///
/// Defines the property.
diff --git a/src/Avalonia.Visuals/VisualTree/VisualLocator.cs b/src/Avalonia.Visuals/VisualTree/VisualLocator.cs
new file mode 100644
index 0000000000..6fcb6c0db1
--- /dev/null
+++ b/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 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 TrackAttachmentToTree(IVisual relativeTo)
+ {
+ var attached = Observable.FromEventPattern(
+ x => relativeTo.AttachedToVisualTree += x,
+ x => relativeTo.AttachedToVisualTree -= x)
+ .Select(x => true)
+ .StartWith(relativeTo.IsAttachedToVisualTree);
+
+ var detached = Observable.FromEventPattern(
+ x => relativeTo.DetachedFromVisualTree += x,
+ x => relativeTo.DetachedFromVisualTree -= x)
+ .Select(x => false);
+
+ var attachmentStatus = attached.Merge(detached);
+ return attachmentStatus;
+ }
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
index dd1d697224..af4916097c 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/StaticResourceExtension.cs
+++ b/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);
}
diff --git a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
index 02e49a4d73..32045f446c 100644
--- a/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
+++ b/src/Markup/Avalonia.Markup/Avalonia.Markup.csproj
@@ -4,8 +4,6 @@
-
-
diff --git a/src/Markup/Avalonia.Markup/Data/Binding.cs b/src/Markup/Avalonia.Markup/Data/Binding.cs
index bf44912bba..31131ecfa0 100644
--- a/src/Markup/Avalonia.Markup/Data/Binding.cs
+++ b/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(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(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; }
- }
}
}
diff --git a/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs b/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs
index df16c02dab..fb126622c1 100644
--- a/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs
+++ b/src/Markup/Avalonia.Markup/Data/DelayedBinding.cs
@@ -24,8 +24,8 @@ namespace Avalonia.Markup.Data
///
public static class DelayedBinding
{
- private static ConditionalWeakTable> _entries =
- new ConditionalWeakTable>();
+ private static ConditionalWeakTable> _entries =
+ new ConditionalWeakTable>();
///
/// Adds a delayed binding to a control.
@@ -33,7 +33,7 @@ namespace Avalonia.Markup.Data
/// The control.
/// The property on the control to bind to.
/// The binding.
- 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 bindings;
- if (!_entries.TryGetValue(target, out bindings))
+ if (!_entries.TryGetValue(target, out List bindings))
{
bindings = new List();
_entries.Add(target, bindings);
@@ -62,7 +61,7 @@ namespace Avalonia.Markup.Data
/// The control.
/// The property on the control to bind to.
/// A function which returns the value.
- public static void Add(IControl target, PropertyInfo property, Func value)
+ public static void Add(IStyledElement target, PropertyInfo property, Func value)
{
if (target.IsInitialized)
{
@@ -89,7 +88,7 @@ namespace Avalonia.Markup.Data
/// Applies any delayed bindings to a control.
///
/// The control.
- public static void ApplyBindings(IControl control)
+ public static void ApplyBindings(IStyledElement control)
{
List 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 value)
+ public ClrPropertyValueEntry(PropertyInfo property, Func value)
{
Property = property;
Value = value;
}
public PropertyInfo Property { get; }
- public Func Value { get; }
+ public Func Value { get; }
- public override void Apply(IControl control)
+ public override void Apply(IStyledElement control)
{
try
{
diff --git a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs b/src/Markup/Avalonia.Markup/Data/RelativeSource.cs
index ffddb7ee03..9a3b770212 100644
--- a/src/Markup/Avalonia.Markup/Data/RelativeSource.cs
+++ b/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,
}
+
+ ///
+ /// The type of tree via which to track a control.
+ ///
+ public enum TreeType
+ {
+ ///
+ /// The visual tree.
+ ///
+ Visual,
+ ///
+ /// The logical tree.
+ ///
+ Logical,
+ }
+
///
/// Describes the the location of a binding source, relative to the binding target.
///
diff --git a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs b/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
index 50610cf595..4da803353e 100644
--- a/tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
+++ b/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],
};
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
index a50fe852c3..c5ff64b0e3 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/CarouselPresenterTests.cs
+++ b/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>(target.ItemContainerGenerator);
diff --git a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs b/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
index 048cdb2ccb..3d13e4c32f 100644
--- a/tests/Avalonia.Controls.UnitTests/Presenters/ItemsPresenterTests.cs
+++ b/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>(target.ItemContainerGenerator);
diff --git a/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs b/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs
index 983b7f7c70..df8a0f723a 100644
--- a/tests/Avalonia.Controls.UnitTests/Templates/TemplateExtensionsTests.cs
+++ b/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;
diff --git a/tests/Avalonia.Controls.UnitTests/Utils/AncestorFinderTests.cs b/tests/Avalonia.Controls.UnitTests/Utils/AncestorFinderTests.cs
index 2f05e11dfc..91a6e2948c 100644
--- a/tests/Avalonia.Controls.UnitTests/Utils/AncestorFinderTests.cs
+++ b/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);
diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
index 6c95a465b3..82b57224f0 100644
--- a/tests/Avalonia.Markup.UnitTests/Data/BindingTests.cs
+++ b/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;
diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
similarity index 97%
rename from tests/Avalonia.Controls.UnitTests/ControlTests.cs
rename to tests/Avalonia.Styling.UnitTests/StyledElementTests.cs
index 313c0b1253..4096dcf380 100644
--- a/tests/Avalonia.Controls.UnitTests/ControlTests.cs
+++ b/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();
- 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";
diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs
similarity index 94%
rename from tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
rename to tests/Avalonia.Styling.UnitTests/StyledElementTests_NameScope.cs
index 9f39f7a47a..47c540f44a 100644
--- a/tests/Avalonia.Controls.UnitTests/ControlTests_NameScope.cs
+++ b/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]
diff --git a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs b/tests/Avalonia.Styling.UnitTests/StyledElementTests_Resources.cs
similarity index 95%
rename from tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs
rename to tests/Avalonia.Styling.UnitTests/StyledElementTests_Resources.cs
index 9ee6e3e456..c321adb394 100644
--- a/tests/Avalonia.Controls.UnitTests/ControlTests_Resources.cs
+++ b/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;