diff --git a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm b/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
index 45d78b3926..77f53332cd 100644
--- a/native/Avalonia.Native/src/OSX/WindowBaseImpl.mm
+++ b/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) {
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index 85a89955f4..95f61422cb 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/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();
}
diff --git a/samples/ControlCatalog.Web/App.razor.cs b/samples/ControlCatalog.Web/App.razor.cs
index c0b7ddbe1e..560e8079a6 100644
--- a/samples/ControlCatalog.Web/App.razor.cs
+++ b/samples/ControlCatalog.Web/App.razor.cs
@@ -11,6 +11,7 @@ public partial class App
{
ControlCatalog.Pages.EmbedSample.Implementation = new EmbedSampleWeb();
})
+ //.With(new SkiaOptions { CustomGpuFactory = null }) // uncomment to disable GPU/GL rendering
.SetupWithSingleViewLifetime();
base.OnParametersSet();
diff --git a/src/Avalonia.Base/ValueStore.cs b/src/Avalonia.Base/ValueStore.cs
index 69c644dff9..bf29e0b0ac 100644
--- a/src/Avalonia.Base/ValueStore.cs
+++ b/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)
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 4801fa69f0..1504d2b25f 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/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)
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 95e5e25c42..1501d97470 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/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);
}
}
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index db029d38c0..4403bfce51 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/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.
///
/// The control.
- 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);
}
}
}
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index 91c93c87c8..bb18bf4c64 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/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);
diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
index e383c160e3..f8e2e0544f 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
@@ -113,6 +113,8 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
+ public bool CanNavigateToParentProperty => _selectedEntitiesStack.Count >= 1;
+
private (object resourceKey, bool isDynamic)? GetResourceInfo(object? value)
{
if (value is StaticResourceExtension staticResource)
@@ -415,7 +417,14 @@ namespace Avalonia.Diagnostics.ViewModels
}
}
- public void ApplySelectedProperty()
+ private static IEnumerable GetAllPublicProperties(Type type)
+ {
+ return type
+ .GetProperties()
+ .Concat(type.GetInterfaces().SelectMany(i => i.GetProperties()));
+ }
+
+ public void NavigateToSelectedProperty()
{
var selectedProperty = SelectedProperty;
var selectedEntity = SelectedEntity;
@@ -423,72 +432,103 @@ namespace Avalonia.Diagnostics.ViewModels
if (selectedEntity == null
|| selectedProperty == null
|| selectedProperty.PropertyType == typeof(string)
- || selectedProperty.PropertyType.IsValueType
- )
+ || selectedProperty.PropertyType.IsValueType)
return;
- object? property;
- if (selectedProperty.Key is AvaloniaProperty avaloniaProperty)
+ object? property = null;
+
+ switch (selectedProperty)
{
- property = (_selectedEntity as IControl)?.GetValue(avaloniaProperty);
+ case AvaloniaPropertyViewModel avaloniaProperty:
+
+ property = (_selectedEntity as IControl)?.GetValue(avaloniaProperty.Property);
+
+ break;
+
+ case ClrPropertyViewModel clrProperty:
+ {
+ property = GetAllPublicProperties(selectedEntity.GetType())
+ .FirstOrDefault(pi => clrProperty.Property == pi)?
+ .GetValue(selectedEntity);
+
+ break;
+ }
}
- else
+
+ if (property == null)
+ return;
+
+ _selectedEntitiesStack.Push((Name:selectedEntityName!, Entry:selectedEntity));
+
+ var propertyName = selectedProperty.Name;
+
+ //Strip out interface names
+ if (propertyName.LastIndexOf('.') is var p && p != -1)
{
- property = selectedEntity.GetType().GetProperties()
- .FirstOrDefault(pi => pi.Name == selectedProperty.Name
- && pi.DeclaringType == selectedProperty.DeclaringType
- && pi.PropertyType.Name == selectedProperty.PropertyType.Name)
- ?.GetValue(selectedEntity);
+ propertyName = propertyName.Substring(p + 1);
}
- if (property == null) return;
- _selectedEntitiesStack.Push((Name:selectedEntityName!,Entry:selectedEntity));
- NavigateToProperty(property, selectedProperty.Name);
+
+ NavigateToProperty(property, selectedEntityName + "." + propertyName);
+
+ RaisePropertyChanged(nameof(CanNavigateToParentProperty));
}
- public void ApplyParentProperty()
+ public void NavigateToParentProperty()
{
- if (_selectedEntitiesStack.Any())
+ if (_selectedEntitiesStack.Count > 0)
{
var property = _selectedEntitiesStack.Pop();
NavigateToProperty(property.Entry, property.Name);
+
+ RaisePropertyChanged(nameof(CanNavigateToParentProperty));
}
}
- protected void NavigateToProperty(object o, string? entityName)
+ protected void NavigateToProperty(object o, string? entityName)
{
var oldSelectedEntity = SelectedEntity;
- if (oldSelectedEntity is IAvaloniaObject ao1)
- {
- ao1.PropertyChanged -= ControlPropertyChanged;
- }
- else if (oldSelectedEntity is INotifyPropertyChanged inpc1)
+
+ switch (oldSelectedEntity)
{
- inpc1.PropertyChanged -= ControlPropertyChanged;
+ case IAvaloniaObject ao1:
+ ao1.PropertyChanged -= ControlPropertyChanged;
+ break;
+
+ case INotifyPropertyChanged inpc1:
+ inpc1.PropertyChanged -= ControlPropertyChanged;
+ break;
}
-
+
SelectedEntity = o;
SelectedEntityName = entityName;
SelectedEntityType = o.ToString();
+
var properties = GetAvaloniaProperties(o)
.Concat(GetClrProperties(o, _showImplementedInterfaces))
.OrderBy(x => x, PropertyComparer.Instance)
.ThenBy(x => x.Name)
.ToArray();
- _propertyIndex = properties.GroupBy(x => x.Key).ToDictionary(x => x.Key, x => x.ToArray());
+ _propertyIndex = properties
+ .GroupBy(x => x.Key)
+ .ToDictionary(x => x.Key, x => x.ToArray());
+
+ TreePage.PropertiesFilter.FilterString = string.Empty;
var view = new DataGridCollectionView(properties);
view.GroupDescriptions.Add(new DataGridPathGroupDescription(nameof(AvaloniaPropertyViewModel.Group)));
view.Filter = FilterProperty;
PropertiesView = view;
- if (o is IAvaloniaObject ao2)
+ switch (o)
{
- ao2.PropertyChanged += ControlPropertyChanged;
- }
- else if (o is INotifyPropertyChanged inpc2)
- {
- inpc2.PropertyChanged += ControlPropertyChanged;
+ case IAvaloniaObject ao2:
+ ao2.PropertyChanged += ControlPropertyChanged;
+ break;
+
+ case INotifyPropertyChanged inpc2:
+ inpc2.PropertyChanged += ControlPropertyChanged;
+ break;
}
}
@@ -498,7 +538,9 @@ namespace Avalonia.Diagnostics.ViewModels
if (SelectedEntity != _avaloniaObject)
{
- NavigateToProperty(_avaloniaObject, (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString());
+ NavigateToProperty(
+ _avaloniaObject,
+ (_avaloniaObject as IControl)?.Name ?? _avaloniaObject.ToString());
}
if (PropertiesView is null)
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
index cc392853be..a426b387f7 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
@@ -30,7 +30,11 @@
-
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
index 08ffe2c081..7e8ffa76cd 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
@@ -25,7 +25,7 @@ namespace Avalonia.Diagnostics.Views
{
if (sender is DataGrid grid && grid.DataContext is ControlDetailsViewModel controlDetails)
{
- controlDetails.ApplySelectedProperty();
+ controlDetails.NavigateToSelectedProperty();
}
}
diff --git a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
index dd5b34d869..79bfc256d3 100644
--- a/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
+++ b/src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
@@ -42,6 +42,7 @@ namespace Avalonia.Web.Blazor
private const SKColorType ColorType = SKColorType.Rgba8888;
private bool _initialised;
+ private bool _useGL;
private bool _inputElementFocused;
[Inject] private IJSRuntime Js { get; set; } = null!;
@@ -283,25 +284,44 @@ namespace Avalonia.Web.Blazor
_interop = await SKHtmlCanvasInterop.ImportAsync(Js, _htmlCanvas, OnRenderFrame);
Console.WriteLine("Interop created");
- _jsGlInfo = _interop.InitGL();
-
- Console.WriteLine("jsglinfo created - init gl");
+
+ var skiaOptions = AvaloniaLocator.Current.GetService();
+ _useGL = skiaOptions?.CustomGpuFactory != null;
- // create the SkiaSharp context
- if (_context == null)
+ if (_useGL)
+ {
+ _jsGlInfo = _interop.InitGL();
+ Console.WriteLine("jsglinfo created - init gl");
+ }
+ else
{
- Console.WriteLine("create glcontext");
- _glInterface = GRGlInterface.Create();
- _context = GRContext.CreateGl(_glInterface);
-
- var options = AvaloniaLocator.Current.GetService();
- // bump the default resource cache limit
- _context.SetResourceCacheLimit(options?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
- Console.WriteLine("glcontext created and resource limit set");
+ var rasterInitialized = _interop.InitRaster();
+ Console.WriteLine("raster initialized: {0}", rasterInitialized);
}
- _topLevelImpl.SetSurface(_context, _jsGlInfo, ColorType,
- new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
+ if (_useGL)
+ {
+ // create the SkiaSharp context
+ if (_context == null)
+ {
+ Console.WriteLine("create glcontext");
+ _glInterface = GRGlInterface.Create();
+ _context = GRContext.CreateGl(_glInterface);
+
+
+ // bump the default resource cache limit
+ _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
+ Console.WriteLine("glcontext created and resource limit set");
+ }
+
+ _topLevelImpl.SetSurface(_context, _jsGlInfo!, ColorType,
+ new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi);
+ }
+ else
+ {
+ _topLevelImpl.SetSurface(ColorType,
+ new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, _interop.PutImageData);
+ }
_interop.SetCanvasSize((int)(_canvasSize.Width * _dpi), (int)(_canvasSize.Height * _dpi));
@@ -323,7 +343,12 @@ namespace Avalonia.Web.Blazor
private void OnRenderFrame()
{
- if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0 || _jsGlInfo == null)
+ if (_useGL && (_jsGlInfo == null))
+ {
+ Console.WriteLine("nothing to render");
+ return;
+ }
+ if (_canvasSize.Width <= 0 || _canvasSize.Height <= 0 || _dpi <= 0)
{
Console.WriteLine("nothing to render");
return;
diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs
new file mode 100644
index 0000000000..603a792de3
--- /dev/null
+++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs
@@ -0,0 +1,87 @@
+using System.Runtime.InteropServices;
+using Avalonia.Controls.Platform.Surfaces;
+using Avalonia.Platform;
+using Avalonia.Skia;
+using SkiaSharp;
+
+namespace Avalonia.Web.Blazor
+{
+ internal class BlazorSkiaRasterSurface : IBlazorSkiaSurface, IFramebufferPlatformSurface, IDisposable
+ {
+ public SKColorType ColorType { get; set; }
+
+ public PixelSize Size { get; set; }
+
+ public double Scaling { get; set; }
+
+ private FramebufferData? _fbData;
+ private readonly Action _blitCallback;
+ private readonly Action _onDisposeAction;
+
+ public BlazorSkiaRasterSurface(
+ SKColorType colorType, PixelSize size, double scaling, Action blitCallback)
+ {
+ ColorType = colorType;
+ Size = size;
+ Scaling = scaling;
+ _blitCallback = blitCallback;
+ _onDisposeAction = Blit;
+ }
+
+ public void Dispose()
+ {
+ _fbData?.Dispose();
+ _fbData = null;
+ }
+
+ public ILockedFramebuffer Lock()
+ {
+ var bytesPerPixel = 4; // TODO: derive from ColorType
+ var dpi = Scaling * 96.0;
+ var width = (int)(Size.Width * Scaling);
+ var height = (int)(Size.Height * Scaling);
+
+ if (_fbData is null || _fbData?.Size.Width != width || _fbData?.Size.Height != height)
+ {
+ _fbData?.Dispose();
+ _fbData = new FramebufferData(width, height, bytesPerPixel);
+ }
+
+ var pixelFormat = ColorType.ToPixelFormat();
+ var data = _fbData.Value;
+ return new LockedFramebuffer(
+ data.Address, data.Size, data.RowBytes,
+ new Vector(dpi, dpi), pixelFormat, _onDisposeAction);
+ }
+
+ private void Blit()
+ {
+ if (_fbData != null)
+ {
+ var data = _fbData.Value;
+ _blitCallback(data.Address, new SKSizeI(data.Size.Width, data.Size.Height));
+ }
+ }
+
+ private readonly struct FramebufferData
+ {
+ public PixelSize Size { get; }
+
+ public int RowBytes { get; }
+
+ public IntPtr Address { get; }
+
+ public FramebufferData(int width, int height, int bytesPerPixel)
+ {
+ Size = new PixelSize(width, height);
+ RowBytes = width * bytesPerPixel;
+ Address = Marshal.AllocHGlobal(width * height * bytesPerPixel);
+ }
+
+ public void Dispose()
+ {
+ Marshal.FreeHGlobal(Address);
+ }
+ }
+ }
+}
diff --git a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
index 512309cfe3..fb49df338b 100644
--- a/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
+++ b/src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
@@ -3,7 +3,7 @@ using SkiaSharp;
namespace Avalonia.Web.Blazor
{
- internal class BlazorSkiaSurface
+ internal class BlazorSkiaSurface : IBlazorSkiaSurface
{
public BlazorSkiaSurface(GRContext context, SKHtmlCanvasInterop.GLInfo glInfo, SKColorType colorType, PixelSize size, double scaling, GRSurfaceOrigin origin)
{
diff --git a/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs b/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs
new file mode 100644
index 0000000000..5463893e27
--- /dev/null
+++ b/src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.Web.Blazor
+{
+ internal interface IBlazorSkiaSurface
+ {
+ public PixelSize Size { get; set; }
+
+ public double Scaling { get; set; }
+ }
+}
diff --git a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
index b8e4636b70..fb9ade7c07 100644
--- a/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Web.Blazor
internal class RazorViewTopLevelImpl : ITopLevelImplWithTextInputMethod, ITopLevelImplWithNativeControlHost, ITopLevelImplWithStorageProvider
{
private Size _clientSize;
- private BlazorSkiaSurface? _currentSurface;
+ private IBlazorSkiaSurface? _currentSurface;
private IInputRoot? _inputRoot;
private readonly Stopwatch _sw = Stopwatch.StartNew();
private readonly AvaloniaView _avaloniaView;
@@ -41,6 +41,11 @@ namespace Avalonia.Web.Blazor
new BlazorSkiaSurface(context, glInfo, colorType, size, scaling, GRSurfaceOrigin.BottomLeft);
}
+ internal void SetSurface(SKColorType colorType, PixelSize size, double scaling, Action blitCallback)
+ {
+ _currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback);
+ }
+
public void SetClientSize(SKSize size, double dpi)
{
var newSize = new Size(size.Width, size.Height);
diff --git a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs b/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs
index 01d5752ead..45de860894 100644
--- a/tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs
+++ b/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();
+
+ 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 FooProperty =
diff --git a/tests/Avalonia.LeakTests/ControlTests.cs b/tests/Avalonia.LeakTests/ControlTests.cs
index 8c05f2a0a7..6fb7b1448c 100644
--- a/tests/Avalonia.LeakTests/ControlTests.cs
+++ b/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 run = () =>
+ {
+ var window = new Window();
+ var source = new Button
+ {
+ Template = new FuncControlTemplate