Browse Source

Reafactor logical tree attach/detach events.

- When detaching set `Parent`  to `null` before raising the `DetachedFromLogicalTree` event
  - To do this, we need to store the logical root in the control so that e.g. a child control can be detached in a `DetachedFromLogicalTree` handler
- Add `Source` and `Parent` properties to `LogicalTreeAttachmentEventArgs` to give more context on what caused the attachment/detachment
pull/3342/head
Steven Kirk 6 years ago
parent
commit
a70da659f6
  1. 4
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  2. 2
      src/Avalonia.Controls/TopLevel.cs
  3. 32
      src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs
  4. 37
      src/Avalonia.Styling/StyledElement.cs
  5. 25
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  6. 63
      tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

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

@ -8,7 +8,7 @@ namespace Avalonia.Controls.Primitives
{ {
private const int AdornerZIndex = int.MaxValue - 100; private const int AdornerZIndex = int.MaxValue - 100;
private const int OverlayZIndex = int.MaxValue - 99; private const int OverlayZIndex = int.MaxValue - 99;
private IStyleHost _styleRoot; private IStyleRoot _styleRoot;
private readonly List<Control> _layers = new List<Control>(); private readonly List<Control> _layers = new List<Control>();
@ -53,7 +53,7 @@ namespace Avalonia.Controls.Primitives
layer.ZIndex = zindex; layer.ZIndex = zindex;
VisualChildren.Add(layer); VisualChildren.Add(layer);
if (((ILogical)this).IsAttachedToLogicalTree) if (((ILogical)this).IsAttachedToLogicalTree)
((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot)); ((ILogical)layer).NotifyAttachedToLogicalTree(new LogicalTreeAttachmentEventArgs(_styleRoot, layer, this));
InvalidateArrange(); InvalidateArrange();
} }

2
src/Avalonia.Controls/TopLevel.cs

@ -266,7 +266,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
protected virtual void HandleClosed() protected virtual void HandleClosed()
{ {
var logicalArgs = new LogicalTreeAttachmentEventArgs(this); var logicalArgs = new LogicalTreeAttachmentEventArgs(this, this, null);
((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs); ((ILogical)this).NotifyDetachedFromLogicalTree(logicalArgs);
var visualArgs = new VisualTreeAttachmentEventArgs(this, this); var visualArgs = new VisualTreeAttachmentEventArgs(this, this);

32
src/Avalonia.Styling/LogicalTree/LogicalTreeAttachmentEventArgs.cs

@ -16,16 +16,44 @@ namespace Avalonia.LogicalTree
/// Initializes a new instance of the <see cref="LogicalTreeAttachmentEventArgs"/> class. /// Initializes a new instance of the <see cref="LogicalTreeAttachmentEventArgs"/> class.
/// </summary> /// </summary>
/// <param name="root">The root of the logical tree.</param> /// <param name="root">The root of the logical tree.</param>
public LogicalTreeAttachmentEventArgs(IStyleHost root) /// <param name="source">The control being attached/detached.</param>
/// <param name="parent">The <see cref="Parent"/>.</param>
public LogicalTreeAttachmentEventArgs(
IStyleRoot root,
ILogical source,
ILogical parent)
{ {
Contract.Requires<ArgumentNullException>(root != null); Contract.Requires<ArgumentNullException>(root != null);
Contract.Requires<ArgumentNullException>(source != null);
Root = root; Root = root;
Source = source;
Parent = parent;
} }
/// <summary> /// <summary>
/// Gets the root of the logical tree that the control is being attached to or detached from. /// Gets the root of the logical tree that the control is being attached to or detached from.
/// </summary> /// </summary>
public IStyleHost Root { get; } public IStyleRoot Root { get; }
/// <summary>
/// Gets the control that was attached or detached from the logical tree.
/// </summary>
/// <remarks>
/// Logical tree attachment events travel down the attached logical tree from the point of
/// attachment/detachment, so this control may be different from the control that the
/// event is being raised on.
/// </remarks>
public ILogical Source { get; }
/// <summary>
/// Gets the control that <see cref="Source"/> is being attached to or detached from.
/// </summary>
/// <remarks>
/// For logical tree attachment, holds the new logical parent of <see cref="Source"/>. For
/// detachment, holds the old logical parent of <see cref="Source"/>. If the detachment event
/// was caused by a top-level control being closed, then this property will be null.
/// </remarks>
public ILogical Parent { get; }
} }
} }

