Skip to content
This repository was archived by the owner on May 1, 2024. It is now read-only.

Commit 4226f5b

Browse files
StephaneDelcroixJason Smith
authored andcommitted
[Xaml] Clone node tree on DT, allow markup to be evaluated multiple times (#295)
1 parent 3ca06ea commit 4226f5b

File tree

7 files changed

+144
-7
lines changed

7 files changed

+144
-7
lines changed

Xamarin.Forms.Xaml.UnitTests/MarkupExpressionParserTests.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,13 @@ public void Accept (IXamlNodeVisitor visitor, INode parentNode)
6868
throw new NotImplementedException ();
6969
}
7070

71+
7172
public List<string> IgnorablePrefixes { get; set; }
73+
74+
public INode Clone()
75+
{
76+
throw new NotImplementedException();
77+
}
7278
}
7379

7480
[SetUp]
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<ListView xmlns="http://xamarin.com/schemas/2014/forms"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
4+
xmlns:local="clr-namespace:Xamarin.Forms.Xaml.UnitTests"
5+
x:Class="Xamarin.Forms.Xaml.UnitTests.TypeExtension">
6+
<ListView.ItemTemplate>
7+
<DataTemplate>
8+
<ViewCell>
9+
<Button Command="{local:Navigate Operation=Forward, Type={x:Type Grid}}" />
10+
</ViewCell>
11+
</DataTemplate>
12+
</ListView.ItemTemplate>
13+
</ListView>
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Windows.Input;
4+
using Xamarin.Forms;
5+
using NUnit.Framework;
6+
7+
namespace Xamarin.Forms.Xaml.UnitTests
8+
{
9+
public enum NavigationOperation
10+
{
11+
Forward,
12+
Back,
13+
Replace,
14+
}
15+
16+
[ContentProperty(nameof(Operation))]
17+
public class NavigateExtension : IMarkupExtension<ICommand>
18+
{
19+
public NavigationOperation Operation { get; set; }
20+
21+
public Type Type { get; set; }
22+
23+
public ICommand ProvideValue(IServiceProvider serviceProvider)
24+
{
25+
return new Command(() => { });
26+
}
27+
28+
object IMarkupExtension.ProvideValue(IServiceProvider serviceProvider)
29+
{
30+
return ProvideValue(serviceProvider);
31+
}
32+
}
33+
34+
public partial class TypeExtension : ListView
35+
{
36+
public TypeExtension()
37+
{
38+
InitializeComponent();
39+
}
40+
41+
public TypeExtension(bool useCompiledXaml)
42+
{
43+
//this stub will be replaced at compile time
44+
}
45+
46+
[TestFixture]
47+
public class Tests
48+
{
49+
[TestCase(false)]
50+
[TestCase(true)]
51+
public void NestedMarkupExtensionInsideDataTemplate(bool useCompiledXaml)
52+
{
53+
var listView = new TypeExtension(useCompiledXaml);
54+
listView.ItemsSource = new string [2];
55+
56+
var cell = (ViewCell)listView.TemplatedItems [0];
57+
var button = (Button)cell.View;
58+
Assert.IsNotNull(button.Command);
59+
60+
cell = (ViewCell)listView.TemplatedItems [1];
61+
button = (Button)cell.View;
62+
Assert.IsNotNull(button.Command);
63+
}
64+
}
65+
}
66+
}

Xamarin.Forms.Xaml.UnitTests/Xamarin.Forms.Xaml.UnitTests.csproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,9 @@
353353
<Compile Include="SetValue.xaml.cs">
354354
<DependentUpon>SetValue.xaml</DependentUpon>
355355
</Compile>
356+
<Compile Include="TypeExtension.xaml.cs">
357+
<DependentUpon>TypeExtension.xaml</DependentUpon>
358+
</Compile>
356359
</ItemGroup>
357360
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
358361
<Import Project="..\.nuspec\Xamarin.Forms.Debug.targets" />
@@ -629,6 +632,9 @@
629632
<EmbeddedResource Include="SetValue.xaml">
630633
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
631634
</EmbeddedResource>
635+
<EmbeddedResource Include="TypeExtension.xaml">
636+
<Generator>MSBuild:UpdateDesignTimeXaml</Generator>
637+
</EmbeddedResource>
632638
</ItemGroup>
633639
<ItemGroup>
634640
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />

Xamarin.Forms.Xaml.UnitTests/XamlLoaderCreateTests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Windows.Input;
23
using NUnit.Framework;
4+
using Xamarin.Forms.Core.UnitTests;
35

