Browse Source

Merge branch 'master' into fixes/textProcessingBugs

pull/7604/head
Benedikt Stebner 4 years ago
committed by GitHub
parent
commit
bd5a59c247
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 26
      Avalonia.sln
  2. 1
      build/CoreLibraries.props
  3. 5
      nukebuild/numerge.config
  4. 33
      src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
  5. 6
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  6. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  7. 26
      src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs
  8. 5
      src/Avalonia.Controls/Window.cs
  9. 3
      src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt
  10. 103
      src/Avalonia.DesktopRuntime/AppBuilder.cs
  11. 21
      src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj
  12. 4
      src/Avalonia.Native/Properties/AssemblyInfo.cs
  13. 20
      src/Avalonia.PlatformSupport/AppBuilder.cs
  14. 1
      src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj
  15. 1
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  16. 27
      src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs
  17. 5
      src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
  18. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs
  19. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
  20. 4
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
  21. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  22. 202
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  23. 116
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs
  24. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
  25. 2
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  26. 156
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
  27. 48
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  28. 74
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs
  29. 5
      src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs
  30. 4
      src/Windows/Avalonia.Win32/Properties/AssemblyInfo.cs
  31. 1
      tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj
  32. 1
      tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj
  33. 22
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs
  34. 231
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
  35. 1
      tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

26
Avalonia.sln

@ -177,8 +177,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.OpenGL", "src\Aval
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Native", "src\Avalonia.Native\Avalonia.Native.csproj", "{12A91A62-C064-42CA-9A8C-A1272F354388}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.DesktopRuntime", "src\Avalonia.DesktopRuntime\Avalonia.DesktopRuntime.csproj", "{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Packages", "Packages", "{E870DCD7-F46A-498D-83FC-D0FD13E0A11C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia", "packages\Avalonia\Avalonia.csproj", "{D49233F8-F29C-47DD-9975-C4C9E4502720}"
@ -1559,30 +1557,6 @@ Global
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhone.Build.0 = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{12A91A62-C064-42CA-9A8C-A1272F354388}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhone.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|Any CPU.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhone.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|Any CPU.ActiveCfg = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|Any CPU.Build.0 = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhone.ActiveCfg = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhone.Build.0 = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{878FEFE0-CD14-41CB-90B0-DBCB163E8F15}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{D49233F8-F29C-47DD-9975-C4C9E4502720}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU

1
build/CoreLibraries.props

@ -16,7 +16,6 @@
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup/Avalonia.Markup.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.MicroCom/Avalonia.MicroCom.csproj" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj" Condition="'$(TargetFramework)' != 'netstandard2.0'" />
<ProjectReference Include="$(MSBuildThisFileDirectory)/../src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj" />
</ItemGroup>
</Project>

5
nukebuild/numerge.config

@ -11,11 +11,6 @@
"Id": "Avalonia.Build.Tasks",
"IgnoreMissingFrameworkBinaries": true,
"DoNotMergeDependencies": true
},
{
"Id": "Avalonia.DesktopRuntime",
"IgnoreMissingFrameworkBinaries": true,
"IgnoreMissingFrameworkDependencies": true
}
]
}

33
src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs

@ -22,19 +22,9 @@ namespace Avalonia.Data.Core.Plugins
var method = GetFirstMethodWithName(instance.GetType(), methodName);
if (method != null)
if (method is not null)
{
var parameters = method.GetParameters();
if (parameters.Length + (method.ReturnType == typeof(void) ? 0 : 1) > 8)
{
var exception = new ArgumentException(
"Cannot create a binding accessor for a method with more than 8 parameters or more than 7 parameters if it has a non-void return type.",
nameof(methodName));
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error));
}
return new Accessor(reference, method, parameters);
return new Accessor(reference, method);
}
else
{
@ -82,18 +72,20 @@ namespace Avalonia.Data.Core.Plugins
private sealed class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference<object?> reference, MethodInfo method, ParameterInfo[] parameters)
public Accessor(WeakReference<object?> reference, MethodInfo method)
{
_ = reference ?? throw new ArgumentNullException(nameof(reference));
_ = method ?? throw new ArgumentNullException(nameof(method));
var returnType = method.ReturnType;
bool hasReturn = returnType != typeof(void);
var signatureTypeCount = (hasReturn ? 1 : 0) + parameters.Length;
var parameters = method.GetParameters();
var signatureTypeCount = parameters.Length + 1;
var paramTypes = new Type[signatureTypeCount];
for (var i = 0; i < parameters.Length; i++)
{
ParameterInfo parameter = parameters[i];
@ -101,16 +93,9 @@ namespace Avalonia.Data.Core.Plugins
paramTypes[i] = parameter.ParameterType;
}
if (hasReturn)
{
paramTypes[paramTypes.Length - 1] = returnType;
paramTypes[paramTypes.Length - 1] = returnType;
PropertyType = Expression.GetFuncType(paramTypes);
}
else
{
PropertyType = Expression.GetActionType(paramTypes);
}
PropertyType = Expression.GetDelegateType(paramTypes);
if (method.IsStatic)
{

6
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -124,6 +124,9 @@ namespace Avalonia.Build.Tasks
var indexerAccessorClosure = new TypeDefinition("CompiledAvaloniaXaml", "!IndexerAccessorFactoryClosure",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(indexerAccessorClosure);
var trampolineBuilder = new TypeDefinition("CompiledAvaloniaXaml", "XamlIlTrampolines",
TypeAttributes.Class, asm.MainModule.TypeSystem.Object);
asm.MainModule.Types.Add(trampolineBuilder);
var (xamlLanguage , emitConfig) = AvaloniaXamlIlLanguage.Configure(typeSystem);
var compilerConfig = new AvaloniaXamlIlCompilerConfiguration(typeSystem,
@ -133,6 +136,7 @@ namespace Avalonia.Build.Tasks
AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(typeSystem.CreateTypeBuilder(clrPropertiesDef)),
new XamlIlPropertyInfoAccessorFactoryEmitter(typeSystem.CreateTypeBuilder(indexerAccessorClosure)),
new XamlIlTrampolineBuilder(typeSystem.CreateTypeBuilder(trampolineBuilder)),
new DeterministicIdGenerator());
@ -255,6 +259,8 @@ namespace Avalonia.Build.Tasks
true),
(closureName, closureBaseType) =>
populateBuilder.DefineSubType(closureBaseType, closureName, false),
(closureName, returnType, parameterTypes) =>
populateBuilder.DefineDelegateSubType(closureName, false, returnType, parameterTypes),
res.Uri, res
);

3
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -48,6 +48,7 @@ MembersMustExist : Member 'public Avalonia.Media.FormattedText Avalonia.Controls
MembersMustExist : Member 'public System.Int32 Avalonia.Controls.Presenters.TextPresenter.GetCaretIndex(Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Controls.Presenters.TextPresenter.InvalidateFormattedText()' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportWindowingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
@ -67,4 +68,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 68
Total Issues: 69

26
src/Avalonia.Controls/Platform/ExportWindowingSubsystemAttribute.cs

@ -1,26 +0,0 @@
using System;
namespace Avalonia.Platform
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ExportWindowingSubsystemAttribute : Attribute
{
public ExportWindowingSubsystemAttribute(OperatingSystemType requiredRuntimePlatform, int priority, string name, Type initializationType,
string initializationMethod, Type? environmentChecker = null)
{
Name = name;
InitializationType = initializationType;
InitializationMethod = initializationMethod;
EnvironmentChecker = environmentChecker;
RequiredOS = requiredRuntimePlatform;
Priority = priority;
}
public string InitializationMethod { get; private set; }
public Type? EnvironmentChecker { get; }
public Type InitializationType { get; private set; }
public string Name { get; private set; }
public int Priority { get; private set; }
public OperatingSystemType RequiredOS { get; private set; }
}
}