37
src/Avalonia.Styling/StyledElement.cs

@ -59,7 +59,7 @@ namespace Avalonia
private int _initCount; private int _initCount;
private string _name; private string _name;
private readonly Classes _classes = new Classes(); private readonly Classes _classes = new Classes();
private bool _isAttachedToLogicalTree; private IStyleRoot _styleRoot;
private IAvaloniaList<ILogical> _logicalChildren; private IAvaloniaList<ILogical> _logicalChildren;
private IResourceDictionary _resources; private IResourceDictionary _resources;
private Styles _styles; private Styles _styles;
@ -81,7 +81,7 @@ namespace Avalonia
/// </summary> /// </summary>
public StyledElement() public StyledElement()
{ {
_isAttachedToLogicalTree = this is IStyleRoot; _styleRoot = this as IStyleRoot;
} }
/// <summary> /// <summary>
@ -307,7 +307,7 @@ namespace Avalonia
/// <summary> /// <summary>
/// Gets a value indicating whether the element is attached to a rooted logical tree. /// Gets a value indicating whether the element is attached to a rooted logical tree.
/// </summary> /// </summary>
bool ILogical.IsAttachedToLogicalTree => _isAttachedToLogicalTree; bool ILogical.IsAttachedToLogicalTree => _styleRoot != null;
/// <summary> /// <summary>
/// Gets the styled element's logical parent. /// Gets the styled element's logical parent.
@ -367,7 +367,7 @@ namespace Avalonia
throw new InvalidOperationException("BeginInit was not called."); throw new InvalidOperationException("BeginInit was not called.");
} }
if (--_initCount == 0 && _isAttachedToLogicalTree) if (--_initCount == 0 && _styleRoot != null)
{ {
InitializeStylesIfNeeded(); InitializeStylesIfNeeded();
@ -435,19 +435,6 @@ namespace Avalonia
throw new InvalidOperationException("The Control already has a parent."); 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) if (InheritanceParent == null || parent == null)
{ {
InheritanceParent = parent as AvaloniaObject; InheritanceParent = parent as AvaloniaObject;
@ -455,6 +442,12 @@ namespace Avalonia
Parent = (IStyledElement)parent; Parent = (IStyledElement)parent;
if (_styleRoot != null)
{
var e = new LogicalTreeAttachmentEventArgs(_styleRoot, this, old);
OnDetachedFromLogicalTreeCore(e);
}
if (old != null) if (old != null)
{ {
old.ResourcesChanged -= ThisResourcesChanged; old.ResourcesChanged -= ThisResourcesChanged;
@ -474,7 +467,7 @@ namespace Avalonia
throw new AvaloniaInternalException("Parent is attached to logical tree but cannot find root."); throw new AvaloniaInternalException("Parent is attached to logical tree but cannot find root.");
} }
var e = new LogicalTreeAttachmentEventArgs(newRoot); var e = new LogicalTreeAttachmentEventArgs(newRoot, this, parent);
OnAttachedToLogicalTreeCore(e); OnAttachedToLogicalTreeCore(e);
} }
@ -716,9 +709,9 @@ namespace Avalonia
// - ListBox makes ListBoxItem a logical child // - ListBox makes ListBoxItem a logical child
// - ListBox template gets applied; making its Panel get attached to logical tree // - ListBox template gets applied; making its Panel get attached to logical tree
// - That AttachedToLogicalTree signal travels down to the ListBoxItem // - That AttachedToLogicalTree signal travels down to the ListBoxItem
if (!_isAttachedToLogicalTree) if (_styleRoot == null)
{ {
_isAttachedToLogicalTree = true; _styleRoot = e.Root;
InitializeStylesIfNeeded(true); InitializeStylesIfNeeded(true);
@ -734,9 +727,9 @@ namespace Avalonia
private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e) private void OnDetachedFromLogicalTreeCore(LogicalTreeAttachmentEventArgs e)
{ {
if (_isAttachedToLogicalTree) if (_styleRoot != null)
{ {
_isAttachedToLogicalTree = false; _styleRoot = null;
_styleDetach.OnNext(this); _styleDetach.OnNext(this);
OnDetachedFromLogicalTree(e); OnDetachedFromLogicalTree(e);
DetachedFromLogicalTree?.Invoke(this, e); DetachedFromLogicalTree?.Invoke(this, e);

25
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -166,6 +166,31 @@ namespace Avalonia.Controls.UnitTests
} }
} }
[Fact]
public void Impl_Close_Should_Raise_DetachedFromLogicalTree_Event()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
impl.SetupAllProperties();
var target = new TestTopLevel(impl.Object);
var raised = 0;
target.DetachedFromLogicalTree += (s, e) =>
{
Assert.Same(target, e.Root);
Assert.Same(target, e.Source);
Assert.Null(e.Parent);
++raised;
};
impl.Object.Closed();
Assert.Equal(1, raised);
}
}
[Fact] [Fact]
public void Impl_Input_Should_Pass_Input_To_InputManager() public void Impl_Input_Should_Pass_Input_To_InputManager()
{ {

63
tests/Avalonia.Styling.UnitTests/StyledElementTests.cs

@ -167,6 +167,50 @@ namespace Avalonia.Styling.UnitTests
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void AttachedToLogicalParent_Should_Have_Source_Set()
{
var root = new TestRoot();
var canvas = new Canvas();
var border = new Border { Child = canvas };
var raised = 0;
void Attached(object sender, LogicalTreeAttachmentEventArgs e)
{
Assert.Same(border, e.Source);
++raised;
}
border.AttachedToLogicalTree += Attached;
canvas.AttachedToLogicalTree += Attached;
root.Child = border;
Assert.Equal(2, raised);
}
[Fact]
public void AttachedToLogicalParent_Should_Have_Parent_Set()
{
var root = new TestRoot();
var canvas = new Canvas();
var border = new Border { Child = canvas };
var raised = 0;
void Attached(object sender, LogicalTreeAttachmentEventArgs e)
{
Assert.Same(root, e.Parent);
++raised;
}
border.AttachedToLogicalTree += Attached;
canvas.AttachedToLogicalTree += Attached;
root.Child = border;
Assert.Equal(2, raised);
}
[Fact] [Fact]
public void DetachedFromLogicalParent_Should_Be_Called_When_Removed_From_Tree() public void DetachedFromLogicalParent_Should_Be_Called_When_Removed_From_Tree()
{ {
@ -213,6 +257,25 @@ namespace Avalonia.Styling.UnitTests
Assert.True(raised); Assert.True(raised);
} }
[Fact]
public void Parent_Should_Be_Null_When_DetachedFromLogicalParent_Called()
{
var target = new TestControl();
var root = new TestRoot(target);
var called = 0;
target.DetachedFromLogicalTree += (s, e) =>
{
Assert.Null(target.Parent);
Assert.Null(target.InheritanceParent);
++called;
};
root.Child = null;
Assert.Equal(1, called);
}
[Fact] [Fact]
public void Adding_Tree_To_IStyleRoot_Should_Style_Controls() public void Adding_Tree_To_IStyleRoot_Should_Style_Controls()
{ {

Loading…
Cancel
Save