Browse Source

Merge pull request #5038 from donandren/issues/compiledbinding

support cast in binding + some related small fixes for compiled binding
pull/5051/head
Dan Walmsley 6 years ago
committed by GitHub
parent
commit
ac681ec942
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 34
      src/Avalonia.Base/Data/Core/TypeCastNode.cs
  2. 32
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/XamlIlBindingPathHelper.cs
  3. 1
      src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
  4. 2
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  5. 51
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CompiledBindingPath.cs
  6. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs
  7. 89
      src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs
  8. 19
      src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs
  9. 2
      tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs
  10. 48
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  11. 229
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs

34
src/Avalonia.Base/Data/Core/TypeCastNode.cs

@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Avalonia.Data.Core
{
public class TypeCastNode : ExpressionNode
{
public override string Description => $"as {TargetType.FullName}";
public Type TargetType { get; }
public TypeCastNode(Type type)
{
TargetType = type;
}
protected virtual object Cast(object value)
{
return TargetType.IsInstanceOfType(value) ? value : null;
}
protected override void StartListeningCore(WeakReference<object> reference)
{
if (reference.TryGetTarget(out object target))
{
target = Cast(target);
reference = target == null ? NullReference : new WeakReference<object>(target);
}
base.StartListeningCore(reference);
}
}
}

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

@ -203,6 +203,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
.ParentNodes()
.OfType<XamlAstConstructableObjectNode>()
.Where(x => styledElementType.IsAssignableFrom(x.Type.GetClrType()))
.Skip(1)
.ElementAtOrDefault(ancestor.Level)
?.Type.GetClrType();
@ -242,6 +243,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
case RawSourceBindingExpressionNode rawSource:
nodes.Add(new RawSourcePathElementNode(rawSource.RawSource));
break;
case BindingExpressionGrammar.TypeCastNode typeCastNode:
var castType = GetType(typeCastNode.Namespace, typeCastNode.TypeName);
if (castType is null)
{
throw new XamlX.XamlParseException($"Unable to resolve cast to type {typeCastNode.Namespace}:{typeCastNode.TypeName} based on XAML tree.", lineInfo);
}
nodes.Add(new TypeCastPathElementNode(castType));
break;
}
}
@ -422,7 +433,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
codeGen.Ldtype(Type)
.Ldc_I4(_level)
.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "FindAncestor"));
.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "Ancestor"));
}
}
@ -608,10 +619,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private readonly IXamlAstValueNode _rawSource;
public RawSourcePathElementNode(IXamlAstValueNode rawSource)
:base(rawSource)
: base(rawSource)
{
_rawSource = rawSource;
}
public IXamlType Type => _rawSource.Type.GetClrType();
@ -625,6 +636,21 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
class TypeCastPathElementNode : IXamlIlBindingPathElementNode
{
public TypeCastPathElementNode(IXamlType ancestorType)
{
Type = ancestorType;
}
public IXamlType Type { get; }
public void Emit(XamlIlEmitContext context, IXamlILEmitter codeGen)
{
codeGen.EmitCall(context.GetAvaloniaTypes().CompiledBindingPathBuilder.FindMethod(m => m.Name == "TypeCast").MakeGenericMethod(new[] { Type }));
}
}
class XamlIlBindingPathNode : XamlAstNode, IXamlIlBindingPathNode, IXamlAstEmitableNode<IXamlILEmitter, XamlILNodeEmitResult>
{
private readonly List<IXamlIlBindingPathElementNode> _transformElements;

1
src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj

@ -23,6 +23,7 @@
<Compile Include="MarkupExtensions\CompiledBindings\ObservableStreamPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorFactory.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\PropertyInfoAccessorPlugin.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\StrongTypeCastNode.cs" />
<Compile Include="MarkupExtensions\CompiledBindings\TaskStreamPlugin.cs" />
<Compile Include="MarkupExtensions\DynamicResourceExtension.cs" />
<Compile Include="MarkupExtensions\ResolveByNameExtension.cs" />

2
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -26,6 +26,8 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
{
Path = Path,
Converter = Converter,
ConverterParameter = ConverterParameter,
TargetNullValue = TargetNullValue,
FallbackValue = FallbackValue,
Mode = Mode,
Priority = Priority,

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

@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
@ -53,6 +54,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
case IStronglyTypedStreamElement stream:
node = new StreamNode(stream.CreatePlugin());
break;
case ITypeCastElement typeCast:
node = new StrongTypeCastNode(typeCast.Type, typeCast.Cast);
break;
default:
throw new InvalidOperationException($"Unknown binding path element type {element.GetType().FullName}");
}
@ -66,6 +70,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal SourceMode SourceMode => _elements.Count > 0 && _elements[0] is IControlSourceBindingPathElement ? SourceMode.Control : SourceMode.Data;
internal object RawSource { get; }
public override string ToString()
=> string.Concat(_elements.Select(e => e.ToString()));
}
public class CompiledBindingPathBuilder
@ -126,6 +133,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
return this;
}
public CompiledBindingPathBuilder TypeCast<T>()
{
_elements.Add(new TypeCastPathElement<T>());
return this;
}
public CompiledBindingPathBuilder SetRawSource(object rawSource)
{
_rawSource = rawSource;
@ -157,6 +170,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public IPropertyInfo Property { get; }
public Func<WeakReference<object>, IPropertyInfo, IPropertyAccessor> AccessorFactory { get; }
public override string ToString()
=> $".{Property.Name}";
}
internal interface IStronglyTypedStreamElement : ICompiledBindingPathElement
@ -164,6 +180,13 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
IStreamPlugin CreatePlugin();
}
internal interface ITypeCastElement : ICompiledBindingPathElement
{
Type Type { get; }
Func<object, object> Cast { get; }
}
internal class TaskStreamPathElement<T> : IStronglyTypedStreamElement
{
public static readonly TaskStreamPathElement<T> Instance = new TaskStreamPathElement<T>();
@ -181,6 +204,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
internal class SelfPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
{
public static readonly SelfPathElement Instance = new SelfPathElement();
public override string ToString()
=> "$self";
}
internal class AncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
@ -193,6 +219,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public Type AncestorType { get; }
public int Level { get; }
public override string ToString()
=> $"$parent[{AncestorType?.Name},{Level}]";
}
internal class VisualAncestorPathElement : ICompiledBindingPathElement, IControlSourceBindingPathElement
@ -217,6 +246,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public INameScope NameScope { get; }
public string Name { get; }
public override string ToString()
=> $"#{Name}";
}
internal class ArrayElementPathElement : ICompiledBindingPathElement
@ -229,5 +261,24 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
public int[] Indices { get; }
public Type ElementType { get; }
public override string ToString()
=> $"[{string.Join(",", Indices)}]";
}
internal class TypeCastPathElement<T> : ITypeCastElement
{
private static object TryCast(object obj)
{
if (obj is T result)
return result;
return null;
}
public Type Type => typeof(T);
public Func<object, object> Cast => TryCast;
public override string ToString()
=> $"({Type.FullName})";
}
}

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/StrongTypeCastNode.cs

