Skip to content

Commit

Permalink
[X] allow namescope resolution before parenting (#22854)
Browse files Browse the repository at this point in the history
* [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

* fix

* fixes #16208
  • Loading branch information
StephaneDelcroix authored Oct 3, 2024
1 parent 6d1f2a4 commit 87253a5
Show file tree
Hide file tree
Showing 8 changed files with 219 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
8 changes: 7 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,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

/// <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>
30 changes: 30 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?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.Maui16208">
<HorizontalStackLayout x:Name="ItemHorizontalStack" HorizontalOptions="Center" VerticalOptions="Center" Padding="20">
<Label x:Name="ItemLabel"
Text="This is the label inside"
FontSize="18" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroupList>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Red" />
<Setter TargetName="ItemLabel" Property="Label.BackgroundColor" Value="Green" />
</VisualState.Setters>
</VisualState>

<VisualState x:Name="PointerOver">
<VisualState.Setters>
<Setter Property="BackgroundColor" Value="Yellow" />
<Setter TargetName="ItemLabel" Property="Label.BackgroundColor" Value="Black" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateGroupList>
</VisualStateManager.VisualStateGroups>
</HorizontalStackLayout>

</ContentPage>
59 changes: 59 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui16208.xaml.cs
Original file line number Diff line number Diff line change
@@ -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));
}
}
}
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 87253a5

Please sign in to comment.