5
src/Avalonia.Controls/Window.cs

@ -255,6 +255,11 @@ namespace Avalonia.Controls
/// </summary>
public new IWindowImpl? PlatformImpl => (IWindowImpl?)base.PlatformImpl;
/// <summary>
/// Gets a collection of child windows owned by this window.
/// </summary>
public IReadOnlyList<Window> OwnedWindows => _children.Select(x => x.child).ToList();
/// <summary>
/// Gets or sets a value indicating how the window will size itself to fit its content.
/// </summary>

3
src/Avalonia.DesktopRuntime/ApiCompatBaseline.txt

@ -1,3 +0,0 @@
Compat issues with assembly Avalonia.DesktopRuntime:
TypesMustExist : Type 'Avalonia.Shared.PlatformSupport.AssetLoader' does not exist in the implementation but it does exist in the contract.
Total Issues: 1

103
src/Avalonia.DesktopRuntime/AppBuilder.cs

@ -1,103 +0,0 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Platform;
using Avalonia.PlatformSupport;
namespace Avalonia
{
/// <summary>
/// Initializes platform-specific services for an <see cref="Application"/>.
/// </summary>
public sealed class AppBuilder : AppBuilderBase<AppBuilder>
{
/// <summary>
/// Initializes a new instance of the <see cref="AppBuilder"/> class.
/// </summary>
public AppBuilder()
: base(new StandardRuntimePlatform(),
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType.Assembly))
{
}
bool CheckEnvironment(Type checkerType)
{
if (checkerType == null)
return true;
try
{
return ((IModuleEnvironmentChecker) Activator.CreateInstance(checkerType)).IsCompatible;
}
catch
{
return false;
}
}
/// <summary>
/// Instructs the <see cref="AppBuilder"/> to use the best settings for the platform.
/// </summary>
/// <returns>An <see cref="AppBuilder"/> instance.</returns>
public AppBuilder UseSubsystemsFromStartupDirectory()
{
var os = RuntimePlatform.GetRuntimeInfo().OperatingSystem;
LoadAssembliesInDirectory();
var windowingSubsystemAttribute = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
from attribute in assembly.GetCustomAttributes<ExportWindowingSubsystemAttribute>()
where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker)
orderby attribute.Priority ascending
select attribute).FirstOrDefault();
if (windowingSubsystemAttribute == null)
{
throw new InvalidOperationException("No windowing subsystem found. Are you missing assembly references?");
}
var renderingSubsystemAttribute = (from assembly in AppDomain.CurrentDomain.GetAssemblies()
from attribute in assembly.GetCustomAttributes<ExportRenderingSubsystemAttribute>()
where attribute.RequiredOS == os && CheckEnvironment(attribute.EnvironmentChecker)
where attribute.RequiresWindowingSubsystem == null
|| attribute.RequiresWindowingSubsystem == windowingSubsystemAttribute.Name
orderby attribute.Priority ascending
select attribute).FirstOrDefault();
if (renderingSubsystemAttribute == null)
{
throw new InvalidOperationException("No rendering subsystem found. Are you missing assembly references?");
}
UseWindowingSubsystem(() => windowingSubsystemAttribute.InitializationType
.GetRuntimeMethod(windowingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null),
windowingSubsystemAttribute.Name);
UseRenderingSubsystem(() => renderingSubsystemAttribute.InitializationType
.GetRuntimeMethod(renderingSubsystemAttribute.InitializationMethod, Type.EmptyTypes).Invoke(null, null),
renderingSubsystemAttribute.Name);
return this;
}
private void LoadAssembliesInDirectory()
{
var location = Assembly.GetEntryAssembly().Location;
if (string.IsNullOrWhiteSpace(location))
return;
var dir = new FileInfo(location).Directory;
if (dir == null)
return;
foreach (var file in dir.EnumerateFiles("*.dll"))
{
try
{
Assembly.LoadFile(file.FullName);
}
catch (Exception)
{
}
}
}
}
}

21
src/Avalonia.DesktopRuntime/Avalonia.DesktopRuntime.csproj

@ -1,21 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net461;netcoreapp2.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../Avalonia.Base/Avalonia.Base.csproj" />
<ProjectReference Include="../Avalonia.Visuals/Avalonia.Visuals.csproj" />
<ProjectReference Include="../Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="../Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Runtime.InteropServices.RuntimeInformation" Version="4.3.0" Condition="'$(TargetFramework)' == 'net461'" />
</ItemGroup>
<Import Project="..\..\build\NetCore.props" />
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\ApiDiff.props" />
</Project>

4
src/Avalonia.Native/Properties/AssemblyInfo.cs

@ -1,4 +0,0 @@
using Avalonia.Native;
using Avalonia.Platform;
[assembly: ExportWindowingSubsystem(OperatingSystemType.OSX, 1, "AvaloniaNative", typeof(AvaloniaNativePlatform), nameof(AvaloniaNativePlatform.Initialize))]

