57 changed files with 916 additions and 375 deletions
@ -1,34 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public class DefaultRenderLayerFactory : IRenderLayerFactory |
|||
{ |
|||
private IPlatformRenderInterface _renderInterface; |
|||
|
|||
public DefaultRenderLayerFactory() |
|||
: this(AvaloniaLocator.Current.GetService<IPlatformRenderInterface>()) |
|||
{ |
|||
} |
|||
|
|||
public DefaultRenderLayerFactory(IPlatformRenderInterface renderInterface) |
|||
{ |
|||
_renderInterface = renderInterface; |
|||
} |
|||
|
|||
public IRenderTargetBitmapImpl CreateLayer( |
|||
IVisual layerRoot, |
|||
Size size, |
|||
double dpiX, |
|||
double dpiY) |
|||
{ |
|||
return _renderInterface.CreateRenderTargetBitmap( |
|||
(int)Math.Ceiling(size.Width), |
|||
(int)Math.Ceiling(size.Height), |
|||
dpiX, |
|||
dpiY); |
|||
} |
|||
} |
|||
} |
|||
@ -1,11 +0,0 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.VisualTree; |
|||
|
|||
namespace Avalonia.Rendering |
|||
{ |
|||
public interface IRenderLayerFactory |
|||
{ |
|||
IRenderTargetBitmapImpl CreateLayer(IVisual layerRoot, Size size, double dpiX, double dpiY); |
|||
} |
|||
} |
|||
@ -0,0 +1,43 @@ |
|||
using Avalonia.Utilities; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Globalization; |
|||
using System.Reflection; |
|||
using System.Text; |
|||
using System.Windows.Input; |
|||
|
|||
namespace Avalonia.Markup |
|||
{ |
|||
class AlwaysEnabledDelegateCommand : ICommand |
|||
{ |
|||
private readonly Delegate action; |
|||
|
|||
private ParameterInfo parameterInfo; |
|||
|
|||
public AlwaysEnabledDelegateCommand(Delegate action) |
|||
{ |
|||
this.action = action; |
|||
var parameters = action.Method.GetParameters(); |
|||
parameterInfo = parameters.Length == 0 ? null : parameters[0]; |
|||
} |
|||
|
|||
#pragma warning disable 0067
|
|||
public event EventHandler CanExecuteChanged; |
|||
#pragma warning restore 0067
|
|||
|
|||
public bool CanExecute(object parameter) => true; |
|||
|
|||
public void Execute(object parameter) |
|||
{ |
|||
if (parameterInfo == null) |
|||
{ |
|||
action.DynamicInvoke(); |
|||
} |
|||
else |
|||
{ |
|||
TypeUtilities.TryConvert(parameterInfo.ParameterType, parameter, CultureInfo.CurrentCulture, out object convertedParameter); |
|||
action.DynamicInvoke(convertedParameter); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text; |
|||
using Avalonia.Data; |
|||
using System.Reflection; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Markup.Data.Plugins |
|||
{ |
|||
class MethodAccessorPlugin : IPropertyAccessorPlugin |
|||
{ |
|||
public bool Match(object obj, string methodName) |
|||
=> obj.GetType().GetRuntimeMethods().Any(x => x.Name == methodName); |
|||
|
|||
public IPropertyAccessor Start(WeakReference reference, string methodName) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(reference != null); |
|||
Contract.Requires<ArgumentNullException>(methodName != null); |
|||
|
|||
var instance = reference.Target; |
|||
var method = instance.GetType().GetRuntimeMethods().FirstOrDefault(x => x.Name == methodName); |
|||
|
|||
if (method != null) |
|||
{ |
|||
if (method.GetParameters().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(method)); |
|||
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); |
|||
} |
|||
|
|||
return new Accessor(reference, method); |
|||
} |
|||
else |
|||
{ |
|||
var message = $"Could not find CLR method '{methodName}' on '{instance}'"; |
|||
var exception = new MissingMemberException(message); |
|||
return new PropertyError(new BindingNotification(exception, BindingErrorType.Error)); |
|||
} |
|||
} |
|||
|
|||
private class Accessor : PropertyAccessorBase |
|||
{ |
|||
public Accessor(WeakReference reference, MethodInfo method) |
|||
{ |
|||
Contract.Requires<ArgumentNullException>(reference != null); |
|||
Contract.Requires<ArgumentNullException>(method != null); |
|||
|
|||
var paramTypes = method.GetParameters().Select(param => param.ParameterType).ToArray(); |
|||
var returnType = method.ReturnType; |
|||
|
|||
if (returnType == typeof(void)) |
|||
{ |
|||
if (paramTypes.Length == 0) |
|||
{ |
|||
PropertyType = typeof(Action); |
|||
} |
|||
else |
|||
{ |
|||
PropertyType = Type.GetType($"System.Action`{paramTypes.Length}").MakeGenericType(paramTypes); |
|||
} |
|||
} |
|||
else |
|||
{ |
|||
var genericTypeParameters = paramTypes.Concat(new[] { returnType }).ToArray(); |
|||
PropertyType = Type.GetType($"System.Func`{genericTypeParameters.Length}").MakeGenericType(genericTypeParameters); |
|||
} |
|||
|
|||
Value = method.IsStatic ? method.CreateDelegate(PropertyType) : method.CreateDelegate(PropertyType, reference.Target); |
|||
} |
|||
|
|||
public override Type PropertyType { get; } |
|||
|
|||
public override object Value { get; } |
|||
|
|||
public override bool SetValue(object value, BindingPriority priority) => false; |
|||
|
|||
protected override void SubscribeCore(IObserver<object> observer) |
|||
{ |
|||
try |
|||
{ |
|||
Observer.OnNext(Value); |
|||
} |
|||
catch { } |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
|
|||
namespace Avalonia.Direct2D1 |
|||
{ |
|||
public interface ILayerFactory |
|||
{ |
|||
IRenderTargetBitmapImpl CreateLayer(Size size); |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
using System; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Rendering; |
|||
using SharpDX; |
|||
using SharpDX.Direct2D1; |
|||
using SharpDX.WIC; |
|||
using D2DBitmap = SharpDX.Direct2D1.Bitmap; |
|||
using DirectWriteFactory = SharpDX.DirectWrite.Factory; |
|||
|
|||
namespace Avalonia.Direct2D1.Media.Imaging |
|||
{ |
|||
public class D2DRenderTargetBitmapImpl : D2DBitmapImpl, IRenderTargetBitmapImpl, ILayerFactory |
|||
{ |
|||
private readonly DirectWriteFactory _dwriteFactory; |
|||
private readonly BitmapRenderTarget _target; |
|||
|
|||
public D2DRenderTargetBitmapImpl( |
|||
ImagingFactory imagingFactory, |
|||
DirectWriteFactory dwriteFactory, |
|||
BitmapRenderTarget target) |
|||
: base(imagingFactory, target.Bitmap) |
|||
{ |
|||
_dwriteFactory = dwriteFactory; |
|||
_target = target; |
|||
} |
|||
|
|||
public override int PixelWidth => _target.PixelSize.Width; |
|||
public override int PixelHeight => _target.PixelSize.Height; |
|||
|
|||
public static D2DRenderTargetBitmapImpl CreateCompatible( |
|||
ImagingFactory imagingFactory, |
|||
DirectWriteFactory dwriteFactory, |
|||
SharpDX.Direct2D1.RenderTarget renderTarget, |
|||
Size size) |
|||
{ |
|||
var bitmapRenderTarget = new BitmapRenderTarget( |
|||
renderTarget, |
|||
CompatibleRenderTargetOptions.None, |
|||
new Size2F((float)size.Width, (float)size.Height)); |
|||
return new D2DRenderTargetBitmapImpl(imagingFactory, dwriteFactory, bitmapRenderTarget); |
|||
} |
|||
|
|||
public IDrawingContextImpl CreateDrawingContext(IVisualBrushRenderer visualBrushRenderer) |
|||
{ |
|||
return new DrawingContextImpl( |
|||
visualBrushRenderer, |
|||
this, |
|||
_target, |
|||
_dwriteFactory, |
|||
WicImagingFactory); |
|||
} |
|||
|
|||
public IRenderTargetBitmapImpl CreateLayer(Size size) |
|||
{ |
|||
return CreateCompatible(WicImagingFactory, _dwriteFactory, _target, size); |
|||
} |
|||
|
|||
public override void Dispose() |
|||
{ |
|||
_target.Dispose(); |
|||
} |
|||
|
|||
public override OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target) |
|||
{ |
|||
return new OptionalDispose<D2DBitmap>(_target.Bitmap, false); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
|
|||
namespace Avalonia.Direct2D1 |
|||
{ |
|||
public struct OptionalDispose<T> : IDisposable where T : IDisposable |
|||
{ |
|||
private readonly bool _dispose; |
|||
|
|||
public OptionalDispose(T value, bool dispose) |
|||
{ |
|||
Value = value; |
|||
_dispose = dispose; |
|||
} |
|||
|
|||
public T Value { get; } |
|||
|
|||
public void Dispose() |
|||
{ |
|||
if (_dispose) Value?.Dispose(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,87 @@ |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Data; |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Reactive.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.UnitTests.Data |
|||
{ |
|||
public class ExpressionObserverTests_Method |
|||
{ |
|||
private class TestObject |
|||
{ |
|||
public void MethodWithoutReturn() { } |
|||
|
|||
public int MethodWithReturn() => 0; |
|||
|
|||
public int MethodWithReturnAndParameters(int i) => i; |
|||
|
|||
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; |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Should_Get_Method() |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithoutReturn)); |
|||
var result = await observer.Take(1); |
|||
|
|||
Assert.NotNull(result); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
|
|||
[Theory] |
|||
[InlineData(nameof(TestObject.MethodWithoutReturn), typeof(Action))] |
|||
[InlineData(nameof(TestObject.MethodWithReturn), typeof(Func<int>))] |
|||
[InlineData(nameof(TestObject.MethodWithReturnAndParameters), typeof(Func<int, int>))] |
|||
[InlineData(nameof(TestObject.StaticMethod), typeof(Action))] |
|||
public async Task Should_Get_Method_WithCorrectDelegateType(string methodName, Type expectedType) |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, methodName); |
|||
var result = await observer.Take(1); |
|||
|
|||
Assert.IsType(expectedType, result); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
|
|||
[Fact] |
|||
public async Task Can_Call_Method_Returned_From_Observer() |
|||
{ |
|||
var data = new TestObject(); |
|||
var observer = new ExpressionObserver(data, nameof(TestObject.MethodWithReturnAndParameters)); |
|||
var result = await observer.Take(1); |
|||
|
|||
var callback = (Func<int, int>)result; |
|||
|
|||
Assert.Equal(1, callback(1)); |
|||
|
|||
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 = new ExpressionObserver(data, methodName); |
|||
var result = await observer.Take(1); |
|||
|
|||
Assert.IsType<BindingNotification>(result); |
|||
|
|||
Assert.Equal(BindingErrorType.Error, ((BindingNotification)result).ErrorType); |
|||
|
|||
GC.KeepAlive(data); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,105 @@ |
|||
// Copyright (c) The Avalonia Project. All rights reserved.
|
|||
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
|||
|
|||
using System.Reactive.Subjects; |
|||
using System.Windows.Input; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Input; |
|||
using Avalonia.UnitTests; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Markup.Xaml.UnitTests.Data |
|||
{ |
|||
public class BindingTests_Method |
|||
{ |
|||
[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.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'> |
|||
<Button Name='button' Command='{Binding Method}'/> |
|||
</Window>";
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var vm = new ViewModel(); |
|||
|
|||
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.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'> |
|||
<Button Name='button' Command='{Binding Method1}' CommandParameter='5'/> |
|||
</Window>";
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var button = window.FindControl<Button>("button"); |
|||
var vm = new ViewModel(); |
|||
|
|||
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.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'> |
|||
<TextBlock Name='textBlock' Text='{Binding Method}'/> |
|||
</Window>";
|
|||
var loader = new AvaloniaXamlLoader(); |
|||
var window = (Window)loader.Load(xaml); |
|||
var textBlock = window.FindControl<TextBlock>("textBlock"); |
|||
var vm = new ViewModel(); |
|||
|
|||
textBlock.DataContext = vm; |
|||
window.ApplyTemplate(); |
|||
|
|||
Assert.NotNull(textBlock.Text); |
|||
} |
|||
} |
|||
|
|||
static void PerformClick(Button button) |
|||
{ |
|||
button.RaiseEvent(new KeyEventArgs |
|||
{ |
|||
RoutedEvent = InputElement.KeyDownEvent, |
|||
Key = Input.Key.Enter, |
|||
}); |
|||
} |
|||
|
|||
private class ViewModel |
|||
{ |
|||
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"; |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue