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 f8a193e6c3e1..ad59ab4801db 100644 --- a/src/Controls/src/Core/Element/Element.cs +++ b/src/Controls/src/Core/Element/Element.cs @@ -502,13 +502,19 @@ public bool EffectIsAttached(string name) return false; } + //this is only used by XAMLC, not added to public API + [EditorBrowsable(EditorBrowsableState.Never)] +#pragma warning disable RS0016 // Add public types and members to the declared API + public INameScope transientNamescope; +#pragma warning restore RS0016 // Add public types and members to the declared API + /// 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 010872642b99..ed2ff96e8a82 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/Maui16208.xaml b/src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml new file mode 100644 index 000000000000..24bfc83c3302 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml.cs new file mode 100644 index 000000000000..de0fab0ec7b9 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Devices; +using Microsoft.Maui.Dispatching; + +using Microsoft.Maui.Graphics; +using Microsoft.Maui.UnitTests; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class Maui16208 +{ + public Maui16208() + { + InitializeComponent(); + } + + public Maui16208(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [TestFixture] + class Test + { + MockDeviceInfo mockDeviceInfo; + [SetUp] + public void Setup() + { + Application.SetCurrentApplication(new MockApplication()); + DispatcherProvider.SetCurrent(new DispatcherProviderStub()); + + DeviceInfo.SetCurrent(mockDeviceInfo = new MockDeviceInfo()); + } + + [TearDown] public void TearDown() + { + AppInfo.SetCurrent(null); + mockDeviceInfo = null; + } + + [Test] + public void SetterAndTargetName([Values(false, true)] bool useCompiledXaml) + { + + Assert.DoesNotThrow(() => new Maui16208(useCompiledXaml)); + var page = new Maui16208(useCompiledXaml); + Assert.That(page!.ItemLabel.BackgroundColor, Is.EqualTo(Colors.Green)); + } + } +} \ 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 new file mode 100644 index 000000000000..a361c85fa558 --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs new file mode 100644 index 000000000000..8829a863af5a --- /dev/null +++ b/src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Maui.ApplicationModel; +using Microsoft.Maui.Controls.Core.UnitTests; +using Microsoft.Maui.Controls.Shapes; +using Microsoft.Maui.Devices; +using Microsoft.Maui.Dispatching; + +using Microsoft.Maui.Graphics; +using Microsoft.Maui.UnitTests; +using NUnit.Framework; + +namespace Microsoft.Maui.Controls.Xaml.UnitTests; + +public partial class Maui22001 +{ + public Maui22001() + { + InitializeComponent(); + } + + public Maui22001(bool useCompiledXaml) + { + //this stub will be replaced at compile time + } + + [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); + mockDeviceDisplay = null; + mockDeviceInfo = null; + } + + [Test] + public void StateTriggerTargetName([Values(false, true)] bool useCompiledXaml) + { + var page = new Maui22001(useCompiledXaml); + + IWindow window = new Window + { + Page = page + }; + 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); + } + } +}