20
src/Avalonia.PlatformSupport/AppBuilder.cs

@ -0,0 +1,20 @@
using Avalonia.Controls;
using Avalonia.PlatformSupport;
namespace Avalonia
{
/// <summary>
/// Initializes platform-specific services for an <see cref="Application"/>.
/// </summary>
public sealed class AppBuilder : AppBuilderBase<AppBuilder>
{
/// <summary>
/// Initializes a new instance of the <see cref="AppBuilder"/> class.
/// </summary>
public AppBuilder()
: base(new StandardRuntimePlatform(),
builder => StandardRuntimePlatformServices.Register(builder.ApplicationType?.Assembly))
{
}
}
}

1
src/Avalonia.PlatformSupport/Avalonia.PlatformSupport.csproj

@ -6,6 +6,7 @@
<ItemGroup>
<ProjectReference Include="../Avalonia.Base/Avalonia.Base.csproj" />
<ProjectReference Include="../Avalonia.Controls/Avalonia.Controls.csproj" />
</ItemGroup>
<ItemGroup>

1
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -115,6 +115,7 @@ CannotAddAbstractMembers : Member 'public Avalonia.Media.BaselineAlignment Avalo
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Media.TextFormatting.TextShaper.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Media.TextFormatting.Unicode.BiDiClass' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.Unicode.BiDiClass Avalonia.Media.TextFormatting.Unicode.Codepoint.BiDiClass.get()' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.ExportRenderingSubsystemAttribute' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawEllipse(Avalonia.Media.IBrush, Avalonia.Media.IPen, Avalonia.Rect)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Platform.IFormattedTextImpl)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawText(Avalonia.Media.IBrush, Avalonia.Point, Avalonia.Platform.IFormattedTextImpl)' does not exist in the implementation but it does exist in the contract.

27
src/Avalonia.Visuals/Platform/ExportRenderingSubsystemAttribute.cs

@ -1,27 +0,0 @@
using System;
namespace Avalonia.Platform
{
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
public class ExportRenderingSubsystemAttribute : Attribute
{
public ExportRenderingSubsystemAttribute(OperatingSystemType requiredOS, int priority, string name, Type initializationType, string initializationMethod,
Type? environmentChecker = null)
{
Name = name;
InitializationType = initializationType;
InitializationMethod = initializationMethod;
EnvironmentChecker = environmentChecker;
RequiredOS = requiredOS;
Priority = priority;
}
public string InitializationMethod { get; private set; }
public Type? EnvironmentChecker { get; }
public Type InitializationType { get; private set; }
public string Name { get; private set; }
public int Priority { get; private set; }
public OperatingSystemType RequiredOS { get; private set; }
public string? RequiresWindowingSubsystem { get; set; }
}
}

5
src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs

@ -177,11 +177,13 @@ namespace Avalonia.Markup.Xaml.XamlIl
var tb = _sreBuilder.DefineType("Builder_" + Guid.NewGuid().ToString("N") + "_" + uri);
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType))),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
_sreEmitMappings,
_sreContextType) { EnableIlVerification = true };
@ -196,6 +198,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
clrPropertyBuilder.CreateTypeInfo();
trampolineBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);
}

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompilerConfiguration.cs

@ -7,6 +7,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
public XamlIlClrPropertyInfoEmitter ClrPropertyEmitter { get; }
public XamlIlPropertyInfoAccessorFactoryEmitter AccessorFactoryEmitter { get; }
public XamlIlTrampolineBuilder TrampolineBuilder { get; }
public AvaloniaXamlIlCompilerConfiguration(IXamlTypeSystem typeSystem,
IXamlAssembly defaultAssembly,
@ -15,13 +16,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
XamlValueConverter customValueConverter,
XamlIlClrPropertyInfoEmitter clrPropertyEmitter,
XamlIlPropertyInfoAccessorFactoryEmitter accessorFactoryEmitter,
XamlIlTrampolineBuilder trampolineBuilder,
IXamlIdentifierGenerator identifierGenerator = null)
: base(typeSystem, defaultAssembly, typeMappings, xmlnsMappings, customValueConverter, identifierGenerator)
{
ClrPropertyEmitter = clrPropertyEmitter;
AccessorFactoryEmitter = accessorFactoryEmitter;
TrampolineBuilder = trampolineBuilder;
AddExtra(ClrPropertyEmitter);
AddExtra(AccessorFactoryEmitter);
AddExtra(TrampolineBuilder);
}
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs

@ -201,7 +201,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (type.Equals(types.Classes))
{
var classes = text.Split(' ');
var classNodes = classes.Select(c => new XamlAstTextNode(node, c, types.XamlIlTypes.String)).ToArray();
var classNodes = classes.Select(c => new XamlAstTextNode(node, c, type: types.XamlIlTypes.String)).ToArray();
result = new AvaloniaXamlIlAvaloniaListConstantAstNode(node, types, types.Classes, types.XamlIlTypes.String, classNodes);
return true;

4
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs

@ -77,7 +77,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
throw new XamlParseException($"Cannot find '{property.Property}' on '{type}", node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, property.Value, context.Configuration.WellKnownTypes.String),
new XamlAstTextNode(node, property.Value, type: context.Configuration.WellKnownTypes.String),
targetProperty.PropertyType, out var typedValue))
throw new XamlParseException(
$"Cannot convert '{property.Value}' to '{targetProperty.PropertyType.GetFqn()}",
@ -118,7 +118,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
.GetAvaloniaPropertyType(targetPropertyField, context.GetAvaloniaTypes(), node);
if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context,
new XamlAstTextNode(node, attachedProperty.Value, context.Configuration.WellKnownTypes.String),
new XamlAstTextNode(node, attachedProperty.Value, type: context.Configuration.WellKnownTypes.String),
targetPropertyType, out var typedValue))
throw new XamlParseException(
$"Cannot convert '{attachedProperty.Value}' to '{targetPropertyType.GetFqn()}",

6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -20,10 +20,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlMethod AvaloniaObjectBindMethod { get; }
public IXamlMethod AvaloniaObjectSetValueMethod { get; }
public IXamlType IDisposable { get; }
public IXamlType ICommand { get; }
public XamlTypeWellKnownTypes XamlIlTypes { get; }
public XamlLanguageTypeMappings XamlIlMappings { get; }
public IXamlType Transitions { get; }
public IXamlType AssignBindingAttribute { get; }
public IXamlType DependsOnAttribute { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType IStyledElement { get; }
@ -85,6 +87,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IBrush { get; }
public IXamlType ImmutableSolidColorBrush { get; }
public IXamlConstructor ImmutableSolidColorBrushConstructorColor { get; }
public IXamlType TypeUtilities { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
@ -98,8 +101,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
BindingPriority = cfg.TypeSystem.GetType("Avalonia.Data.BindingPriority");
IBinding = cfg.TypeSystem.GetType("Avalonia.Data.IBinding");
IDisposable = cfg.TypeSystem.GetType("System.IDisposable");
ICommand = cfg.TypeSystem.GetType("System.Windows.Input.ICommand");
Transitions = cfg.TypeSystem.GetType("Avalonia.Animation.Transitions");
AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, cfg.WellKnownTypes.Object);
@ -187,6 +192,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
IBrush = cfg.TypeSystem.GetType("Avalonia.Media.IBrush");
ImmutableSolidColorBrush = cfg.TypeSystem.GetType("Avalonia.Media.Immutable.ImmutableSolidColorBrush");
ImmutableSolidColorBrushConstructorColor = ImmutableSolidColorBrush.GetConstructor(new List<IXamlType> { UInt });
TypeUtilities = cfg.TypeSystem.GetType("Avalonia.Utilities.TypeUtilities");
}
}

