Browse Source

Merge branch 'master' into devtools-nav-properties

pull/8445/head
Max Katz 4 years ago
committed by GitHub
parent
commit
cc8f9754a0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
  2. 2
      native/Avalonia.Native/src/OSX/WindowImpl.mm
  3. 4
      src/Avalonia.Base/ValueStore.cs
  4. 1
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  5. 17
      src/Avalonia.Controls/Primitives/Popup.cs
  6. 10
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  7. 5
      src/Avalonia.Controls/ToolTip.cs
  8. 30
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs
  9. 105
      tests/Avalonia.LeakTests/ControlTests.cs
  10. 127
      tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs

1
native/Avalonia.Native/src/OSX/WindowBaseImpl.mm

@ -48,7 +48,6 @@ WindowBaseImpl::WindowBaseImpl(IAvnWindowBaseEvents *events, IAvnGlContext *gl,
[Window setContentMaxSize:lastMaxSize];
[Window setOpaque:false];
[Window setHasShadow:true];
}
HRESULT WindowBaseImpl::ObtainNSViewHandle(void **ret) {

2
native/Avalonia.Native/src/OSX/WindowImpl.mm

@ -24,6 +24,8 @@ WindowImpl::WindowImpl(IAvnWindowEvents *events, IAvnGlContext *gl) : WindowBase
_lastTitle = @"";
_parent = nullptr;
WindowEvents = events;
[Window setHasShadow:true];
OnInitialiseNSWindow();
}

4
src/Avalonia.Base/ValueStore.cs

@ -462,10 +462,6 @@ namespace Avalonia
values.Remove(entry.property);
}
}
else
{
throw new AvaloniaInternalException("Value could not be found at the end of batch update.");
}
// If a new batch update was started while ending this one, abort.
if (_batchUpdateCount > 0)

1
src/Avalonia.Controls/Flyouts/FlyoutBase.cs

@ -223,6 +223,7 @@ namespace Avalonia.Controls.Primitives
{
Popup.PlacementTarget = Target = placementTarget;
((ISetLogicalParent)Popup).SetParent(placementTarget);
Popup.SetValue(StyledElement.TemplatedParentProperty, placementTarget.TemplatedParent);
}
if (Popup.Child == null)

17
src/Avalonia.Controls/Primitives/Popup.cs

@ -860,22 +860,7 @@ namespace Avalonia.Controls.Primitives
{
if (control != null)
{
var templatedParent = TemplatedParent;
if (control.TemplatedParent == null)
{
control.SetValue(TemplatedParentProperty, templatedParent);
}
control.ApplyTemplate();
if (!(control is IPresenter) && control.TemplatedParent == templatedParent)
{
foreach (IControl child in control.VisualChildren)
{
SetTemplatedParentAndApplyChildTemplates(child);
}
}
TemplatedControl.ApplyTemplatedParent(control, TemplatedParent);
}
}

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

@ -285,7 +285,7 @@ namespace Avalonia.Controls.Primitives
Logger.TryGet(LogEventLevel.Verbose, LogArea.Control)?.Log(this, "Creating control template");
var (child, nameScope) = template.Build(this);
ApplyTemplatedParent(child);
ApplyTemplatedParent(child, this);
((ISetLogicalParent)child).SetParent(this);
VisualChildren.Add(child);
@ -387,18 +387,18 @@ namespace Avalonia.Controls.Primitives
/// Sets the TemplatedParent property for the created template children.
/// </summary>
/// <param name="control">The control.</param>
private void ApplyTemplatedParent(IControl control)
internal static void ApplyTemplatedParent(IStyledElement control, ITemplatedControl? templatedParent)
{
control.SetValue(TemplatedParentProperty, this);
control.SetValue(TemplatedParentProperty, templatedParent);
var children = control.LogicalChildren;
var count = children.Count;
for (var i = 0; i < count; i++)
{
if (children[i] is IControl child)
if (children[i] is IStyledElement child)
{
ApplyTemplatedParent(child);
ApplyTemplatedParent(child, templatedParent);
}
}
}

5
src/Avalonia.Controls/ToolTip.cs

