From 7d6eda54229bc3f2ade455341ed2f911f3302e3f Mon Sep 17 00:00:00 2001 From: Stephane Delcroix Date: Wed, 5 Jun 2024 16:42:42 +0200 Subject: [PATCH] [X] allow namescope resolution before parenting when an element doesn't override the NameScope (DataTemplate, etc...) FindName fails until the element is parented. this PR sets a field (less expensive than a BP) used only during that time, and allow VSM to be applied. - fixes #22001 --- .../SetNamescopesAndRegisterNamesVisitor.cs | 12 ++++++++++++ src/Controls/src/Core/Element/Element.cs | 4 +++- src/Controls/src/Xaml/CreateValuesVisitor.cs | 5 +++++ .../Controls.Xaml.UnitTests.csproj | 3 ++- .../tests/Xaml.UnitTests/Issues/Maui22001.xaml | 3 ++- .../Xaml.UnitTests/Issues/Maui22001.xaml.cs | 18 ++++++++++++++++-- 6 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs b/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs index 2e8b09230506..7e63f019d585 100644 --- a/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs +++ b/src/Controls/src/Build.Tasks/SetNamescopesAndRegisterNamesVisitor.cs @@ -59,6 +59,18 @@ public void Visit(ElementNode node, INode parentNode) } if (setNameScope && Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "BindableObject")))) SetNameScope(node, namescopeVarDef); + //workaround when VSM tries to apply state before parenting + else if (Context.Variables[node].VariableType.InheritsFromOrImplements(Context.Cache, Context.Body.Method.Module.ImportReference(Context.Cache, ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element")))) + { + var module = Context.Body.Method.Module; + var parameterTypes = new[] { + ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls", "Element"), + ("Microsoft.Maui.Controls", "Microsoft.Maui.Controls.Internals", "INameScope"), + }; + Context.IL.Append(Context.Variables[node].LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[0]), module)); + Context.IL.Append(namescopeVarDef.LoadAs(Context.Cache, module.GetTypeDefinition(Context.Cache, parameterTypes[1]), module)); + Context.IL.Emit(OpCodes.Stfld, module.ImportFieldReference(Context.Cache, parameterTypes[0], nameof(Element.transientNamescope))); + } Context.Scopes[node] = new Tuple>(namescopeVarDef, namesInNamescope); } diff --git a/src/Controls/src/Core/Element/Element.cs b/src/Controls/src/Core/Element/Element.cs index f25b0d25944b..3c835036eed7 100644 --- a/src/Controls/src/Core/Element/Element.cs +++ b/src/Controls/src/Core/Element/Element.cs @@ -492,13 +492,15 @@ public bool EffectIsAttached(string name) return false; } + internal INameScope transientNamescope; + /// Returns the element that has the specified name. /// The name of the element to be found. /// The element that has the specified name. /// Thrown if the element's namescope couldn't be found. public object FindByName(string name) { - var namescope = GetNameScope(); + var namescope = GetNameScope() ?? transientNamescope; if (namescope == null) throw new InvalidOperationException("this element is not in a namescope"); return namescope.FindByName(name); diff --git a/src/Controls/src/Xaml/CreateValuesVisitor.cs b/src/Controls/src/Xaml/CreateValuesVisitor.cs index 5eef0829d3bb..06f4ec560873 100644 --- a/src/Controls/src/Xaml/CreateValuesVisitor.cs +++ b/src/Controls/src/Xaml/CreateValuesVisitor.cs @@ -152,6 +152,11 @@ public void Visit(ElementNode node, INode parentNode) if (value is BindableObject bindableValue && node.NameScopeRef != (parentNode as IElementNode)?.NameScopeRef) NameScope.SetNameScope(bindableValue, node.NameScopeRef.NameScope); + //Workaround for when a VSM is applied before parenting + if (value is Element iElement) + iElement.transientNamescope = node.NameScopeRef.NameScope; + + var assemblyName = (Context.RootAssembly ?? Context.RootElement?.GetType().Assembly)?.GetName().Name; if (assemblyName != null && value != null && !value.GetType().IsValueType && XamlFilePathAttribute.GetFilePathForObject(Context.RootElement) is string path) VisualDiagnostics.RegisterSourceInfo(value, new Uri($"{path};assembly={assemblyName}", UriKind.Relative), ((IXmlLineInfo)node).LineNumber, ((IXmlLineInfo)node).LinePosition); diff --git a/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj b/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj index 4ded530f749a..610960638da1 100644 --- a/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj +++ b/src/Controls/tests/Xaml.UnitTests/Controls.Xaml.UnitTests.csproj @@ -49,8 +49,9 @@ + - \ No newline at end of file + diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml index 10884f16c03e..a361c85fa558 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml @@ -2,8 +2,9 @@ + - + diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs index 9a69fba29387..8829a863af5a 100644 --- a/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs @@ -30,15 +30,25 @@ public Maui22001(bool useCompiledXaml) [TestFixture] class Test - { + { + MockDeviceDisplay mockDeviceDisplay; + MockDeviceInfo mockDeviceInfo; [SetUp] public void Setup() { Application.SetCurrentApplication(new MockApplication()); DispatcherProvider.SetCurrent(new DispatcherProviderStub()); + + DeviceDisplay.SetCurrent(mockDeviceDisplay = new MockDeviceDisplay()); + DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo()); } - [TearDown] public void TearDown() => AppInfo.SetCurrent(null); + [TearDown] public void TearDown() + { + AppInfo.SetCurrent(null); + mockDeviceDisplay = null; + mockDeviceInfo = null; + } [Test] public void StateTriggerTargetName([Values(false, true)] bool useCompiledXaml) @@ -51,6 +61,10 @@ public void StateTriggerTargetName([Values(false, true)] bool useCompiledXaml) }; Assert.That(page._firstGrid.IsVisible, Is.True); Assert.That(page._secondGrid.IsVisible, Is.False); + + mockDeviceDisplay.SetMainDisplayOrientation(DisplayOrientation.Landscape); + Assert.That(page._firstGrid.IsVisible, Is.False); + Assert.That(page._secondGrid.IsVisible, Is.True); } } }