202
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs

@ -14,7 +14,7 @@ using XamlX.Emit;
using XamlX.IL;
using Avalonia.Utilities;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContext<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
using XamlIlEmitContext = XamlX.Emit.XamlEmitContextWithLocals<XamlX.IL.IXamlILEmitter, XamlX.IL.XamlILNodeEmitResult>;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
@ -32,6 +32,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
selfType,
bindingPath.Path);
transformed = TransformForTargetTyping(transformed, context);
bindingResultType = transformed.BindingResultType;
binding.Arguments[0] = transformed;
}
@ -54,6 +56,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
selfType,
bindingPathNode.Path);
transformed = TransformForTargetTyping(transformed, context);
bindingResultType = transformed.BindingResultType;
bindingPathAssignment.Values[0] = transformed;
}
@ -66,7 +70,32 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
return bindingResultType;
}
private static IXamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
private static XamlIlBindingPathNode TransformForTargetTyping(XamlIlBindingPathNode transformed, AstTransformationContext context)
{
if (context.ParentNodes().OfType<XamlPropertyAssignmentNode>().FirstOrDefault().Property.Getter.ReturnType == context.GetAvaloniaTypes().ICommand
&& transformed.Elements[transformed.Elements.Count - 1] is XamlIlClrMethodPathElementNode method)
{
IXamlMethod executeMethod = method.Method;
IXamlMethod canExecuteMethod = executeMethod.DeclaringType.FindMethod(new FindMethodMethodSignature($"Can{executeMethod.Name}", context.Configuration.WellKnownTypes.Boolean, context.Configuration.WellKnownTypes.Object));
List<string> dependsOnProperties = new();
if (canExecuteMethod is not null)
{
foreach (var attr in canExecuteMethod.CustomAttributes)
{
if (attr.Type == context.GetAvaloniaTypes().DependsOnAttribute)
{
dependsOnProperties.Add((string)attr.Parameters[0]);
}
}
}
transformed.Elements.RemoveAt(transformed.Elements.Count - 1);
transformed.Elements.Add(new XamlIlClrMethodAsCommandPathElementNode(context.GetAvaloniaTypes().ICommand, executeMethod, canExecuteMethod, dependsOnProperties));
}
return transformed;
}
private static XamlIlBindingPathNode TransformBindingPath(AstTransformationContext context, IXamlLineInfo lineInfo, Func<IXamlType> startTypeResolver, IXamlType selfType, IEnumerable<BindingExpressionGrammar.INode> bindingExpression)
{
List<IXamlIlBindingPathElementNode> transformNodes = new List<IXamlIlBindingPathElementNode>();
List<IXamlIlBindingPathElementNode> nodes = new List<IXamlIlBindingPathElementNode>();
@ -126,16 +155,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
nodes.Add(new XamlIlAvaloniaPropertyPropertyPathElementNode(avaloniaPropertyFieldMaybe,
XamlIlAvaloniaPropertyHelper.GetAvaloniaPropertyType(avaloniaPropertyFieldMaybe, context.GetAvaloniaTypes(), lineInfo)));
}
else
else if (GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName) is IXamlProperty clrProperty)
{
var clrProperty = GetAllDefinedProperties(targetType).FirstOrDefault(p => p.Name == propName.PropertyName);
if (clrProperty is null)
{
throw new XamlX.XamlParseException($"Unable to resolve property of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
nodes.Add(new XamlIlClrPropertyPathElementNode(clrProperty));
}
else if (GetAllDefinedMethods(targetType).FirstOrDefault(m => m.Name == propName.PropertyName) is IXamlMethod method)
{
nodes.Add(new XamlIlClrMethodPathElementNode(method, context.Configuration.WellKnownTypes.Delegate));
}
else
{
throw new XamlX.XamlParseException($"Unable to resolve property or method of name '{propName.PropertyName}' on type '{targetType}'.", lineInfo);
}
break;
}
case BindingExpressionGrammar.IndexerNode indexer:
@ -284,6 +315,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
static IEnumerable<IXamlMethod> GetAllDefinedMethods(IXamlType type)
{
foreach (var t in TraverseTypeHierarchy(type))
{
foreach (var m in t.Methods)
{
yield return m;
}
}
}
static IEnumerable<IXamlType> TraverseTypeHierarchy(IXamlType type)
{
if (type.IsInterface)
@ -538,6 +580,131 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public IXamlType Type => _property.Getter?.ReturnType ?? _property.Setter?.Parameters[0];
}
class XamlIlClrMethodPathElementNode : IXamlIlBindingPathElementNode
{
public XamlIlClrMethodPathElementNode(IXamlMethod method, IXamlType systemDelegateType)
{
Method = method;
Type = systemDelegateType;
}
public IXamlMethod Method { get; }
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
IXamlTypeBuilder<IXamlILEmitter> newDelegateTypeBuilder = null;
IXamlType specificDelegateType;
if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count == 0)
{
specificDelegateType = context.Configuration.TypeSystem
.GetType("System.Action");
}
else if (Method.ReturnType == context.Configuration.WellKnownTypes.Void && Method.Parameters.Count <= 16)
{
specificDelegateType = context.Configuration.TypeSystem
.GetType($"System.Action`{Method.Parameters.Count}")
.MakeGenericType(Method.Parameters);
}
else if (Method.Parameters.Count <= 16)
{
List<IXamlType> genericParameters = new();
genericParameters.AddRange(Method.Parameters);
genericParameters.Add(Method.ReturnType);
specificDelegateType = context.Configuration.TypeSystem
.GetType($"System.Func`{Method.Parameters.Count + 1}")
.MakeGenericType(genericParameters);
}
else
{
// In this case, we need to emit our own delegate type.
string delegateTypeName = context.Configuration.IdentifierGenerator.GenerateIdentifierPart();
specificDelegateType = newDelegateTypeBuilder = context.DefineDelegateSubType(delegateTypeName, Method.ReturnType, Method.Parameters);
}
codeGen
.Ldtoken(Method)
.Ldtoken(specificDelegateType)
.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Method"));
newDelegateTypeBuilder?.CreateType();
}
}
class XamlIlClrMethodAsCommandPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlMethod _executeMethod;
private readonly IXamlMethod _canExecuteMethod;
private readonly IReadOnlyList<string> _dependsOnProperties;
public XamlIlClrMethodAsCommandPathElementNode(IXamlType iCommandType, IXamlMethod executeMethod, IXamlMethod canExecuteMethod, IReadOnlyList<string> dependsOnProperties)
{
Type = iCommandType;
_executeMethod = executeMethod;
_canExecuteMethod = canExecuteMethod;
_dependsOnProperties = dependsOnProperties;
}
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var trampolineBuilder = context.Configuration.GetExtra<XamlIlTrampolineBuilder>();
var objectType = context.Configuration.WellKnownTypes.Object;
codeGen
.Ldstr(_executeMethod.Name)
.Ldnull()
.Ldftn(trampolineBuilder.EmitCommandExecuteTrampoline(context, _executeMethod))
.Newobj(context.Configuration.TypeSystem.GetType("System.Action`2")
.MakeGenericType(objectType, objectType)
.GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));
if (_canExecuteMethod is null)
{
codeGen.Ldnull();
}
else
{
codeGen
.Ldnull()
.Ldftn(trampolineBuilder.EmitCommandCanExecuteTrampoline(context, _canExecuteMethod))
.Newobj(context.Configuration.TypeSystem.GetType("System.Func`3")
.MakeGenericType(objectType, objectType, context.Configuration.WellKnownTypes.Boolean)
.GetConstructor(new() { objectType, context.Configuration.TypeSystem.GetType("System.IntPtr") }));
}
if (_dependsOnProperties is { Count:> 1 })
{
using var dependsOnPropertiesArray = context.GetLocalOfType(context.Configuration.WellKnownTypes.String.MakeArrayType(1));
codeGen
.Ldc_I4(_dependsOnProperties.Count)
.Newarr(context.Configuration.WellKnownTypes.String)
.Stloc(dependsOnPropertiesArray.Local);
for (int i = 0; i < _dependsOnProperties.Count; i++)
{
codeGen
.Ldloc(dependsOnPropertiesArray.Local)
.Ldc_I4(i)
.Ldstr(_dependsOnProperties[i])
.Stelem_ref();
}
codeGen.Ldloc(dependsOnPropertiesArray.Local);
}
else
{
codeGen.Ldnull();
}
codeGen
.EmitCall(context.GetAvaloniaTypes()
.CompiledBindingPathBuilder.FindMethod(m => m.Name == "Command"));
}
}
class XamlIlClrIndexerPathElementNode : IXamlIlBindingPathElementNode
{
private readonly IXamlProperty _property;
@ -660,10 +827,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstLocalsEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private readonly List<IXamlIlBindingPathElementNode> _transformElements;
private readonly List<IXamlIlBindingPathElementNode> _elements;
public XamlIlBindingPathNode(IXamlLineInfo lineInfo,
IXamlType bindingPathType,
@ -672,16 +838,18 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
Type = new XamlAstClrTypeReference(lineInfo, bindingPathType, false);
_transformElements = transformElements;
_elements = elements;
Elements = elements;
}
public IXamlType BindingResultType
=> _transformElements.Count > 0
? _transformElements[0].Type
: _elements[_elements.Count - 1].Type;
: Elements[Elements.Count - 1].Type;
public IXamlAstTypeReference Type { get; }
public List<IXamlIlBindingPathElementNode> Elements { get; }
public XamlILNodeEmitResult Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
var types = context.GetAvaloniaTypes();
@ -692,7 +860,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
transform.Emit(context, codeGen);
}
foreach (var element in _elements)
foreach (var element in Elements)
{
element.Emit(context, codeGen);
}
@ -710,11 +878,11 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_transformElements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
}
}
for (int i = 0; i < _elements.Count; i++)
for (int i = 0; i < Elements.Count; i++)
{
if (_elements[i] is IXamlAstNode ast)
if (Elements[i] is IXamlAstNode ast)
{
_elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
Elements[i] = (IXamlIlBindingPathElementNode)ast.Visit(visitor);
}
}
}

