Skip to content

Commit dbd8fb7

Browse files
committed
chore: Progress on Leave
1 parent 7852566 commit dbd8fb7

File tree

6 files changed

+103
-14
lines changed

6 files changed

+103
-14
lines changed

src/Uno.UI.RuntimeTests/Tests/BindingTests/BindingTests.cs

+68
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public async Task When_Binding_By_Programmatically_Setting_Name()
4343
},
4444
};
4545

46+
Assert.AreEqual(null, button.Content);
47+
4648
await UITestHelper.Load(sp);
4749

4850
#if UNO_HAS_ENHANCED_LIFECYCLE || WINAPPSDK
@@ -51,6 +53,72 @@ public async Task When_Binding_By_Programmatically_Setting_Name()
5153
// Unfortunate wrong behavior on Android and iOS.
5254
Assert.AreEqual(null, button.Content);
5355
#endif
56+
57+
tb.DataContext = "Hello World Updated";
58+
59+
#if UNO_HAS_ENHANCED_LIFECYCLE || WINAPPSDK
60+
Assert.AreEqual("Hello World Updated", button.Content);
61+
#else
62+
// Unfortunate wrong behavior on Android and iOS.
63+
Assert.AreEqual(null, button.Content);
64+
#endif
65+
66+
sp.Children.Remove(tb);
67+
68+
#if UNO_HAS_ENHANCED_LIFECYCLE || WINAPPSDK
69+
Assert.AreEqual("Hello World Updated", button.Content);
70+
#else
71+
// Unfortunate wrong behavior on Android and iOS.
72+
Assert.AreEqual(null, button.Content);
73+
#endif
74+
75+
// At this point, the textBox name is was unregistered as it's removed from the visual tree.
76+
// However, on WinUI, the binding have already solved the target for ElementName and the binding will continue to work.
77+
tb.DataContext = "Hello World Updated 2";
78+
79+
#if UNO_HAS_ENHANCED_LIFECYCLE || WINAPPSDK
80+
Assert.AreEqual("Hello World Updated 2", button.Content);
81+
#else
82+
// Unfortunate wrong behavior on Android and iOS.
83+
Assert.AreEqual(null, button.Content);
84+
#endif
85+
}
86+
87+
[TestMethod]
88+
public async Task When_Binding_Is_Set_After_RegisterName_And_UnregisterName()
89+
{
90+
var tb = new TextBox();
91+
tb.Name = "textBox";
92+
tb.Text = "Text";
93+
tb.DataContext = "Hello World";
94+
95+
var button = new Button();
96+
button.Name = "button";
97+
98+
var sp = new StackPanel()
99+
{
100+
Children =
101+
{
102+
tb,
103+
button,
104+
},
105+
};
106+
107+
Assert.AreEqual(null, button.Content);
108+
109+
await UITestHelper.Load(sp);
110+
111+
sp.Children.Remove(tb);
112+
113+
// The remove call will UnregisterName for the textBox.
114+
// The binding shouldn't be able to find the target for ElementName
115+
button.SetBinding(Button.ContentProperty, new Binding()
116+
{
117+
ElementName = "textBox",
118+
Path = new PropertyPath("DataContext")
119+
});
120+
121+
Assert.AreEqual(null, button.Content);
54122
}
55123

56124
[TestMethod]

src/Uno.UI/UI/Xaml/Controls/FlipView/FlipView.managed.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ internal override void Enter(INameScope nameScope, EnterParams @params, int dept
129129
HookTemplate();
130130
}
131131

132-
internal override void Leave(LeaveParams @params)
132+
internal override void Leave(INameScope nameScope, LeaveParams @params)
133133
{
134-
base.Leave(@params);
134+
base.Leave(nameScope, @params);
135135

136136
UnhookTemplate();
137137
}

src/Uno.UI/UI/Xaml/FrameworkElement.Layout.crossruntime.cs

+28-7
Original file line numberDiff line numberDiff line change
@@ -902,16 +902,21 @@ private void ArrangeNative(Point offset, Rect? clippedFrame)
902902
#endif
903903
}
904904

