Browse Source

Merge remote-tracking branch 'upstream/master' into fixes/imeIssues

pull/10508/head
Benedikt Stebner 4 years ago
parent
commit
338e8c4283
  1. 5
      packages/Avalonia/Avalonia.props
  2. 155
      src/Avalonia.Base/Platform/AssetLoader.cs
  3. 2
      src/Avalonia.Base/PropertyStore/ValueStore.cs
  4. 2
      src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
  5. 35
      src/Avalonia.Controls/Panel.cs
  6. 1
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  7. 2
      src/Windows/Avalonia.Win32/Input/KeyInterop.cs
  8. 13
      src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs
  9. 13
      tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
  10. 81
      tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs
  11. 15
      tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs
  12. 15
      tests/Avalonia.Controls.UnitTests/PanelTests.cs
  13. 2
      tests/Avalonia.Controls.UnitTests/TabControlTests.cs
  14. 2
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

5
packages/Avalonia/Avalonia.props

@ -6,4 +6,9 @@
<AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild> <AvaloniaUseExternalMSBuild>false</AvaloniaUseExternalMSBuild>
</PropertyGroup> </PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/> <Import Project="$(MSBuildThisFileDirectory)\AvaloniaBuildTasks.props"/>
<!-- Allow loading the AvaloniaVS extension when referencing the Avalonia nuget package -->
<ItemGroup>
<ProjectCapability Include="Avalonia"/>
</ItemGroup>
</Project> </Project>

