Browse Source

Merge pull request #1031 from AvaloniaUI/fixes/1029-fix-tooltip-crash

Fix exception in ToolTip.
pull/1042/head
Steven Kirk 9 years ago
committed by GitHub
parent
commit
2f301b2bcd
  1. 11
      src/Avalonia.Controls/Control.cs
  2. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  3. 40
      src/Avalonia.Controls/ToolTip.cs
  4. 10
      src/Avalonia.Styling/LogicalTree/ILogical.cs
  5. 109
      tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs
  6. 28
      tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs
  7. 19
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  8. 5
      tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs
  9. 5
      tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

11
src/Avalonia.Controls/Control.cs

@ -118,6 +118,7 @@ namespace Avalonia.Controls
public Control()
{
_nameScope = this as INameScope;
_isAttachedToLogicalTree = this is IStyleRoot;
}
/// <summary>
@ -369,6 +370,12 @@ namespace Avalonia.Controls
}
}
/// <inheritdoc/>
void ILogical.NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
this.OnAttachedToLogicalTreeCore(e);
}
/// <inheritdoc/>
void ILogical.NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
@ -418,7 +425,7 @@ namespace Avalonia.Controls
if (_isAttachedToLogicalTree)
{
var oldRoot = FindStyleRoot(old);
var oldRoot = FindStyleRoot(old) ?? this as IStyleRoot;
if (oldRoot == null)
{
@ -436,7 +443,7 @@ namespace Avalonia.Controls
_parent = (IControl)parent;
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true)
if (_parent is IStyleRoot || _parent?.IsAttachedToLogicalTree == true || this is IStyleRoot)
{
var newRoot = FindStyleRoot(this);

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -285,6 +285,17 @@ namespace Avalonia.Controls.Primitives
return this;
}
/// <inheritdoc/>
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (VisualChildren.Count > 0)
{
((ILogical)VisualChildren[0]).NotifyAttachedToLogicalTree(e);
}
base.OnAttachedToLogicalTree(e);
}
/// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{

40
src/Avalonia.Controls/ToolTip.cs

@ -105,16 +105,21 @@ namespace Avalonia.Controls
{
if (control != null && control.IsVisible && control.GetVisualRoot() != null)
{
if (s_popup != null)
{
throw new AvaloniaInternalException("Previous ToolTip not disposed.");
}
var cp = (control.GetVisualRoot() as IInputRoot)?.MouseDevice?.GetPosition(control);
var position = control.PointToScreen(cp ?? new Point(0, 0)) + new Vector(0, 22);
s_popup = new PopupRoot();
if (s_popup == null)
{
s_popup = new PopupRoot();
s_popup.Content = new ToolTip();
}
else
{
((ISetLogicalParent)s_popup).SetParent(null);
}
((ISetLogicalParent)s_popup).SetParent(control);
s_popup.Content = new ToolTip { Content = GetTip(control) };
((ToolTip)s_popup.Content).Content = GetTip(control);
s_popup.Position = position;
s_popup.Show();
@ -146,16 +151,23 @@ namespace Avalonia.Controls
{
if (s_popup != null)
{
// Clear the ToolTip's Content in case it has control content: this will
// reset its visual parent allowing it to be used again.
((ToolTip)s_popup.Content).Content = null;
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
DisposeTooltip();
s_show.OnNext(null);
}
}
}
private static void DisposeTooltip()
{
if (s_popup != null)
{
// Clear the ToolTip's Content in case it has control content: this will
// reset its visual parent allowing it to be used again.
((ToolTip)s_popup.Content).Content = null;
s_show.OnNext(null);
// Dispose of the popup.
s_popup.Dispose();
s_popup = null;
}
}
}

10
src/Avalonia.Styling/LogicalTree/ILogical.cs

@ -36,6 +36,16 @@ namespace Avalonia.LogicalTree
/// </summary>
IAvaloniaReadOnlyList<ILogical> LogicalChildren { get; }
/// <summary>
/// Notifies the control that it is being attached to a rooted logical tree.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e);
/// <summary>
/// Notifies the control that it is being detached from a rooted logical tree.
/// </summary>

109
tests/Avalonia.Controls.UnitTests/Primitives/PopupRootTests.cs