116
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlTrampolineBuilder.cs

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using XamlX.Emit;
using XamlX.IL;
using XamlX.TypeSystem;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using System.Reflection.Emit;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
internal class XamlIlTrampolineBuilder
{
private IXamlTypeBuilder<IXamlILEmitter> _builder;
private Dictionary<string, IXamlMethod> _trampolines = new();
public XamlIlTrampolineBuilder(IXamlTypeBuilder<IXamlILEmitter> builder)
{
_builder = builder;
}
public IXamlMethod EmitCommandExecuteTrampoline(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlMethod executeMethod)
{
Debug.Assert(!executeMethod.IsStatic);
string methodName = $"{executeMethod.DeclaringType.GetFqn()}+{executeMethod.Name}_{executeMethod.Parameters.Count}!CommandExecuteTrampoline";
if (_trampolines.TryGetValue(methodName, out var method))
{
return method;
}
var trampoline = _builder.DefineMethod(
context.Configuration.WellKnownTypes.Void,
new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object },
methodName,
true,
true,
false);
var gen = trampoline.Generator;
if (executeMethod.DeclaringType.IsValueType)
{
gen.Ldarg_0()
.Unbox(executeMethod.DeclaringType);
}
else
{
gen.Ldarg_0()
.Castclass(executeMethod.DeclaringType);
}
if (executeMethod.Parameters.Count != 0)
{
Debug.Assert(executeMethod.Parameters.Count == 1);
if (executeMethod.Parameters[0] != context.Configuration.WellKnownTypes.Object)
{
var convertedValue = gen.DefineLocal(context.Configuration.WellKnownTypes.Object);
gen.Ldtype(executeMethod.Parameters[0])
.Ldarg(1)
.EmitCall(context.Configuration.WellKnownTypes.CultureInfo.FindMethod(m => m.Name == "get_CurrentCulture"))
.Ldloca(convertedValue)
.EmitCall(
context.GetAvaloniaTypes().TypeUtilities.FindMethod(m => m.Name == "TryConvert"),
swallowResult: true)
.Ldloc(convertedValue)
.Unbox_Any(executeMethod.Parameters[0]);
}
else
{
gen.Ldarg(1);
}
}
gen.EmitCall(executeMethod, swallowResult: true);
gen.Ret();
_trampolines.Add(methodName, trampoline);
return trampoline;
}
public IXamlMethod EmitCommandCanExecuteTrampoline(XamlEmitContext<IXamlILEmitter, XamlILNodeEmitResult> context, IXamlMethod canExecuteMethod)
{
Debug.Assert(!canExecuteMethod.IsStatic);
Debug.Assert(canExecuteMethod.Parameters.Count == 1);
Debug.Assert(canExecuteMethod.ReturnType == context.Configuration.WellKnownTypes.Boolean);
string methodName = $"{canExecuteMethod.DeclaringType.GetFqn()}+{canExecuteMethod.Name}!CommandCanExecuteTrampoline";
if (_trampolines.TryGetValue(methodName, out var method))
{
return method;
}
var trampoline = _builder.DefineMethod(
context.Configuration.WellKnownTypes.Boolean,
new[] { context.Configuration.WellKnownTypes.Object, context.Configuration.WellKnownTypes.Object },
methodName,
true,
true,
false);
if (canExecuteMethod.DeclaringType.IsValueType)
{
trampoline.Generator
.Ldarg_0()
.Unbox(canExecuteMethod.DeclaringType);
}
else
{
trampoline.Generator
.Ldarg_0()
.Castclass(canExecuteMethod.DeclaringType);
}
trampoline.Generator
.Ldarg(1)
.Emit(OpCodes.Tailcall)
.EmitCall(canExecuteMethod)
.Ret();
_trampolines.Add(methodName, trampoline);
return trampoline;
}
}
}