155
src/Avalonia.Base/Platform/AssetLoader.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
@ -62,7 +63,7 @@ namespace Avalonia.Platform
/// <returns>True if the asset could be found; otherwise false.</returns> /// <returns>True if the asset could be found; otherwise false.</returns>
public bool Exists(Uri uri, Uri? baseUri = null) public bool Exists(Uri uri, Uri? baseUri = null)
{ {
return GetAsset(uri, baseUri) != null; return TryGetAsset(uri, baseUri, out _);
} }
/// <summary> /// <summary>
@ -94,21 +95,27 @@ namespace Avalonia.Platform
/// </exception> /// </exception>
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null) public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{ {
var asset = GetAsset(uri, baseUri); if (TryGetAsset(uri, baseUri, out var assetDescriptor))
if (asset == null)
{ {
throw new FileNotFoundException($"The resource {uri} could not be found."); return (assetDescriptor.GetStream(), assetDescriptor.Assembly);
} }
return (asset.GetStream(), asset.Assembly); throw new FileNotFoundException($"The resource {uri} could not be found.");
} }
public Assembly? GetAssembly(Uri uri, Uri? baseUri) public Assembly? GetAssembly(Uri uri, Uri? baseUri)
{ {
if (!uri.IsAbsoluteUri && baseUri != null) if (!uri.IsAbsoluteUri && baseUri != null)
{
uri = new Uri(baseUri, uri); uri = new Uri(baseUri, uri);
return GetAssembly(uri)?.Assembly; }
if (TryGetAssembly(uri, out var assemblyDescriptor))
{
return assemblyDescriptor.Assembly;
}
return null;
} }
/// <summary> /// <summary>
@ -121,99 +128,145 @@ namespace Avalonia.Platform
{ {
if (uri.IsAbsoluteResm()) if (uri.IsAbsoluteResm())
{ {
var assembly = GetAssembly(uri); if (!TryGetAssembly(uri, out var assembly))
{
assembly = _defaultResmAssembly;
}
return assembly?.Resources? return assembly?.Resources?
.Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0) .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
.Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ?? .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
Enumerable.Empty<Uri>(); Enumerable.Empty<Uri>();
} }
uri = uri.EnsureAbsolute(baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares()) if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
if (asm == null)
{ {
throw new ArgumentException( return Enumerable.Empty<Uri>();
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
} }
if (asm.AvaloniaResources == null) if (assembly?.AvaloniaResources == null)
{
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
}
if (path[path.Length - 1] != '/') if (path.Length > 0 && path[path.Length - 1] != '/')
{
path += '/'; path += '/';
}
return asm.AvaloniaResources return assembly.AvaloniaResources
.Where(r => r.Key.StartsWith(path, StringComparison.Ordinal)) .Where(r => r.Key.StartsWith(path, StringComparison.Ordinal))
.Select(x => new Uri($"avares://{asm.Name}{x.Key}")); .Select(x => new Uri($"avares://{assembly.Name}{x.Key}"));
} }
return Enumerable.Empty<Uri>(); return Enumerable.Empty<Uri>();
} }
private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri) private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
{ {
assetDescriptor = null;
if (uri.IsAbsoluteResm()) if (uri.IsAbsoluteResm())
{ {
var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly; if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
if (asm == null)
{ {
throw new ArgumentException( assembly = _defaultResmAssembly;
"No default assembly, entry assembly or explicit assembly specified; " +
"don't know where to look up for the resource, try specifying assembly explicitly.");
} }
var resourceKey = uri.AbsolutePath; if (assembly?.Resources != null)
IAssetDescriptor? rv = null; {
asm.Resources?.TryGetValue(resourceKey, out rv); var resourceKey = uri.AbsolutePath;
return rv;
if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
{
return true;
}
}
} }
uri = uri.EnsureAbsolute(baseUri); uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares()) if (uri.IsAvares())
{ {
var (asm, path) = GetResAsmAndPath(uri); if (TryGetResAsmAndPath(uri, out var assembly, out var path))
if (asm.AvaloniaResources == null) {
return null; if (assembly.AvaloniaResources == null)
asm.AvaloniaResources.TryGetValue(path, out var desc); {
return desc; return false;
}
if (assembly.AvaloniaResources.TryGetValue(path, out assetDescriptor))
{
return true;
}
}
} }
throw new ArgumentException($"Unsupported url type: " + uri.Scheme, nameof(uri)); return false;
} }
private static (IAssemblyDescriptor asm, string path) GetResAsmAndPath(Uri uri) private static bool TryGetResAsmAndPath(Uri uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly, out string path)
{ {
var asm = s_assemblyDescriptorResolver.GetAssembly(uri.Authority); path = uri.GetUnescapeAbsolutePath();
return (asm, uri.GetUnescapeAbsolutePath());
if (TryLoadAssembly(uri.Authority, out assembly))
{
return true;
}
return false;
} }
private static IAssemblyDescriptor? GetAssembly(Uri? uri) private static bool TryGetAssembly(Uri? uri, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{ {
assembly = null;
if (uri != null) if (uri != null)
{ {
if (!uri.IsAbsoluteUri) if (!uri.IsAbsoluteUri)
return null; {
if (uri.IsAvares()) return false;
return GetResAsmAndPath(uri).asm; }
if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
{
return true;
}
if (uri.IsResm()) if (uri.IsResm())
{ {
var assemblyName = uri.GetAssemblyNameFromQuery(); var assemblyName = uri.GetAssemblyNameFromQuery();
if (assemblyName.Length > 0)
return s_assemblyDescriptorResolver.GetAssembly(assemblyName); if (assemblyName.Length > 0 && TryLoadAssembly(assemblyName, out assembly))
{
return true;
}
} }
} }
return null; return false;
}
private static bool TryLoadAssembly(string assemblyName, [NotNullWhen(true)] out IAssemblyDescriptor? assembly)
{
assembly = null;
try
{
assembly = s_assemblyDescriptorResolver.GetAssembly(assemblyName);
return true;
}
catch (Exception) { }
return false;
} }
#endif #endif
public static void RegisterResUriParsers() public static void RegisterResUriParsers()
{ {
if (!UriParser.IsKnownScheme("avares")) if (!UriParser.IsKnownScheme("avares"))

2
src/Avalonia.Base/PropertyStore/ValueStore.cs

@ -924,7 +924,7 @@ namespace Avalonia.PropertyStore
{ {
_effectiveValues.GetKeyValue(i, out var key, out var e); _effectiveValues.GetKeyValue(i, out var key, out var e);
if (e.Priority == BindingPriority.Unset) if (e.Priority == BindingPriority.Unset && !e.IsOverridenCurrentValue)
{ {
RemoveEffectiveValue(key, i); RemoveEffectiveValue(key, i);
e.DisposeAndRaiseUnset(this, key); e.DisposeAndRaiseUnset(this, key);

2
src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs

@ -21,7 +21,7 @@ namespace Avalonia.Rendering.SceneGraph
Matrix transform, Matrix transform,
IImmutableBrush foreground, IImmutableBrush foreground,
IRef<IGlyphRunImpl> glyphRun) IRef<IGlyphRunImpl> glyphRun)
: base(new Rect(glyphRun.Item.Size), transform, foreground) : base(new Rect(glyphRun.Item.BaselineOrigin, glyphRun.Item.Size), transform, foreground)
{ {
GlyphRun = glyphRun.Clone(); GlyphRun = glyphRun.Clone();
} }

35
src/Avalonia.Controls/Panel.cs

@ -1,13 +1,12 @@
using System; using System;
using System.Collections.Generic;
using System.Collections.Specialized; using System.Collections.Specialized;
using System.ComponentModel; using System.ComponentModel;
using System.Linq; using System.Linq;
using Avalonia.Controls.Presenters;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Reactive; using Avalonia.Reactive;
using Avalonia.Styling;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -59,6 +58,11 @@ namespace Avalonia.Controls
set { SetValue(BackgroundProperty, value); } set { SetValue(BackgroundProperty, value); }
} }
/// <summary>
/// Gets whether the <see cref="Panel"/> hosts the items created by an <see cref="ItemsPresenter"/>.
/// </summary>
public bool IsItemsHost { get; internal set; }
event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged event EventHandler<ChildIndexChangedEventArgs>? IChildIndexProvider.ChildIndexChanged
{ {
add add
@ -129,24 +133,29 @@ namespace Avalonia.Controls
/// <param name="e">The event args.</param> /// <param name="e">The event args.</param>
protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) protected virtual void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e)
{ {
List<Control> controls;
switch (e.Action) switch (e.Action)
{ {
case NotifyCollectionChangedAction.Add: case NotifyCollectionChangedAction.Add:
controls = e.NewItems!.OfType<Control>().ToList(); if (!IsItemsHost)
LogicalChildren.InsertRange(e.NewStartingIndex, controls); {
LogicalChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Control>().ToList());
}
VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>()); VisualChildren.InsertRange(e.NewStartingIndex, e.NewItems!.OfType<Visual>());
break; break;
case NotifyCollectionChangedAction.Move: case NotifyCollectionChangedAction.Move:
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex); if (!IsItemsHost)
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems.Count, e.NewStartingIndex); {
LogicalChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
}
VisualChildren.MoveRange(e.OldStartingIndex, e.OldItems!.Count, e.NewStartingIndex);
break; break;
case NotifyCollectionChangedAction.Remove: case NotifyCollectionChangedAction.Remove:
controls = e.OldItems!.OfType<Control>().ToList(); if (!IsItemsHost)
LogicalChildren.RemoveAll(controls); {
LogicalChildren.RemoveAll(e.OldItems!.OfType<Control>().ToList());
}
VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>()); VisualChildren.RemoveAll(e.OldItems!.OfType<Visual>());
break; break;
@ -155,7 +164,10 @@ namespace Avalonia.Controls
{ {
var index = i + e.OldStartingIndex; var index = i + e.OldStartingIndex;
var child = (Control)e.NewItems![i]!; var child = (Control)e.NewItems![i]!;
LogicalChildren[index] = child; if (!IsItemsHost)
{
LogicalChildren[index] = child;
}
VisualChildren[index] = child; VisualChildren[index] = child;
} }
break; break;
@ -200,6 +212,7 @@ namespace Avalonia.Controls
return child is Control control ? Children.IndexOf(control) : -1; return child is Control control ? Children.IndexOf(control) : -1;
} }
/// <inheritdoc />
public bool TryGetTotalCount(out int count) public bool TryGetTotalCount(out int count)
{ {
count = Children.Count; count = Children.Count;

1
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -167,6 +167,7 @@ namespace Avalonia.Controls.Presenters
Panel = ItemsPanel.Build(); Panel = ItemsPanel.Build();
Panel.SetValue(TemplatedParentProperty, TemplatedParent); Panel.SetValue(TemplatedParentProperty, TemplatedParent);
Panel.IsItemsHost = true;
_scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo; _scrollSnapPointsInfo = Panel as IScrollSnapPointsInfo;
LogicalChildren.Add(Panel); LogicalChildren.Add(Panel);
VisualChildren.Add(Panel); VisualChildren.Add(Panel);

2
src/Windows/Avalonia.Win32/Input/KeyInterop.cs

@ -4,7 +4,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input namespace Avalonia.Win32.Input
{ {
static class KeyInterop public static class KeyInterop
{ {
private static readonly Dictionary<Key, int> s_virtualKeyFromKey = new Dictionary<Key, int> private static readonly Dictionary<Key, int> s_virtualKeyFromKey = new Dictionary<Key, int>
{ {

13
src/tools/PublicAnalyzers/AvaloniaPropertyAnalyzer.CompileAnalyzer.cs

@ -396,7 +396,7 @@ public partial class AvaloniaPropertyAnalyzer
hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration hostTypeRef = new(_avaloniaObjectType, Location.None); // assume that an attached property applies everywhere until we find its registration
} }
var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef }; var result = new AvaloniaPropertyDescription(inferredName, propertyType, valueType) { HostType = hostTypeRef };
// assume that the property is owned by its containing type at the point of assignment, until we find its registration // assume that the property is owned by its containing type at the point of assignment, until we find its registration
result.SetAssignment(s, new(s.ContainingType, Location.None)); result.SetAssignment(s, new(s.ContainingType, Location.None));
@ -570,7 +570,7 @@ public partial class AvaloniaPropertyAnalyzer
if (_allGetSetMethods.Contains(originalMethod)) if (_allGetSetMethods.Contains(originalMethod))
{ {
if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } && if (invocation.Instance is IInstanceReferenceOperation { ReferenceKind: InstanceReferenceKind.ContainingTypeInstance } &&
GetReferencedProperty(invocation.Arguments[0]) is { } refProp && GetReferencedProperty(invocation.Arguments[0]) is { } refProp &&
refProp.description.AssignedTo.TryGetValue(refProp.storageSymbol, out var ownerType) && refProp.description.AssignedTo.TryGetValue(refProp.storageSymbol, out var ownerType) &&
!DerivesFrom(context.ContainingSymbol.ContainingType, ownerType.Type) && !DerivesFrom(context.ContainingSymbol.ContainingType, ownerType.Type) &&
@ -694,9 +694,14 @@ public partial class AvaloniaPropertyAnalyzer
void VerifyAccessor(IMethodSymbol? method, string verb, string methodName) void VerifyAccessor(IMethodSymbol? method, string verb, string methodName)
{ {
if (method == null) if (method is null)
{ {
context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName)); if (avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Public ||
(avaloniaPropertyStorage.DeclaredAccessibility == Accessibility.Protected
&& avaloniaPropertyStorage.ContainingSymbol.DeclaredAccessibility == Accessibility.Public))
{
context.ReportDiagnostic(Diagnostic.Create(MissingAccessor, property.Locations[0], avaloniaPropertyStorage, verb, methodName));
}
} }
else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility) else if (method.DeclaredAccessibility != avaloniaPropertyStorage.DeclaredAccessibility && method.DeclaredAccessibility != property.DeclaredAccessibility)
{ {

13
tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs

@ -9,7 +9,7 @@ namespace Avalonia.Base.UnitTests;
public class AssetLoaderTests : IDisposable public class AssetLoaderTests : IDisposable
{ {
public class MockAssembly : Assembly {} public class MockAssembly : Assembly { }
private const string AssemblyNameWithWhitespace = "Awesome Library"; private const string AssemblyNameWithWhitespace = "Awesome Library";
@ -50,6 +50,17 @@ public class AssetLoaderTests : IDisposable
Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName); Assert.Equal(AssemblyNameWithNonAscii, assemblyActual?.FullName);
} }
[Fact]
public void Invalid_AssemblyName_Should_Yield_Empty_Enumerable()
{
var uri = new Uri($"avares://InvalidAssembly");
var loader = new AssetLoader();
var assemblyActual = loader.GetAssets(uri, null);
Assert.Empty(assemblyActual);
}
private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName) private static IAssemblyDescriptor CreateAssemblyDescriptor(string assemblyName)
{ {
var assembly = Mock.Of<MockAssembly>(); var assembly = Mock.Of<MockAssembly>();

81
tests/Avalonia.Base.UnitTests/AvaloniaObjectTests_SetCurrentValue.cs

@ -1,6 +1,9 @@
using System; using System;
using Avalonia.Controls;
using Avalonia.Data; using Avalonia.Data;
using Avalonia.Diagnostics; using Avalonia.Diagnostics;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit; using Xunit;
using Observable = Avalonia.Reactive.Observable; using Observable = Avalonia.Reactive.Observable;
@ -275,6 +278,79 @@ namespace Avalonia.Base.UnitTests
Assert.Equal("style", target.Foo); Assert.Equal("style", target.Foo);
} }
[Fact]
public void SetCurrent_Value_Persists_When_Toggling_Style_1()
{
var target = new Class1();
var root = new TestRoot(target)
{
Styles =
{
new Style(x => x.OfType<Class1>().Class("foo"))
{
Setters = { new Setter(Class1.BarProperty, "bar") },
}
}
};
root.LayoutManager.ExecuteInitialLayoutPass();
target.SetCurrentValue(Class1.FooProperty, "current");
Assert.Equal("current", target.Foo);
Assert.Equal("bardefault", target.Bar);
target.Classes.Add("foo");
Assert.Equal("current", target.Foo);
Assert.Equal("bar", target.Bar);
target.Classes.Remove("foo");
Assert.Equal("current", target.Foo);
Assert.Equal("bardefault", target.Bar);
}
[Fact]
public void SetCurrent_Value_Persists_When_Toggling_Style_2()
{
var target = new Class1();
var root = new TestRoot(target)
{
Styles =
{
new Style(x => x.OfType<Class1>().Class("foo"))
{
Setters =
{
new Setter(Class1.BarProperty, "bar"),
new Setter(Class1.InheritedProperty, "inherited"),
},
}
}
};
root.LayoutManager.ExecuteInitialLayoutPass();
target.SetCurrentValue(Class1.FooProperty, "current");
Assert.Equal("current", target.Foo);
Assert.Equal("bardefault", target.Bar);
Assert.Equal("inheriteddefault", target.Inherited);
target.Classes.Add("foo");
Assert.Equal("current", target.Foo);
Assert.Equal("bar", target.Bar);
Assert.Equal("inherited", target.Inherited);
target.Classes.Remove("foo");
Assert.Equal("current", target.Foo);
Assert.Equal("bardefault", target.Bar);
Assert.Equal("inheriteddefault", target.Inherited);
}
private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property) private BindingPriority GetPriority(AvaloniaObject target, AvaloniaProperty property)
{ {
return target.GetDiagnostic(property).Priority; return target.GetDiagnostic(property).Priority;
@ -285,16 +361,19 @@ namespace Avalonia.Base.UnitTests
return target.GetDiagnostic(property).IsOverriddenCurrentValue; return target.GetDiagnostic(property).IsOverriddenCurrentValue;
} }
private class Class1 : AvaloniaObject private class Class1 : Control
{ {
public static readonly StyledProperty<string> FooProperty = public static readonly StyledProperty<string> FooProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Foo), "foodefault"); AvaloniaProperty.Register<Class1, string>(nameof(Foo), "foodefault");
public static readonly StyledProperty<string> BarProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Bar), "bardefault");
public static readonly StyledProperty<string> InheritedProperty = public static readonly StyledProperty<string> InheritedProperty =
AvaloniaProperty.Register<Class1, string>(nameof(Inherited), "inheriteddefault", inherits: true); AvaloniaProperty.Register<Class1, string>(nameof(Inherited), "inheriteddefault", inherits: true);
public static readonly StyledProperty<double> CoercedProperty = public static readonly StyledProperty<double> CoercedProperty =
AvaloniaProperty.Register<Class1, double>(nameof(Coerced), coerce: Coerce); AvaloniaProperty.Register<Class1, double>(nameof(Coerced), coerce: Coerce);
public string Foo => GetValue(FooProperty); public string Foo => GetValue(FooProperty);
public string Bar => GetValue(BarProperty);
public string Inherited => GetValue(InheritedProperty); public string Inherited => GetValue(InheritedProperty);
public double Coerced => GetValue(CoercedProperty); public double Coerced => GetValue(CoercedProperty);
public double CoerceMax { get; set; } = 100; public double CoerceMax { get; set; } = 100;

15
tests/Avalonia.Controls.UnitTests/ItemsControlTests.cs

@ -73,6 +73,19 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(target, target.Presenter.Panel.TemplatedParent); Assert.Equal(target, target.Presenter.Panel.TemplatedParent);
} }
[Fact]
public void Panel_Should_Have_ItemsHost_Set_To_True()
{
var target = new ItemsControl();
target.Template = GetTemplate();
target.Items = new[] { "Foo" };
target.ApplyTemplate();
target.Presenter!.ApplyTemplate();
Assert.True(target.Presenter.Panel!.IsItemsHost);
}
[Fact] [Fact]
public void Container_Should_Have_TemplatedParent_Set_To_Null() public void Container_Should_Have_TemplatedParent_Set_To_Null()
{ {
@ -634,7 +647,7 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate(); target.ApplyTemplate();
target.Presenter.ApplyTemplate(); target.Presenter.ApplyTemplate();
var item = target.Presenter.Panel.LogicalChildren[0]; var item = target.LogicalChildren[0];
Assert.Null(NameScope.GetNameScope((TextBlock)item)); Assert.Null(NameScope.GetNameScope((TextBlock)item));
} }

15
tests/Avalonia.Controls.UnitTests/PanelTests.cs

@ -141,5 +141,20 @@ namespace Avalonia.Controls.UnitTests
var panel = new Panel(); var panel = new Panel();
Assert.Throws<ArgumentNullException>(() => panel.Children.Add(null!)); Assert.Throws<ArgumentNullException>(() => panel.Children.Add(null!));
} }
[Fact]
public void Adding_Control_To_Items_Host_Panel_Should_Not_Affect_Logical_Children()
{
var child = new Control();
var realParent = new ContentControl { Content = child };
var panel = new Panel { IsItemsHost = true };
panel.Children.Add(child);
Assert.Empty(panel.LogicalChildren);
Assert.Same(child.Parent, realParent);
Assert.Same(child.GetLogicalParent(), realParent);
Assert.Same(child.GetVisualParent(), panel);
}
} }
} }

2
tests/Avalonia.Controls.UnitTests/TabControlTests.cs

@ -327,7 +327,7 @@ namespace Avalonia.Controls.UnitTests
ApplyTemplate(target); ApplyTemplate(target);
var logicalChildren = target.ItemsPresenterPart.Panel.GetLogicalChildren(); var logicalChildren = target.GetLogicalChildren();
var result = logicalChildren var result = logicalChildren
.OfType<TabItem>() .OfType<TabItem>()

2
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -1178,7 +1178,7 @@ namespace Avalonia.Controls.UnitTests
target.ApplyTemplate(); target.ApplyTemplate();
target.Presenter.ApplyTemplate(); target.Presenter.ApplyTemplate();
var item = target.Presenter.Panel.LogicalChildren[0]; var item = target.LogicalChildren[0];
Assert.Null(NameScope.GetNameScope((TreeViewItem)item)); Assert.Null(NameScope.GetNameScope((TreeViewItem)item));
} }

Loading…
Cancel
Save