@ -271,8 +271,9 @@ namespace Avalonia.Controls
_popupHost = OverlayPopupHost.CreatePopupHost(control, null);
_popupHost.SetChild(this);
((ISetLogicalParent)_popupHost).SetParent(control);
_popupHost.ConfigurePosition(control, GetPlacement(control),
ApplyTemplatedParent(this, control.TemplatedParent);
_popupHost.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
WindowManagerAddShadowHintChanged(_popupHost, false);

30
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs

@ -611,6 +611,36 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("foo", notifications[1].NewValue);
}
[Fact]
public void Can_Run_Empty_Batch_Update_When_Ending_Batch_Update()
{
var target = new TestClass();
var raised = 0;
var notifications = new List<AvaloniaPropertyChangedEventArgs>();
target.Foo = "foo";
target.Bar = "bar";
target.BeginBatchUpdate();
target.ClearValue(TestClass.FooProperty);
target.ClearValue(TestClass.BarProperty);
target.PropertyChanged += (sender, e) =>
{
if (e.Property == TestClass.BarProperty)
{
target.BeginBatchUpdate();
target.EndBatchUpdate();
}
++raised;
};
target.EndBatchUpdate();
Assert.Null(target.Foo);
Assert.Null(target.Bar);
Assert.Equal(2, raised);
}
public class TestClass : AvaloniaObject
{
public static readonly StyledProperty<string> FooProperty =

105
tests/Avalonia.LeakTests/ControlTests.cs

@ -7,6 +7,7 @@ using System.Reactive.Disposables;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates;
using Avalonia.Data;
@ -877,6 +878,110 @@ namespace Avalonia.LeakTests
}
}
[Fact]
public void ToolTip_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window();
var source = new Button
{
Template = new FuncControlTemplate<Button>((parent, _) =>
new Decorator
{
[ToolTip.TipProperty] = new TextBlock
{
[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
}
}),
};
window.Content = source;
window.Show();
var templateChild = (Decorator)source.GetVisualChildren().Single();
ToolTip.SetIsOpen(templateChild, true);
ToolTip.SetIsOpen(templateChild, false);
// Detach the button from the logical tree, so there is no reference to it
window.Content = null;
// Mock keep reference on a Popup via InvocationsCollection. So let's clear it before.
Mock.Get(window.PlatformImpl).Invocations.Clear();
return window;
};
var result = run();
// Process all Loaded events to free control reference(s)
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
dotMemory.Check(memory =>
{
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBlock>()).ObjectsCount);
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ToolTip>()).ObjectsCount);
});
}
}
[Fact]
public void Flyout_Is_Freed()
{
using (Start())
{
Func<Window> run = () =>
{
var window = new Window();
var source = new Button
{
Template = new FuncControlTemplate<Button>((parent, _) =>
new Button
{
Flyout = new Flyout
{
Content = new TextBlock
{
[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
}
}
}),
};
window.Content = source;
window.Show();
var templateChild = (Button)source.GetVisualChildren().Single();
templateChild.Flyout!.ShowAt(templateChild);
templateChild.Flyout!.Hide();
// Detach the button from the logical tree, so there is no reference to it
window.Content = null;
// Mock keep reference on a Popup via InvocationsCollection. So let's clear it before.
Mock.Get(window.PlatformImpl).Invocations.Clear();
return window;
};
var result = run();
// Process all Loaded events to free control reference(s)
Dispatcher.UIThread.RunJobs(DispatcherPriority.Loaded);
dotMemory.Check(memory =>
{
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<TextBlock>()).ObjectsCount);
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Flyout>()).ObjectsCount);
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<Popup>()).ObjectsCount);
});
}
}
private FuncControlTemplate CreateWindowTemplate()
{
return new FuncControlTemplate<Window>((parent, scope) =>

127
tests/Avalonia.Markup.UnitTests/Data/TemplateBindingTests.cs

@ -3,9 +3,11 @@ using System.Globalization;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Converters;
using Avalonia.UnitTests;
using Avalonia.VisualTree;
using Xunit;
@ -90,6 +92,131 @@ namespace Avalonia.Markup.UnitTests.Data
Assert.Equal("bar", source.Content);
}
[Fact]
public void Should_Work_Inside_Of_Tooltip()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var source = new Button
{
Template = new FuncControlTemplate<Button>((parent, _) =>
new Decorator
{
[ToolTip.TipProperty] = new TextBlock
{
[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
}
}),
};
window.Content = source;
window.Show();
try
{
var templateChild = (Decorator)source.GetVisualChildren().Single();
ToolTip.SetIsOpen(templateChild, true);
var target = (TextBlock)ToolTip.GetTip(templateChild)!;
Assert.Null(target.Text);
source.Content = "foo";
Assert.Equal("foo", target.Text);
source.Content = "bar";
Assert.Equal("bar", target.Text);
}
finally
{
window.Close();
}
}
}
[Fact]
public void Should_Work_Inside_Of_Popup()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var source = new Button
{
Template = new FuncControlTemplate<Button>((parent, _) =>
new Popup
{
Child = new TextBlock
{
[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
}
}),
};
window.Content = source;
window.Show();
try
{
var popup = (Popup)source.GetVisualChildren().Single();
popup.IsOpen = true;
var target = (TextBlock)popup.Child!;
target[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty);
Assert.Null(target.Text);
source.Content = "foo";
Assert.Equal("foo", target.Text);
source.Content = "bar";
Assert.Equal("bar", target.Text);
}
finally
{
window.Close();
}
}
}
[Fact]
public void Should_Work_Inside_Of_Flyout()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var window = new Window();
var source = new Button
{
Template = new FuncControlTemplate<Button>((parent, _) =>
new Button
{
Flyout = new Flyout
{
Content = new TextBlock
{
[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty)
}
}
}),
};
window.Content = source;
window.Show();
try
{
var templateChild = (Button)source.GetVisualChildren().Single();
templateChild.Flyout!.ShowAt(templateChild);
var target = (TextBlock)((Flyout)templateChild.Flyout).Content!;
target[~TextBlock.TextProperty] = new TemplateBinding(ContentControl.ContentProperty);
Assert.Null(target.Text);
source.Content = "foo";
Assert.Equal("foo", target.Text);
source.Content = "bar";
Assert.Equal("bar", target.Text);
}
finally
{
window.Close();
}
}
}
private class PrefixConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)

Loading…
Cancel
Save