2
src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github

@ -1 +1 @@
Subproject commit 8e20d65eb5f1efbae08e49b18f39bfdce32df7b3
Subproject commit daaac590e078967b78045f74c38ef046d00d8582

2
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -18,8 +18,10 @@
<Compile Include="MarkupExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindingExtension.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\ArrayElementPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CommandAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\CompiledBindingPath.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\FindVisualAncestorNode.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\MethodAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\ObservableStreamPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorFactory.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorPlugin.cs" />

156
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs

@ -0,0 +1,156 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Reflection;
using System.Text;
using System.Windows.Input;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
using Avalonia.Utilities;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class CommandAccessorPlugin : IPropertyAccessorPlugin
{
private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessorPlugin(Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
{
_execute = execute;
_canExecute = canExecute;
_dependsOnProperties = dependsOnProperties;
}
public bool Match(object obj, string propertyName)
{
throw new InvalidOperationException("The CommandAccessorPlugin does not support dynamic matching");
}
public IPropertyAccessor Start(WeakReference<object> reference, string propertyName)
{
return new CommandAccessor(reference, _execute, _canExecute, _dependsOnProperties);
}
private sealed class CommandAccessor : PropertyAccessorBase
{
private readonly WeakReference<object> _reference;
private Command _command;
private readonly ISet<string> _dependsOnProperties;
public CommandAccessor(WeakReference<object> reference, Action<object, object> execute, Func<object, object, bool> canExecute, ISet<string> dependsOnProperties)
{
Contract.Requires<ArgumentNullException>(reference != null);
_reference = reference;
_dependsOnProperties = dependsOnProperties;
_command = new Command(reference, execute, canExecute);
}
public override object Value => _reference.TryGetTarget(out var _) ? _command : null;
private void RaiseCanExecuteChanged()
{
_command.RaiseCanExecuteChanged();
}
private sealed class Command : ICommand
{
private readonly WeakReference<object> _target;
private readonly Action<object, object> _execute;
private readonly Func<object, object, bool> _canExecute;
public event EventHandler CanExecuteChanged;
public Command(WeakReference<object> target, Action<object, object> execute, Func<object, object, bool> canExecute)
{
_target = target;
_execute = execute;
_canExecute = canExecute;
}
public void RaiseCanExecuteChanged()
{
Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
, Threading.DispatcherPriority.Input);
}
public bool CanExecute(object parameter)
{
if (_target.TryGetTarget(out var target))
{
if (_canExecute == null)
{
return true;
}
return _canExecute(target, parameter);
}
return false;
}
public void Execute(object parameter)
{
if (_target.TryGetTarget(out var target))
{
_execute(target, parameter);
}
}
}
public override Type PropertyType => typeof(ICommand);
public override bool SetValue(object value, BindingPriority priority)
{
return false;
}
void OnNotifyPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (string.IsNullOrEmpty(e.PropertyName) || _dependsOnProperties.Contains(e.PropertyName))
{
RaiseCanExecuteChanged();
}
}
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
}
protected override void UnsubscribeCore()
{
if (_dependsOnProperties is { Count: > 0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
WeakEventHandlerManager.Unsubscribe<PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
private void SendCurrentValue()
{
try
{
var value = Value;
PublishValue(value);
}
catch { }
}
private void SubscribeToChanges()
{
if (_dependsOnProperties is { Count:>0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
WeakEventHandlerManager.Subscribe<INotifyPropertyChanged, PropertyChangedEventArgs, InpcPropertyAccessor>(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
}
}
}
}
}

48
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
@ -36,6 +37,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case PropertyElement prop:
node = new PropertyAccessorNode(prop.Property.Name, enableValidation, new PropertyInfoAccessorPlugin(prop.Property, prop.AccessorFactory));
break;
case MethodAsCommandElement methodAsCommand:
node = new PropertyAccessorNode(methodAsCommand.MethodName, enableValidation, new CommandAccessorPlugin(methodAsCommand.ExecuteMethod, methodAsCommand.CanExecuteMethod, methodAsCommand.DependsOnProperties));
break;
case MethodAsDelegateElement methodAsDelegate:
node = new PropertyAccessorNode(methodAsDelegate.Method.Name, enableValidation, new MethodAccessorPlugin(methodAsDelegate.Method, methodAsDelegate.DelegateType));
break;
case ArrayElementPathElement arr:
node = new PropertyAccessorNode(CommonPropertyNames.IndexerName, enableValidation, new ArrayElementPlugin(arr.Indices, arr.ElementType));
break;
@ -92,6 +99,18 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder Method(RuntimeMethodHandle handle, RuntimeTypeHandle delegateType)
{
_elements.Add(new MethodAsDelegateElement(handle, delegateType));
return this;
}
public CompiledBindingPathBuilder Command(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnProperties)
{
_elements.Add(new MethodAsCommandElement(methodName, executeHelper, canExecuteHelper, dependsOnProperties ?? Array.Empty<string>()));
return this;
}
public CompiledBindingPathBuilder StreamTask<T>()
{
_elements.Add(new TaskStreamPathElement<T>());
@ -178,6 +197,35 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
=> _isFirstElement ? Property.Name : $".{Property.Name}";
}
internal class MethodAsDelegateElement : ICompiledBindingPathElement
{
public MethodAsDelegateElement(RuntimeMethodHandle method, RuntimeTypeHandle delegateType)
{
Method = (MethodInfo)MethodBase.GetMethodFromHandle(method);
DelegateType = Type.GetTypeFromHandle(delegateType);
}
public MethodInfo Method { get; }
public Type DelegateType { get; }
}
internal class MethodAsCommandElement : ICompiledBindingPathElement
{
public MethodAsCommandElement(string methodName, Action<object, object> executeHelper, Func<object, object, bool> canExecuteHelper, string[] dependsOnElements)
{
MethodName = methodName;
ExecuteMethod = executeHelper;
CanExecuteMethod = canExecuteHelper;
DependsOnProperties = new HashSet<string>(dependsOnElements);
}
public string MethodName { get; }
public Action<object, object> ExecuteMethod { get; }
public Func<object, object, bool> CanExecuteMethod { get; }
public HashSet<string> DependsOnProperties { get; }
}
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
{
IStreamPlugin CreatePlugin();

74
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/MethodAccessorPlugin.cs

@ -0,0 +1,74 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Avalonia.Data;
using Avalonia.Data.Core.Plugins;
#nullable enable
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
internal class MethodAccessorPlugin : IPropertyAccessorPlugin
{
private MethodInfo _method;
private readonly Type _delegateType;
public MethodAccessorPlugin(MethodInfo method, Type delegateType)
{
_method = method;
_delegateType = delegateType;
}
public bool Match(object obj, string propertyName)
{
throw new InvalidOperationException("The MethodAccessorPlugin does not support dynamic matching");
}
public IPropertyAccessor Start(WeakReference<object?> reference, string propertyName)
{
Debug.Assert(_method.Name == propertyName);
return new Accessor(reference, _method, _delegateType);
}
private sealed class Accessor : PropertyAccessorBase
{
public Accessor(WeakReference<object?> reference, MethodInfo method, Type delegateType)
{
_ = reference ?? throw new ArgumentNullException(nameof(reference));
_ = method ?? throw new ArgumentNullException(nameof(method));
PropertyType = delegateType;
if (method.IsStatic)
{
Value = method.CreateDelegate(PropertyType);
}
else if (reference.TryGetTarget(out var target))
{
Value = method.CreateDelegate(PropertyType, target);
}
}
public override Type? PropertyType { get; }
public override object? Value { get; }
public override bool SetValue(object? value, BindingPriority priority) => false;
protected override void SubscribeCore()
{
try
{
PublishValue(Value);
}
catch { }
}
protected override void UnsubscribeCore()
{
}
}
}
}

5
src/Windows/Avalonia.Direct2D1/Properties/AssemblyInfo.cs

@ -1,9 +1,4 @@
using System.Runtime.CompilerServices;
using Avalonia.Platform;
using Avalonia.Direct2D1;
[assembly: ExportRenderingSubsystem(OperatingSystemType.WinNT, 1, "Direct2D1", typeof(Direct2D1Platform), nameof(Direct2D1Platform.Initialize),
typeof(Direct2DChecker))]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.RenderTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]
[assembly: InternalsVisibleTo("Avalonia.Direct2D1.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c1bba1142285fe0419326fb25866ba62c47e6c2b5c1ab0c95b46413fad375471232cb81706932e1cef38781b9ebd39d5100401bacb651c6c5bbf59e571e81b3bc08d2a622004e08b1a6ece82a7e0b9857525c86d2b95fab4bc3dce148558d7f3ae61aa3a234086902aeface87d9dfdd32b9d2fe3c6dd4055b5ab4b104998bd87")]

4
src/Windows/Avalonia.Win32/Properties/AssemblyInfo.cs

@ -1,4 +0,0 @@
using Avalonia.Platform;
using Avalonia.Win32;
[assembly: ExportWindowingSubsystem(OperatingSystemType.WinNT, 1, "Win32", typeof(Win32Platform), nameof(Win32Platform.Initialize))]

1
tests/Avalonia.DesignerSupport.TestApp/Avalonia.DesignerSupport.TestApp.csproj

@ -15,7 +15,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DesignerSupport\Avalonia.DesignerSupport.csproj" />
<ProjectReference Include="..\..\src\Avalonia.DesktopRuntime\Avalonia.DesktopRuntime.csproj" />
<ProjectReference Include="..\..\src\Avalonia.OpenGL\Avalonia.OpenGL.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />

1
tests/Avalonia.Direct2D1.RenderTests/Avalonia.Direct2D1.RenderTests.csproj

@ -9,7 +9,6 @@
<EmbeddedResource Include="..\Avalonia.RenderTests\**\*.ttf" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DesktopRuntime\Avalonia.DesktopRuntime.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />

22
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_Method.cs

@ -23,8 +23,8 @@ namespace Avalonia.Markup.UnitTests.Parsers
public static void StaticMethod() { }
public static void TooManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { }
public static int TooManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1;
public static void ManyParameters(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8, int a9) { }
public static int ManyParametersWithReturnType(int a1, int a2, int a3, int a4, int a5, int a6, int a7, int a8) => 1;
}
[Fact]
@ -44,6 +44,8 @@ namespace Avalonia.Markup.UnitTests.Parsers
[InlineData(nameof(TestObject.MethodWithReturn), typeof(Func<int>))]
[InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func<int, int>))]
[InlineData(nameof(TestObject.StaticMethod), typeof(Action))]
[InlineData(nameof(TestObject.ManyParameters), typeof(Action<int, int, int, int, int, int, int, int, int>))]
[InlineData(nameof(TestObject.ManyParametersWithReturnType), typeof(Func<int, int, int, int, int, int, int, int, int>))]
public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType)
{
var data = new TestObject();
@ -68,21 +70,5 @@ namespace Avalonia.Markup.UnitTests.Parsers
GC.KeepAlive(data);
}
[Theory]
[InlineData(nameof(TestObject.TooManyParameters))]
[InlineData(nameof(TestObject.TooManyParametersWithReturnType))]
public async Task Should_Return_Error_Notification_If_Too_Many_Parameters(string methodName)
{
var data = new TestObject();
var observer = ExpressionObserverBuilder.Build(data, methodName);
var result = await observer.Take(1);
Assert.IsType<BindingNotification>(result);
Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType);
GC.KeepAlive(data);
}
}
}

