Skip to content

Commit

Permalink
[X] allow namescope resolution before parenting
Browse files Browse the repository at this point in the history
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
  • Loading branch information
StephaneDelcroix committed Sep 11, 2024
1 parent 0548772 commit cb38846
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<VariableDefinition, IList<string>>(namescopeVarDef, namesInNamescope);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Controls/src/Core/Element/Element.cs
Original file line number Diff line number Diff line change
Expand Up @@ -502,13 +502,15 @@ public bool EffectIsAttached(string name)
return false;
}

internal INameScope transientNamescope;

/// <summary>Returns the element that has the specified name.</summary>
/// <param name="name">The name of the element to be found.</param>
/// <returns>The element that has the specified name.</returns>
/// <exception cref="InvalidOperationException">Thrown if the element's namescope couldn't be found.</exception>
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);
Expand Down
5 changes: 5 additions & 0 deletions src/Controls/src/Xaml/CreateValuesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,9 @@
<Compile Include="..\Core.UnitTests\MockMauiContext.cs" />
<Compile Include="..\Core.UnitTests\MockServiceProvider.cs" />
<Compile Include="..\Core.UnitTests\MockFontManager.cs" />
<Compile Include="..\Core.UnitTests\MockDeviceDisplay.cs" />
</ItemGroup>

<Import Project="$(MauiSrcDirectory)Maui.InTree.props" Condition=" '$(UseMaui)' != 'true' " />

</Project>
</Project>
34 changes: 34 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui22001">

<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup>
<VisualState x:Name="Portrait">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Portrait" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.IsVisible" TargetName="_firstGrid" Value="true"/>
<Setter Property="Grid.IsVisible" TargetName="_secondGrid" Value="false"/>
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Landscape">
<VisualState.StateTriggers>
<OrientationStateTrigger Orientation="Landscape" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Property="Grid.IsVisible" TargetName="_firstGrid" Value="false"/>
<Setter Property="Grid.IsVisible" TargetName="_secondGrid" Value="true"/>
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
<Grid x:Name="_firstGrid" HeightRequest="200" BackgroundColor="Yellow" />
<Grid x:Name="_secondGrid" HeightRequest="200" BackgroundColor="Red"/>
</Grid>
</ContentPage>
70 changes: 70 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui22001.xaml.cs
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

0 comments on commit cb38846

Please sign in to comment.