From 65cac33549b78add805db59b061fc436e1665ab0 Mon Sep 17 00:00:00 2001 From: donandren Date: Tue, 17 May 2016 16:13:51 +0300 Subject: [PATCH 1/2] member selector support member path --- .../Templates/MemberSelector.cs | 41 +++++++++++++++---- .../Properties/AssemblyInfo.cs | 3 +- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs index 6836dbf9df..c04bc5ece9 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs @@ -1,20 +1,47 @@ -// Copyright (c) The Avalonia Project. All rights reserved. +// 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.Reflection; using Avalonia.Controls.Templates; +using Avalonia.Markup.Data; +using System; namespace Avalonia.Markup.Xaml.Templates { public class MemberSelector : IMemberSelector { - public string MemberName { get; set; } + public string MemberName + { + get { return _memberName; } + set + { + if (_memberName != value) + { + _memberName = value; + _expressionNode = null; + _memberValueNode = null; + } + } + } public object Select(object o) { - // TODO: Handle nested property paths, changing values etc. - var property = o.GetType().GetRuntimeProperty(MemberName); - return property?.GetValue(o); + if (_expressionNode == null) + { + _expressionNode = ExpressionNodeBuilder.Build(MemberName); + + _memberValueNode = _expressionNode; + + while (_memberValueNode.Next != null) + _memberValueNode = _memberValueNode.Next; + } + + _expressionNode.Target = new WeakReference(o); + + return _memberValueNode.CurrentValue.Target; } + + private ExpressionNode _expressionNode; + private string _memberName; + private ExpressionNode _memberValueNode; } -} +} \ No newline at end of file diff --git a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs index 4d6ccdf31a..d74ccf00a1 100644 --- a/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs +++ b/src/Markup/Avalonia.Markup/Properties/AssemblyInfo.cs @@ -7,4 +7,5 @@ using System.Runtime.CompilerServices; [assembly: AssemblyTitle("Avalonia.Markup")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Markup")] -[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] \ No newline at end of file +[assembly: InternalsVisibleTo("Avalonia.Markup.UnitTests")] +[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml")] \ No newline at end of file From 4c0a0269496dc1ee592a1033f7b49d8fb53e54ac Mon Sep 17 00:00:00 2001 From: donandren Date: Thu, 19 May 2016 20:13:20 +0300 Subject: [PATCH 2/2] MemberSelector UnitTests --- .../Templates/MemberSelector.cs | 19 +- .../Avalonia.Markup.Xaml.UnitTests.csproj | 3 +- .../Templates/MemberSelectorTests.cs | 168 ++++++++++++++++++ 3 files changed, 188 insertions(+), 2 deletions(-) create mode 100644 tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs diff --git a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs index c04bc5ece9..bc07da8b14 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs +++ b/src/Markup/Avalonia.Markup.Xaml/Templates/MemberSelector.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Markup.Data; using System; @@ -25,6 +26,11 @@ namespace Avalonia.Markup.Xaml.Templates public object Select(object o) { + if (string.IsNullOrEmpty(MemberName)) + { + return o; + } + if (_expressionNode == null) { _expressionNode = ExpressionNodeBuilder.Build(MemberName); @@ -37,7 +43,18 @@ namespace Avalonia.Markup.Xaml.Templates _expressionNode.Target = new WeakReference(o); - return _memberValueNode.CurrentValue.Target; + object result = _memberValueNode.CurrentValue.Target; + + if (result == AvaloniaProperty.UnsetValue) + { + return null; + } + else if (result is BindingError) + { + return null; + } + + return result; } private ExpressionNode _expressionNode; diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj index a2f2d3db56..7f8c790492 100644 --- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj @@ -1,4 +1,4 @@ - + @@ -103,6 +103,7 @@ + diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs new file mode 100644 index 0000000000..4903ca7b35 --- /dev/null +++ b/tests/Avalonia.Markup.Xaml.UnitTests/Templates/MemberSelectorTests.cs @@ -0,0 +1,168 @@ +// 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 Avalonia.Markup.Xaml.Templates; +using System; +using Xunit; + +namespace Avalonia.Markup.Xaml.UnitTests.Templates +{ + public class MemberSelectorTests + { + [Fact] + public void Should_Not_Hold_Reference_To_Object() + { + WeakReference dataRef = null; + + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + Action run = () => + { + var data = new Item() + { + Child = new Item() { StringValue = "Value1" } + }; + + Assert.Same("Value1", selector.Select(data)); + + dataRef = new WeakReference(data); + }; + + run(); + + GC.Collect(); + + Assert.False(dataRef.IsAlive); + } + + [Fact] + public void Should_Select_Child_Property_Value() + { + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + var data = new Item() + { + Child = new Item() { StringValue = "Value1" } + }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Child_Property_Value_In_Multiple_Items() + { + var selector = new MemberSelector() { MemberName = "Child.StringValue" }; + + var data = new Item[] + { + new Item() { Child = new Item() { StringValue = "Value1" } }, + new Item() { Child = new Item() { StringValue = "Value2" } }, + new Item() { Child = new Item() { StringValue = "Value3" } } + }; + + Assert.Same("Value1", selector.Select(data[0])); + Assert.Same("Value2", selector.Select(data[1])); + Assert.Same("Value3", selector.Select(data[2])); + } + + [Fact] + public void Should_Select_MoreComplex_Property_Value() + { + var selector = new MemberSelector() { MemberName = "Child.Child.Child.StringValue" }; + + var data = new Item() + { + Child = new Item() + { + Child = new Item() + { + Child = new Item() { StringValue = "Value1" } + } + } + }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Null_Value_On_Null_Object() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + Assert.Equal(null, selector.Select(null)); + } + + [Fact] + public void Should_Select_Null_Value_On_Wrong_MemberName() + { + var selector = new MemberSelector() { MemberName = "WrongProperty" }; + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same(null, selector.Select(data)); + } + + [Fact] + public void Should_Select_Simple_Property_Value() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same("Value1", selector.Select(data)); + } + + [Fact] + public void Should_Select_Simple_Property_Value_In_Multiple_Items() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item[] + { + new Item() { StringValue = "Value1" }, + new Item() { StringValue = "Value2" }, + new Item() { StringValue = "Value3" } + }; + + Assert.Same("Value1", selector.Select(data[0])); + Assert.Same("Value2", selector.Select(data[1])); + Assert.Same("Value3", selector.Select(data[2])); + } + + [Fact] + public void Should_Select_Target_On_Empty_MemberName() + { + var selector = new MemberSelector(); + + var data = new Item() { StringValue = "Value1" }; + + Assert.Same(data, selector.Select(data)); + } + + [Fact] + public void Should_Support_Change_Of_MemberName() + { + var selector = new MemberSelector() { MemberName = "StringValue" }; + + var data = new Item() + { + StringValue = "Value1", + IntValue = 1 + }; + + Assert.Same("Value1", selector.Select(data)); + + selector.MemberName = "IntValue"; + + Assert.Equal(1, selector.Select(data)); + } + + private class Item + { + public Item Child { get; set; } + public int IntValue { get; set; } + + public string StringValue { get; set; } + } + } +} \ No newline at end of file