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

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

2
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());
}

754
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);
}
}
}

6
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();
}

30
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; }
}
}

24
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();
});
});
}
}
}

16
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
src/Avalonia.Controls/IPseudoClasses.cs → src/Avalonia.Styling/Controls/IPseudoClasses.cs

2
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.

2
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.

36
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>

3
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
src/Avalonia.Visuals/INamed.cs → src/Avalonia.Styling/INamed.cs

42
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; }
}
}

53
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
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
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
src/Avalonia.Styling/Styling/IStyleable.cs

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

4
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;
}

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>

3
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>

2
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
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;
}
}
}

2
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);
}

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>

41
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; }
}
}
}

25
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
{

17
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>

4
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],
};

2
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);

2
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);

20
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;

2
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);

2
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;

17
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";

4
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]

12
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;
Loading…
Cancel
Save