@ -0,0 +1,18 @@
using System;
using Avalonia.Data.Core;
namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
public class StrongTypeCastNode : TypeCastNode
{
private Func<object, object> _cast;
public StrongTypeCastNode(Type type, Func<object, object> cast) : base(type)
{
_cast = cast;
}
protected override object Cast(object value)
=> _cast(value);
}
}

89
src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs

@ -46,6 +46,10 @@ namespace Avalonia.Markup.Parsers
state = ParseIndexer(ref r, nodes);
break;
case State.TypeCast:
state = ParseTypeCast(ref r, nodes);
break;
case State.ElementName:
state = ParseElementName(ref r, nodes);
mode = SourceMode.Control;
@ -84,6 +88,11 @@ namespace Avalonia.Markup.Parsers
}
else if (ParseOpenBrace(ref r))
{
if (PeekOpenBrace(ref r))
{
return State.TypeCast;
}
return State.AttachedProperty;
}
else if (PeekOpenBracket(ref r))
@ -124,6 +133,10 @@ namespace Avalonia.Markup.Parsers
{
return State.Indexer;
}
else if (ParseOpenBrace(ref r))
{
return State.TypeCast;
}
return State.End;
}
@ -132,6 +145,11 @@ namespace Avalonia.Markup.Parsers
{
if (ParseOpenBrace(ref r))
{
if (PeekOpenBrace(ref r))
{
return State.TypeCast;
}
return State.AttachedProperty;
}
else
@ -152,6 +170,12 @@ namespace Avalonia.Markup.Parsers
{
var (ns, owner) = ParseTypeName(ref r);
if(!r.End && r.TakeIf(')'))
{
nodes.Add(new TypeCastNode() { Namespace = ns, TypeName = owner });
return State.AfterMember;
}
if (r.End || !r.TakeIf('.'))
{
throw new ExpressionParseException(r.Position, "Invalid attached property name.");
@ -159,6 +183,11 @@ namespace Avalonia.Markup.Parsers
var name = r.ParseIdentifier();
if (name.Length == 0)
{
throw new ExpressionParseException(r.Position, "Attached Property name expected after '.'.");
}
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
@ -186,6 +215,39 @@ namespace Avalonia.Markup.Parsers
return State.AfterMember;
}
private static State ParseTypeCast(ref CharacterReader r, List<INode> nodes)
{
bool parseMemberBeforeAddCast = ParseOpenBrace(ref r);
var (ns, typeName) = ParseTypeName(ref r);
var result = State.AfterMember;
if (parseMemberBeforeAddCast)
{
if (!ParseCloseBrace(ref r))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
}
result = ParseBeforeMember(ref r, nodes);
if(r.Peek == '[')
{
result = ParseIndexer(ref r, nodes);
}
}
nodes.Add(new TypeCastNode { Namespace = ns, TypeName = typeName });
if (r.End || !r.TakeIf(')'))
{
throw new ExpressionParseException(r.Position, "Expected ')'.");
}
return result;
}
private static State ParseElementName(ref CharacterReader r, List<INode> nodes)
{
var name = r.ParseIdentifier();
@ -288,11 +350,21 @@ namespace Avalonia.Markup.Parsers
return !r.End && r.TakeIf('(');
}
private static bool ParseCloseBrace(ref CharacterReader r)
{
return !r.End && r.TakeIf(')');
}
private static bool PeekOpenBracket(ref CharacterReader r)
{
return !r.End && r.Peek == '[';
}
private static bool PeekOpenBrace(ref CharacterReader r)
{
return !r.End && r.Peek == '(';
}
private static bool ParseStreamOperator(ref CharacterReader r)
{
return !r.End && r.TakeIf('^');
@ -322,6 +394,7 @@ namespace Avalonia.Markup.Parsers
BeforeMember,
AttachedProperty,
Indexer,
TypeCast,
End,
}
@ -343,9 +416,9 @@ namespace Avalonia.Markup.Parsers
}
}
public interface INode {}
public interface INode { }
public interface ITransformNode {}
public interface ITransformNode { }
public class EmptyExpressionNode : INode { }
@ -366,11 +439,11 @@ namespace Avalonia.Markup.Parsers
public IList<string> Arguments { get; set; }
}
public class NotNode : INode, ITransformNode {}
public class NotNode : INode, ITransformNode { }
public class StreamNode : INode {}
public class StreamNode : INode { }
public class SelfNode : INode {}
public class SelfNode : INode { }
public class NameNode : INode
{
@ -383,5 +456,11 @@ namespace Avalonia.Markup.Parsers
public string TypeName { get; set; }
public int Level { get; set; }
}
public class TypeCastNode : INode
{
public string Namespace { get; set; }
public string TypeName { get; set; }
}
}
}