46
namespace Xamarin.Forms.Xaml.UnitTests
57
{

Xamarin.Forms.Xaml/ApplyPropertiesVisitor.cs

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -433,14 +433,16 @@ void SetTemplate(ElementTemplate dt, INode node)
433433
((IDataTemplate)dt).LoadTemplate = () =>
434434
{
435435
#pragma warning restore 0612
436+
var cnode = node.Clone();
436437
var context = new HydratationContext { ParentContext = Context, RootElement = Context.RootElement };
437-
node.Accept(new ExpandMarkupsVisitor(context), null);
438-
node.Accept(new NamescopingVisitor(context), null);
439-
node.Accept(new CreateValuesVisitor(context), null);
440-
node.Accept(new RegisterXNamesVisitor(context), null);
441-
node.Accept(new FillResourceDictionariesVisitor(context), null);
442-
node.Accept(new ApplyPropertiesVisitor(context, true), null);
443-
return context.Values[node];
438+
cnode.Accept(new XamlNodeVisitor((n, parent) => n.Parent = parent), node.Parent); //set parents for {StaticResource}
439+
cnode.Accept(new ExpandMarkupsVisitor(context), null);
440+
cnode.Accept(new NamescopingVisitor(context), null);
441+
cnode.Accept(new CreateValuesVisitor(context), null);
442+
cnode.Accept(new RegisterXNamesVisitor(context), null);
443+
cnode.Accept(new FillResourceDictionariesVisitor(context), null);
444+
cnode.Accept(new ApplyPropertiesVisitor(context, true), null);
445+
return context.Values[cnode];
444446
};
445447
}
446448
}

Xamarin.Forms.Xaml/XamlNode.cs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System;
12
using System.Collections.Generic;
23
using System.Diagnostics;
34
using System.Linq;
@@ -15,6 +16,7 @@ internal interface INode
1516
INode Parent { get; set; }
1617

1718
void Accept(IXamlNodeVisitor visitor, INode parentNode);
19+
INode Clone();
1820
}
1921

2022
internal interface IValueNode : INode
@@ -81,6 +83,8 @@ public bool HasLineInfo()
8183
public int LineNumber { get; set; }
8284

8385
public int LinePosition { get; set; }
86+
87+
public abstract INode Clone();
8488
}
8589

8690
[DebuggerDisplay("{Value}")]
@@ -98,6 +102,13 @@ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
98102
{
99103
visitor.Visit(this, parentNode);
100104
}
105+
106+
public override INode Clone()
107+
{
108+
return new ValueNode(Value, NamespaceResolver, LineNumber, LinePosition) {
109+
IgnorablePrefixes = IgnorablePrefixes
110+
};
111+
}
101112
}
102113

103114
[DebuggerDisplay("{MarkupString}")]
@@ -116,6 +127,13 @@ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
116127
{
117128
visitor.Visit(this, parentNode);
118129
}
130+
131+
public override INode Clone()
132+
{
133+
return new MarkupNode(MarkupString, NamespaceResolver, LineNumber, LinePosition) {
134+
IgnorablePrefixes = IgnorablePrefixes
135+
};
136+
}
119137
}
120138

121139
internal class ElementNode : BaseNode, IValueNode, IElementNode
@@ -174,6 +192,20 @@ internal static bool IsResourceDictionary(INode node, INode parentNode)
174192
var enode = node as ElementNode;
175193
return enode.XmlType.Name == "ResourceDictionary";
176194
}
195+
196+
public override INode Clone()
197+
{
198+
var clone = new ElementNode(XmlType, NamespaceURI, NamespaceResolver, LineNumber, LinePosition) {
199+
IgnorablePrefixes = IgnorablePrefixes
200+
};
201+
foreach (var kvp in Properties)
202+
clone.Properties.Add(kvp.Key, kvp.Value.Clone());
203+
foreach (var p in SkipProperties)
204+
clone.SkipProperties.Add(p);
205+
foreach (var p in CollectionItems)
206+
clone.CollectionItems.Add(p.Clone());
207+
return clone;
208+
}
177209
}
178210

179211
internal abstract class RootNode : ElementNode
@@ -220,6 +252,16 @@ public override void Accept(IXamlNodeVisitor visitor, INode parentNode)
220252
if (visitor.VisitChildrenFirst)
221253
visitor.Visit(this, parentNode);
222254
}
255+
256+
public override INode Clone()
257+
{
258+
var items = new List<INode>();
259+
foreach (var p in CollectionItems)
260+
items.Add(p.Clone());
261+
return new ListNode(items, NamespaceResolver, LineNumber, LinePosition) {
262+
IgnorablePrefixes = IgnorablePrefixes
263+
};
264+
}
223265
}
224266

225267
internal static class INodeExtensions

0 commit comments

Comments
 (0)