905+
private void AdjustNameScope(ref INameScope? nameScope)
906+
{
907+
if (NameScope.GetNameScope(this) is { } currentNameScope)
908+
{
909+
nameScope = currentNameScope;
910+
}
911+
}
912+
905913
internal override void Enter(INameScope? nameScope, EnterParams @params, int depth)
906914
{
907915
// This should happen regardless of whether Name is null or not.
908916
// What we need here is that once we find an element being entered that
909917
// has its own NameScope, then we start using this namescope and use it for entering its children.
910918
// For example, elements in a template where the template root will have its own NameScope.
911-
if (NameScope.GetNameScope(this) is { } currentNameScope)
912-
{
913-
nameScope = currentNameScope;
914-
}
919+
AdjustNameScope(ref nameScope);
915920

916921
if (nameScope is not null)
917922
{
@@ -963,8 +968,24 @@ internal override void Enter(INameScope? nameScope, EnterParams @params, int dep
963968
m_firedLoadingEvent = false;
964969
}
965970

966-
internal override void Leave(LeaveParams @params)
971+
internal override void Leave(INameScope? nameScope, LeaveParams @params)
967972
{
973+
// This should happen regardless of whether Name is null or not.
974+
// What we need here is that once we find an element leaving that
975+
// has its own NameScope, then we start using this namescope and use it for leaving its children.
976+
// For example, elements in a template where the template root will have its own NameScope.
977+
AdjustNameScope(ref nameScope);
978+
979+
if (nameScope is not null)
980+
{
981+
var name = this.Name;
982+
983+
if (!string.IsNullOrEmpty(name))
984+
{
985+
nameScope.UnregisterName(name);
986+
}
987+
}
988+
968989
// The way this works on WinUI is that when an element enters the visual tree, all values
969990
// of properties that are marked with MetaDataPropertyInfoFlags::IsSparse and MetaDataPropertyInfoFlags::IsVisualTreeProperty
970991
// are entered as well.
@@ -975,12 +996,12 @@ internal override void Leave(LeaveParams @params)
975996
{
976997
if (resource is FrameworkElement resourceAsUIElement)
977998
{
978-
resourceAsUIElement.Leave(@params);
999+
resourceAsUIElement.Leave(nameScope, @params);
9791000
}
9801001
}
9811002
}
9821003

983-
base.Leave(@params);
1004+
base.Leave(nameScope, @params);
9841005

9851006
ReconfigureViewportPropagation(isLeavingTree: true);
9861007
}

src/Uno.UI/UI/Xaml/ResourceDictionary.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ public bool Remove(object key)
140140
if (value is FrameworkElement fe)
141141
{
142142
#if UNO_HAS_ENHANCED_LIFECYCLE
143-
fe.Leave(new LeaveParams());
143+
fe.Leave(UIElement.FindNameScope(fe), new LeaveParams());
144144
#else
145145
fe.PerformOnUnloaded(isFromResources: true);
146146
#endif

src/Uno.UI/UI/Xaml/UIElement.crossruntime.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -110,15 +110,15 @@ private void OnChildRemoved(UIElement child)
110110

111111
#if UNO_HAS_ENHANCED_LIFECYCLE
112112
var leaveParams = new LeaveParams(IsActiveInVisualTree);
113-
child.Leave(leaveParams);
113+
child.Leave(FindNameScope(this), leaveParams);
114114
#endif
115115
}
116116

117117
internal Point GetPosition(Point position, UIElement relativeTo)
118118
=> TransformToVisual(relativeTo).TransformPoint(position);
119119

120120
#if UNO_HAS_ENHANCED_LIFECYCLE
121-
private static INameScope FindNameScope(DependencyObject dependencyObject)
121+
internal static INameScope FindNameScope(DependencyObject dependencyObject)
122122
{
123123
// In many cases, this will not traverse the entire visual tree, as the NameScope is often
124124
// present on each element as the DP is inherited. However, it can happen that the DP inheritance

src/Uno.UI/UI/Xaml/UIElement.mux.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -652,7 +652,7 @@ internal virtual void Enter(INameScope? nameScope, EnterParams @params, int dept
652652
SetLayoutFlags(LayoutFlag.MeasureDirty | LayoutFlag.ArrangeDirty);
653653
}
654654

655-
internal virtual void Leave(LeaveParams @params)
655+
internal virtual void Leave(INameScope? nameScope, LeaveParams @params)
656656
{
657657
foreach (var child in _children)
658658
{
@@ -666,7 +666,7 @@ internal virtual void Leave(LeaveParams @params)
666666
continue;
667667
}
668668

669-
child.Leave(@params);
669+
child.Leave(nameScope, @params);
670670
}
671671

672672
if (IsActiveInVisualTree)

0 commit comments

Comments
 (0)