19
src/Markup/Avalonia.Markup/Markup/Parsers/ExpressionParser.cs

@ -59,6 +59,9 @@ namespace Avalonia.Markup.Parsers
case BindingExpressionGrammar.NameNode elementName:
nextNode = new ElementNameNode(_nameScope, elementName.Name);
break;
case BindingExpressionGrammar.TypeCastNode typeCast:
nextNode = ParseTypeCastNode(typeCast);
break;
}
if (rootNode is null)
{
@ -92,6 +95,22 @@ namespace Avalonia.Markup.Parsers
return new FindAncestorNode(ancestorType, ancestorLevel);
}
private TypeCastNode ParseTypeCastNode(BindingExpressionGrammar.TypeCastNode node)
{
Type castType = null;
if (!(node.Namespace is null) && !(node.TypeName is null))
{
if (_typeResolver == null)
{
throw new InvalidOperationException("Cannot parse a binding path with a typed Cast without a type resolver. Maybe you can use a LINQ Expression binding path instead?");
}
castType = _typeResolver(node.Namespace, node.TypeName);
}
return new TypeCastNode(castType);
}
private AvaloniaPropertyAccessorNode ParseAttachedProperty(BindingExpressionGrammar.AttachedPropertyNameNode node)
{
if (_typeResolver == null)

2
tests/Avalonia.Markup.UnitTests/Parsers/ExpressionObserverBuilderTests_AttachedProperty.cs

@ -129,7 +129,7 @@ namespace Avalonia.Markup.UnitTests.Parsers
{
var data = new Class1();
Assert.Throws<ExpressionParseException>(() => ExpressionObserverBuilder.Build(data, "(Owner)", typeResolver: _typeResolver));
Assert.Throws<ExpressionParseException>(() => ExpressionObserverBuilder.Build(data, "(Owner.)", typeResolver: _typeResolver));
}
[Fact]

48
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@ -84,6 +84,54 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void SupportCastToTypeInExpression()
{
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'
>
<ContentControl Content='{Binding $parent.((local:TestDataContext)DataContext).StringProperty}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(dataContext.StringProperty, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpression_DifferentTypeEvaluatesToNull()
{
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'
>
<ContentControl Content='{Binding $parent.((local:TestDataContext)DataContext)}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = "foo";
window.DataContext = dataContext;
Assert.Equal(null, contentControl.Content);
}
}
private class FooBar
{
public object Foo { get; } = null;

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

@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Reactive.Subjects;
using System.Text;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Data.Converters;
using Avalonia.Data.Core;
using Avalonia.Markup.Data;
using Avalonia.UnitTests;
@ -601,6 +603,48 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void SupportParentInPath()
{
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'
Title='foo'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.Title}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
Assert.Equal("foo", contentControl.Content);
}
}
[Fact]
public void SupportConverterWithParameter()
{
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:TestDataContext' x:CompileBindings='True'>
<TextBlock Name='textBlock' Text='{Binding StringProperty, Converter={x:Static local:AppendConverter.Instance}, ConverterParameter=Bar}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.DataContext = new TestDataContext() { StringProperty = "Foo" };
Assert.Equal("Foo+Bar", textBlock.Text);
}
}
[Fact]
public void ThrowsOnInvalidCompileBindingsDirective()
{
@ -616,23 +660,198 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
Assert.Throws<XamlX.XamlParseException>(() => AvaloniaRuntimeXamlLoader.Load(xaml));
}
}
[Fact]
public void SupportCastToTypeInExpression()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.((local:TestDataContext)DataContext)}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = new TestDataContext();
window.DataContext = dataContext;
Assert.Equal(dataContext, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpression_DifferentTypeEvaluatesToNull()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.((local:TestDataContext)DataContext)}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = "foo";
window.DataContext = dataContext;
Assert.Equal(null, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpressionWithProperty()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.((local:TestDataContext)DataContext).StringProperty}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(dataContext.StringProperty, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpressionWithProperty1()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.DataContext(local:TestDataContext).StringProperty}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(dataContext.StringProperty, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpressionWithPropertyIndexer()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding ((local:TestData)ObjectsArrayProperty[0]).StringProperty}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var data = new TestData()
{
StringProperty = "Foo"
};
var dataContext = new TestDataContext
{
ObjectsArrayProperty = new object[] { data }
};
window.DataContext = dataContext;
Assert.Equal(data.StringProperty, contentControl.Content);
}
}
[Fact]
public void SupportCastToTypeInExpressionWithProperty_DifferentTypeEvaluatesToNull()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='using:Avalonia.Markup.Xaml.UnitTests.MarkupExtensions'
x:DataType='local:TestDataContext'>
<ContentControl Content='{CompiledBinding $parent.((local:TestDataContext)DataContext).StringProperty}' Name='contentControl' />
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var contentControl = window.FindControl<ContentControl>("contentControl");
var dataContext = new TestDataContext
{
StringProperty = "foobar"
};
window.DataContext = dataContext;
Assert.Equal(dataContext.StringProperty, contentControl.Content);
window.DataContext = "foo";
Assert.Equal(null, contentControl.Content);
}
}
}
public interface INonIntegerIndexer
{
string this[string key] {get; set;}
string this[string key] { get; set; }
}
public interface INonIntegerIndexerDerived : INonIntegerIndexer
{}
{ }
public interface IHasProperty
{
string StringProperty {get; set; }
string StringProperty { get; set; }
}
public interface IHasPropertyDerived : IHasProperty
{}
{ }
public class AppendConverter : IValueConverter
{
public static IValueConverter Instance { get; } = new AppendConverter();
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> string.Format("{0}+{1}", value, parameter);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotImplementedException();
}
public class TestData
{
public string StringProperty { get; set; }
}
public class TestDataContext : IHasPropertyDerived
{
@ -646,6 +865,8 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
public string[] ArrayProperty { get; set; }
public object[] ObjectsArrayProperty { get; set; }
public List<string> ListProperty { get; set; } = new List<string>();
public NonIntegerIndexer NonIntegerIndexerProperty { get; set; } = new NonIntegerIndexer();

Loading…
Cancel
Save