Browse Source

Merge branch 'master' into file-picker

pull/8303/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
8fea718182
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. 1
      samples/ControlCatalog.Web/App.razor.cs
  4. 4
      src/Avalonia.Base/ValueStore.cs
  5. 1
      src/Avalonia.Controls/Flyouts/FlyoutBase.cs
  6. 17
      src/Avalonia.Controls/Primitives/Popup.cs
  7. 10
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  8. 5
      src/Avalonia.Controls/ToolTip.cs
  9. 108
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  10. 6
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml
  11. 2
      src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml.cs
  12. 57
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  13. 87
      src/Web/Avalonia.Web.Blazor/BlazorSkiaRasterSurface.cs
  14. 2
      src/Web/Avalonia.Web.Blazor/BlazorSkiaSurface.cs
  15. 9
      src/Web/Avalonia.Web.Blazor/IBlazorSkiaSurface.cs
  16. 7
      src/Web/Avalonia.Web.Blazor/RazorViewTopLevelImpl.cs
  17. 30
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_BatchUpdate.cs
  18. 105
      tests/Avalonia.LeakTests/ControlTests.cs
  19. 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();
}

1
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();

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

108
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<PropertyInfo> 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)

6
src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml

@ -30,7 +30,11 @@
<Grid Grid.Column="0" RowDefinitions="Auto,Auto,*">
<Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto">
<Button Grid.Column="0" Grid.RowSpan="2" Content="^" Command="{Binding ApplyParentProperty}" />
<Button Grid.Column="0" Grid.RowSpan="2" Content="^"
IsEnabled="{Binding CanNavigateToParentProperty}"
Margin="0 0 4 0"
ToolTip.Tip="Navigate to parent property"
Command="{Binding NavigateToParentProperty}" />
<TextBlock Grid.Column="1" Grid.Row="0" Text="{Binding SelectedEntityName}" FontWeight="Bold" />
<TextBlock Grid.Column="1" Grid.Row="1" Text="{Binding SelectedEntityType}" FontStyle="Italic" />
</Grid>

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

57
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<SkiaOptions>();
_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<SkiaOptions>();
// 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;

87
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<IntPtr, SKSizeI> _blitCallback;
private readonly Action _onDisposeAction;
public BlazorSkiaRasterSurface(
SKColorType colorType, PixelSize size, double scaling, Action<IntPtr, SKSizeI> 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);
}
}
}
}

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

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

7
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<IntPtr, SKSizeI> blitCallback)
{
_currentSurface = new BlazorSkiaRasterSurface(colorType, size, scaling, blitCallback);
}
public void SetClientSize(SKSize size, double dpi)
{
var newSize = new Size(size.Width, size.Height);

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