@ -0,0 +1,109 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Controls.UnitTests.Primitives
{
public class PopupRootTests
{
[Fact]
public void PopupRoot_IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = CreateTarget();
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
}
}
[Fact]
public void Templated_Child_IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = CreateTarget();
Assert.True(target.Presenter.IsAttachedToLogicalTree);
}
}
[Fact]
public void Attaching_PopupRoot_To_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Decorator();
var target = CreateTarget();
var window = new Window();
var detachedCount = 0;
var attachedCount = 0;
target.Content = child;
target.DetachedFromLogicalTree += (s, e) => ++detachedCount;
child.DetachedFromLogicalTree += (s, e) => ++detachedCount;
target.AttachedToLogicalTree += (s, e) => ++attachedCount;
child.AttachedToLogicalTree += (s, e) => ++attachedCount;
((ISetLogicalParent)target).SetParent(window);
Assert.Equal(2, detachedCount);
Assert.Equal(2, attachedCount);
}
}
[Fact]
public void Detaching_PopupRoot_From_Parent_Logical_Tree_Raises_DetachedFromLogicalTree_And_AttachedToLogicalTree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var child = new Decorator();
var target = CreateTarget();
var window = new Window();
var detachedCount = 0;
var attachedCount = 0;
target.Content = child;
((ISetLogicalParent)target).SetParent(window);
target.DetachedFromLogicalTree += (s, e) => ++detachedCount;
child.DetachedFromLogicalTree += (s, e) => ++detachedCount;
target.AttachedToLogicalTree += (s, e) => ++attachedCount;
child.AttachedToLogicalTree += (s, e) => ++attachedCount;
((ISetLogicalParent)target).SetParent(null);
// Despite being detached from the parent logical tree, we're still attached to a
// logical tree as PopupRoot itself is a logical tree root.
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
Assert.True(((ILogical)child).IsAttachedToLogicalTree);
Assert.Equal(2, detachedCount);
Assert.Equal(2, attachedCount);
}
}
private PopupRoot CreateTarget()
{
var result = new PopupRoot
{
Template = new FuncControlTemplate<PopupRoot>(_ =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
}),
};
result.ApplyTemplate();
return result;
}
}
}

28
tests/Avalonia.Controls.UnitTests/Primitives/TemplatedControlTests.cs

@ -527,6 +527,34 @@ namespace Avalonia.Controls.UnitTests.Primitives
}
}
[Fact]
public void Moving_To_New_LogicalTree_Should_Detach_Attach_Template_Child()
{
using (UnitTestApplication.Start(TestServices.RealStyler))
{
TestTemplatedControl target;
var root = new TestRoot
{
Child = target = new TestTemplatedControl
{
Template = new FuncControlTemplate(_ => new Decorator()),
}
};
Assert.NotNull(target.Template);
target.ApplyTemplate();
var templateChild = (ILogical)target.GetVisualChildren().Single();
Assert.True(templateChild.IsAttachedToLogicalTree);
root.Child = null;
Assert.False(templateChild.IsAttachedToLogicalTree);
var newRoot = new TestRoot { Child = target };
Assert.True(templateChild.IsAttachedToLogicalTree);
}
}
private static IControl ScrollingContentControlTemplate(ContentControl control)
{
return new Border

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

@ -2,24 +2,33 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive;
using System.Reactive.Subjects;
using Moq;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
public class TopLevelTests
{
[Fact]
public void IsAttachedToLogicalTree_Is_True()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = new Mock<ITopLevelImpl>();
var target = new TestTopLevel(impl.Object);
Assert.True(((ILogical)target).IsAttachedToLogicalTree);
}
}
[Fact]
public void ClientSize_Should_Be_Set_On_Construction()
{

5
tests/Avalonia.Styling.UnitTests/SelectorTests_Child.cs

@ -145,6 +145,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();

5
tests/Avalonia.Styling.UnitTests/SelectorTests_Descendent.cs

@ -175,6 +175,11 @@ namespace Avalonia.Styling.UnitTests
throw new NotImplementedException();
}
public void NotifyAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();
}
public void NotifyDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
throw new NotImplementedException();

Loading…
Cancel
Save