231
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Reactive.Subjects;
using System.Text;
@ -9,6 +10,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Input;
using Avalonia.Markup.Data;
using Avalonia.Media;
using Avalonia.UnitTests;
@ -1062,6 +1064,174 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void SupportsMethodBindingAsDelegate()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodDataContext'>
<StackPanel>
<ContentControl Content='{CompiledBinding Action}' Name='action' />
<ContentControl Content='{CompiledBinding Func}' Name='func' />
<ContentControl Content='{CompiledBinding Action16}' Name='action16' />
<ContentControl Content='{CompiledBinding Func16}' Name='func16' />
<ContentControl Content='{CompiledBinding CustomDelegateTypeVoid}' Name='customvoid' />
<ContentControl Content='{CompiledBinding CustomDelegateTypeInt}' Name='customint' />
</StackPanel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.DataContext = new MethodDataContext();
Assert.IsAssignableFrom(typeof(Action), window.FindControl<ContentControl>("action").Content);
Assert.IsAssignableFrom(typeof(Func<int>), window.FindControl<ContentControl>("func").Content);
Assert.IsAssignableFrom(typeof(Action<int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int>), window.FindControl<ContentControl>("action16").Content);
Assert.IsAssignableFrom(typeof(Func<int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int>), window.FindControl<ContentControl>("func16").Content);
Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl<ContentControl>("customvoid").Content.GetType()));
Assert.True(typeof(Delegate).IsAssignableFrom(window.FindControl<ContentControl>("customint").Content.GetType()));
}
}
[Fact]
public void Binding_Method_To_Command_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{CompiledBinding Method}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new MethodAsCommandDataContext();
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal("Called", vm.Value);
}
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{CompiledBinding Method1}' CommandParameter='5'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new MethodAsCommandDataContext();
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal("Called 5", vm.Value);
}
}
[Fact]
public void Binding_Method_To_TextBlock_Text_Works()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<TextBlock Name='textBlock' Text='{CompiledBinding Method}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
var vm = new MethodAsCommandDataContext();
textBlock.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(textBlock.Text);
}
}
[Theory]
[InlineData(null, "Not called")]
[InlineData("A", "Do A")]
public void Binding_Method_With_Parameter_To_Command_CanExecute(object commandParameter, string result)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{CompiledBinding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneTime}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new MethodAsCommandDataContext()
{
Parameter = commandParameter
};
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal(vm.Value, result);
}
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_CanExecute_DependsOn()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;assembly=Avalonia.Markup.Xaml.UnitTests'
x:DataType='local:MethodAsCommandDataContext'>
<Button Name='button' Command='{CompiledBinding Do}' CommandParameter='{CompiledBinding Parameter, Mode=OneWay}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new MethodAsCommandDataContext()
{
Parameter = null,
};
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
Assert.Equal(button.IsEffectivelyEnabled, false);
vm.Parameter = true;
Threading.Dispatcher.UIThread.RunJobs();
Assert.Equal(button.IsEffectivelyEnabled, true);
}
}
void Throws(string type, Action cb)
{
try
@ -1078,6 +1248,16 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
void ThrowsXamlParseException(Action cb) => Throws("XamlParseException", cb);
void ThrowsXamlTransformException(Action cb) => Throws("XamlTransformException", cb);
static void PerformClick(Button button)
{
button.RaiseEvent(new KeyEventArgs
{
RoutedEvent = InputElement.KeyDownEvent,
Key = Input.Key.Enter,
});
}
}
public interface INonIntegerIndexer
@ -1151,4 +1331,55 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
}
public class MethodDataContext
{
public void Action() { }
public int Func() => 1;
public void Action16(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16) { }
public int Func16(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16) => i;
public void CustomDelegateTypeVoid(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16, int i17) { }
public int CustomDelegateTypeInt(int i, int i2, int i3, int i4, int i5, int i6, int i7, int i8, int i9, int i10, int i11, int i12, int i13, int i14, int i15, int i16, int i17) => i;
}
public class MethodAsCommandDataContext : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Method() => Value = "Called";
public string Method1(int i) => Value = $"Called {i}";
public string Method2(int i, int j) => Value = $"Called {i},{j}";
public string Value { get; private set; } = "Not called";
object _parameter;
public object Parameter
{
get
{
return _parameter;
}
set
{
if (_parameter == value)
{
return;
}
_parameter = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Parameter)));
}
}
public void Do(object parameter)
{
Value = $"Do {parameter}";
}
[Metadata.DependsOn(nameof(Parameter))]
public bool CanDo(object parameter)
{
return ReferenceEquals(null, parameter) == false;
}
}
}

1
tests/Avalonia.Skia.RenderTests/Avalonia.Skia.RenderTests.csproj

@ -10,7 +10,6 @@
<EmbeddedResource Include="..\Avalonia.RenderTests\**\*.ttf" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.DesktopRuntime\Avalonia.DesktopRuntime.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml\Avalonia.Markup.Xaml.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup\Avalonia.Markup.csproj" />
<ProjectReference Include="..\..\src\Avalonia.Animation\Avalonia.Animation.csproj" />

Loading…
Cancel
Save