diff --git a/src/Avalonia.Base/Platform/AssetLoader.cs b/src/Avalonia.Base/Platform/AssetLoader.cs
index 659cfb75df..7df446e854 100644
--- a/src/Avalonia.Base/Platform/AssetLoader.cs
+++ b/src/Avalonia.Base/Platform/AssetLoader.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Reflection;
@@ -62,7 +63,7 @@ namespace Avalonia.Platform
/// True if the asset could be found; otherwise false.
public bool Exists(Uri uri, Uri? baseUri = null)
{
- return GetAsset(uri, baseUri) != null;
+ return TryGetAsset(uri, baseUri, out _);
}
///
@@ -94,21 +95,27 @@ namespace Avalonia.Platform
///
public (Stream stream, Assembly assembly) OpenAndGetAssembly(Uri uri, Uri? baseUri = null)
{
- var asset = GetAsset(uri, baseUri);
-
- if (asset == null)
+ if (TryGetAsset(uri, baseUri, out var assetDescriptor))
{
- 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)
{
if (!uri.IsAbsoluteUri && baseUri != null)
+ {
uri = new Uri(baseUri, uri);
- return GetAssembly(uri)?.Assembly;
+ }
+
+ if (TryGetAssembly(uri, out var assemblyDescriptor))
+ {
+ return assemblyDescriptor.Assembly;
+ }
+
+ return null;
}
///
@@ -121,99 +128,145 @@ namespace Avalonia.Platform
{
if (uri.IsAbsoluteResm())
{
- var assembly = GetAssembly(uri);
+ if (!TryGetAssembly(uri, out var assembly))
+ {
+ assembly = _defaultResmAssembly;
+ }
return assembly?.Resources?
- .Where(x => x.Key.IndexOf(uri.GetUnescapeAbsolutePath(), StringComparison.Ordinal) >= 0)
- .Select(x =>new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
- Enumerable.Empty();
+ .Where(x => x.Key.Contains(uri.GetUnescapeAbsolutePath()))
+ .Select(x => new Uri($"resm:{x.Key}?assembly={assembly.Name}")) ??
+ Enumerable.Empty();
}
uri = uri.EnsureAbsolute(baseUri);
+
if (uri.IsAvares())
{
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm == null)
+ if (!TryGetResAsmAndPath(uri, out var assembly, out var path))
{
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
+ return Enumerable.Empty();
}
- if (asm.AvaloniaResources == null)
+ if (assembly?.AvaloniaResources == null)
+ {
return Enumerable.Empty();
+ }
- if (path[path.Length - 1] != '/')
+ if (path.Length > 0 && path[path.Length - 1] != '/')
+ {
path += '/';
+ }
- return asm.AvaloniaResources
+ return assembly.AvaloniaResources
.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();
}
-
- private IAssetDescriptor? GetAsset(Uri uri, Uri? baseUri)
- {
+
+ private bool TryGetAsset(Uri uri, Uri? baseUri, [NotNullWhen(true)] out IAssetDescriptor? assetDescriptor)
+ {
+ assetDescriptor = null;
+
if (uri.IsAbsoluteResm())
{
- var asm = GetAssembly(uri) ?? GetAssembly(baseUri) ?? _defaultResmAssembly;
-
- if (asm == null)
+ if (!TryGetAssembly(uri, out var assembly) && !TryGetAssembly(baseUri, out assembly))
{
- throw new ArgumentException(
- "No default assembly, entry assembly or explicit assembly specified; " +
- "don't know where to look up for the resource, try specifying assembly explicitly.");
+ assembly = _defaultResmAssembly;
}
- var resourceKey = uri.AbsolutePath;
- IAssetDescriptor? rv = null;
- asm.Resources?.TryGetValue(resourceKey, out rv);
- return rv;
+ if (assembly?.Resources != null)
+ {
+ var resourceKey = uri.AbsolutePath;
+
+ if (assembly.Resources.TryGetValue(resourceKey, out assetDescriptor))
+ {
+ return true;
+ }
+ }
}
uri = uri.EnsureAbsolute(baseUri);
if (uri.IsAvares())
{
- var (asm, path) = GetResAsmAndPath(uri);
- if (asm.AvaloniaResources == null)
- return null;
- asm.AvaloniaResources.TryGetValue(path, out var desc);
- return desc;
+ if (TryGetResAsmAndPath(uri, out var assembly, out var path))
+ {
+ if (assembly.AvaloniaResources == null)
+ {
+ 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);
- return (asm, uri.GetUnescapeAbsolutePath());
+ path = 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.IsAbsoluteUri)
- return null;
- if (uri.IsAvares())
- return GetResAsmAndPath(uri).asm;
+ {
+ return false;
+ }
+
+ if (uri.IsAvares() && TryGetResAsmAndPath(uri, out assembly, out _))
+ {
+ return true;
+ }
if (uri.IsResm())
{
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
-
+
public static void RegisterResUriParsers()
{
if (!UriParser.IsKnownScheme("avares"))
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
index 4d8759f545..4b09bc9280 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
@@ -21,7 +21,7 @@ namespace Avalonia.Rendering.SceneGraph
Matrix transform,
IImmutableBrush foreground,
IRef glyphRun)
- : base(new Rect(glyphRun.Item.Size), transform, foreground)
+ : base(new Rect(glyphRun.Item.BaselineOrigin, glyphRun.Item.Size), transform, foreground)
{
GlyphRun = glyphRun.Clone();
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
index 6150265cf8..736e764aa7 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlBindingPathParser.cs
@@ -146,13 +146,23 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "Tree")
?.Values[0] is XamlAstTextNode treeTypeValue ? treeTypeValue.Text : "Visual";
- var ancestorTypeName = relativeSourceObject.Children
+ var ancestorType = relativeSourceObject.Children
.OfType()
.FirstOrDefault(x => x.Property.GetClrProperty().Name == "AncestorType")
- ?.Values[0] as XamlAstTextNode;
+ ?.Values[0] switch
+ {
+ XamlAstTextNode textNode => TypeReferenceResolver.ResolveType(
+ context,
+ textNode.Text,
+ false,
+ textNode,
+ true).GetClrType(),
+ XamlTypeExtensionNode typeExtensionNode => typeExtensionNode.Value.GetClrType(),
+ null => null,
+ _ => throw new XamlParseException($"Unsupported node for AncestorType property", relativeSourceObject)
+ };
- IXamlType ancestorType = null;
- if (ancestorTypeName is null)
+ if (ancestorType is null)
{
if (treeType == "Visual")
{
@@ -174,15 +184,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
}
}
}
- else
- {
- ancestorType = TypeReferenceResolver.ResolveType(
- context,
- ancestorTypeName.Text,
- false,
- ancestorTypeName,
- true).GetClrType();
- }
if (treeType == "Visual")
{
diff --git a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
index f5b2d462ab..1ab4c0d2dc 100644
--- a/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
+++ b/src/Windows/Avalonia.Win32/Input/KeyInterop.cs
@@ -4,7 +4,7 @@ using Avalonia.Win32.Interop;
namespace Avalonia.Win32.Input
{
- static class KeyInterop
+ public static class KeyInterop
{
private static readonly Dictionary s_virtualKeyFromKey = new Dictionary
{
diff --git a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
index 28fb19e119..894b6578e3 100644
--- a/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
+++ b/tests/Avalonia.Base.UnitTests/AssetLoaderTests.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Base.UnitTests;
public class AssetLoaderTests : IDisposable
{
- public class MockAssembly : Assembly {}
+ public class MockAssembly : Assembly { }
private const string AssemblyNameWithWhitespace = "Awesome Library";
@@ -50,6 +50,17 @@ public class AssetLoaderTests : IDisposable
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)
{
var assembly = Mock.Of();
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 0b33cd9d97..27634b457b 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -847,6 +847,30 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Equal("test", target.Text);
}
}
+
+ [Fact]
+ public void ResolvesRelativeSourceBindingEvenLongerForm()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var target = window.FindControl("text");
+
+ window.ApplyTemplate();
+ window.Presenter.ApplyTemplate();
+ target.ApplyTemplate();
+
+ Assert.Equal("test", target.Text);
+ }
+ }
[Fact]
public void ResolvesRelativeSourceBindingFromTemplate()
@@ -1735,7 +1759,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
";
+ X='{CompiledBinding StringProperty, DataType=local:TestDataContext}' />";
var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(new RuntimeXamlLoaderDocument(xaml),
new RuntimeXamlLoaderConfiguration { UseCompiledBindingsByDefault = true });
var compiledPath = ((CompiledBindingExtension)control.X).Path;
@@ -1745,6 +1769,33 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ [Fact]
+ public void Should_Bind_To_Nested_Generic_Property()
+ {
+ // See https://github.com/AvaloniaUI/Avalonia/issues/10485
+ // This code works fine with SRE, and test is passing, but it fails on Cecil.
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+";
+ var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var comboBox = window.FindControl("comboBox");
+
+ var dataContext = new TestDataContext();
+ dataContext.GenericProperty.Add(123);
+ dataContext.GenericProperty.CurrentItem = 123;
+ window.DataContext = dataContext;
+
+ Assert.Equal(123, comboBox.SelectedItem);
+ }
+ }
+
static void Throws(string type, Action cb)
{
try
@@ -1837,8 +1888,10 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public string ExplicitProperty => "Bye";
- public static string StaticProperty => "World";
+ public static string StaticProperty => "World";
+ public ListItemCollectionView GenericProperty { get; } = new();
+
public class NonIntegerIndexer : NotifyingBase, INonIntegerIndexerDerived
{
private readonly Dictionary _storage = new Dictionary();
@@ -1858,6 +1911,11 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ public class ListItemCollectionView : List
+ {
+ public T CurrentItem { get; set; }
+ }
+
public class MethodDataContext
{
public void Action() { }