Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[release/9.0.1xx] [XC] Propagate x:DataType from parent scope to "standalone bindings" #25295

Merged
merged 3 commits into from
Oct 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions src/Controls/src/Build.Tasks/SetPropertiesVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference
bool skipBindingCompilation = hasSource && !context.CompileBindingsWithSource;
if (!skipBindingCompilation)
{
if (TryCompileBindingPath(node, context, vardefref.VariableDefinition, bindingExtensionType.Value, isStandaloneBinding: bpRef is null, out var instructions))
if (TryCompileBindingPath(node, context, vardefref.VariableDefinition, bindingExtensionType.Value, out var instructions))
{
foreach (var instruction in instructions)
yield return instruction;
Expand Down Expand Up @@ -421,7 +421,7 @@ public static IEnumerable<Instruction> ProvideValue(VariableDefinitionReference
}

//Once we get compiled IValueProvider, this will move to the BindingExpression
static bool TryCompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt, (string, string, string) bindingExtensionType, bool isStandaloneBinding, out IEnumerable<Instruction> instructions)
static bool TryCompileBindingPath(ElementNode node, ILContext context, VariableDefinition bindingExt, (string, string, string) bindingExtensionType, out IEnumerable<Instruction> instructions)
{
instructions = null;

Expand Down Expand Up @@ -456,13 +456,6 @@ static bool TryCompileBindingPath(ElementNode node, ILContext context, VariableD
{
break;
}
else if (isStandaloneBinding)
{
// For standalone bindings we don't allow inheriting the x:DataType from its parents.
// A standalone binding is a binding instance which is not immediately applied through `SetBinding(...)`
// but it is applied later (for example it is applied to items in a collection).
break;
}

if (n.XmlType.Name == nameof(Microsoft.Maui.Controls.DataTemplate)
&& n.XmlType.NamespaceUri == XamlParser.MauiUri)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void Setup()
public void ItemDisplayBindingWithoutDataTypeFails([Values(false, true)] bool useCompiledXaml)
{
if (useCompiledXaml)
Assert.Throws(new BuildExceptionConstraint(12, 13, s => s.Contains("0022", StringComparison.Ordinal)), ()=> MockCompiler.Compile(typeof(Maui23989), null, true));
Assert.Throws(new BuildExceptionConstraint(12, 13, s => s.Contains("0045", StringComparison.Ordinal)), ()=> MockCompiler.Compile(typeof(Maui23989), null, true));

var layout = new Maui23989(useCompiledXaml);
//without x:DataType, bindings aren't compiled
Expand Down
43 changes: 43 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui25141.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?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"
xmlns:local="clr-namespace:Microsoft.Maui.Controls.Xaml.UnitTests"
x:DataType="local:Maui25141ViewModel"
x:Class="Microsoft.Maui.Controls.Xaml.UnitTests.Maui25141">
<ContentPage.Resources>
<MultiBinding
x:Key="MultiBinding"
Mode="TwoWay">
<Binding Path="Text"/>
</MultiBinding>

<DataTrigger
x:Key="DataTrigger"
Binding="{Binding TriggerFlag}"
TargetType="Label"
Value="True">
<Setter Property="Text" Value="TRIGGERED" />
</DataTrigger>
</ContentPage.Resources>

<StackLayout>
<Label x:Name="LabelA">
<Label.Text>
<MultiBinding Mode="TwoWay">
<Binding Path="Text"/>
</MultiBinding>
</Label.Text>
</Label>

<Label x:Name="LabelB">
<Label.Triggers>
<DataTrigger
Binding="{Binding TriggerFlag}"
TargetType="Button"
Value="True">
<Setter Property="Text" Value="TRIGGERED" />
</DataTrigger>
</Label.Triggers>
</Label>
</StackLayout>
</ContentPage>
86 changes: 86 additions & 0 deletions src/Controls/tests/Xaml.UnitTests/Issues/Maui25141.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
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;

[XamlCompilation(XamlCompilationOptions.Skip)]
public partial class Maui25141 : ContentPage
{
public Maui25141()
{
InitializeComponent();
BindingContext = new Maui25141ViewModel
{
Text = "Hello, Maui!",
TriggerFlag = true
};
}

public Maui25141(bool useCompiledXaml)
{
//this stub will be replaced at compile time
}

[TestFixture]
class Test
{
[SetUp]
public void Setup()
{
Application.SetCurrentApplication(new MockApplication());
DispatcherProvider.SetCurrent(new DispatcherProviderStub());
}

[TearDown] public void TearDown()
{
AppInfo.SetCurrent(null);
DeviceInfo.SetCurrent(null);
}

[Test]
public void BindingsInDataTriggerAndMultiBindingAreCompiledCorrectly()
{
MockCompiler.Compile(typeof(Maui25141), treatWarningsAsErrors: true);
}
}
}

public class Maui25141ViewModel : INotifyPropertyChanged
{
private bool _triggerFlag;
public bool TriggerFlag
{
get => _triggerFlag;
set
{
_triggerFlag = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TriggerFlag)));
}
}

private string _text;
public string Text
{
get => _text;
set
{
_text = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Text)));
}
}

public event PropertyChangedEventHandler PropertyChanged;
}