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

Compile-time binding of BindingContext should use the inherited x:DataType #21434

Closed
jskeet opened this issue Mar 25, 2024 · 9 comments · Fixed by #21454
Closed

Compile-time binding of BindingContext should use the inherited x:DataType #21434

jskeet opened this issue Mar 25, 2024 · 9 comments · Fixed by #21454
Assignees
Labels
area-xaml XAML, CSS, Triggers, Behaviors fixed-in-8.0.40 fixed-in-9.0.0-preview.4.10690 platform/android 🤖 platform/windows 🪟 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Milestone

Comments

@jskeet
Copy link

jskeet commented Mar 25, 2024

Description

Consider a XAML element which specifies both a BindingContext that uses a binding from its inherited BindingContext, and another dependency property using a Binding:

  • Without specifying x:DataType on the element, both bindings are compiled as if they refer to the inherited binding context data type
  • When specifying x:DataType on the element, both bindings are compiled as if they refer to the newly-specified type

What you actually want is for the binding in BindingContext to be resolved against the inherited BindingContext data type, and all other bindings to be resolved against either the newly-specified x:DataType or (better) against the compile-time type of the new BindingContext.

Steps to Reproduce

  1. Create a new MAUI app called ContextAndType (or adjust the clr-namespace in the examples to match whatever you choose)
  2. Remove all the code in MainPage.xaml.cs other than the constructor
  3. Create a ParentViewModel.cs file containing:
namespace ContextAndType;

public class ParentViewModel
{
    public string Text => "ParentText";
    public ChildViewModel Child { get; } = new ChildViewModel();
}

public class ChildViewModel
{
    public string Text => "ChildText";
    public string Text2 => "ChildText2";
}

Now we can experiment...

Dynamic bindings

Edit MainPage.xaml to:

<?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:ContextAndType"
             x:Class="ContextAndType.MainPage">
    <ContentPage.BindingContext>
        <local:ParentViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding Text}" />
        <Label BindingContext="{Binding Child}" Text="{Binding Text}" />
    </VerticalStackLayout>

</ContentPage>

Note that this has no x:DataType specified, so the bindings are dynamic.

Run the code (I ran it on my local Android device, in the debugger, as well as on my Windows box - I don't know if Debug/Release makes any difference).

Result: Labels of "ParentText" then "ChildText"

In other words, the bindings are behaving as I'd expect:

  • The first Label has Text bound to ParentViewModel.Text
  • The second Label has BindingContext is bound to ParentViewModel.Child
  • The second Label has Text bound to ChildViewModel.Text

There are three warnings of "Binding could be compiled if x:DataType is specified."

Compiled bindings (1)

Just add x:DataType of local:ParentViewModel to the root element:

<?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:ContextAndType"
             x:Class="ContextAndType.MainPage"
             x:DataType="local:ParentViewModel">
    <ContentPage.BindingContext>
        <local:ParentViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding Text}" />
        <Label BindingContext="{Binding Child}" Text="{Binding Text}" />
    </VerticalStackLayout>
</ContentPage>

This still compiles, and runs with the same output. (Note that the Text property exists in both ParentViewModel and ChildViewModel, which is - I believe - why it compiles. Given what comes later though, I'm surprised it doesn't fail at execution time.)

Compiled bindings (2)

Change the second label to use the Text2 property:

<?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:ContextAndType"
             x:Class="ContextAndType.MainPage"
             x:DataType="local:ParentViewModel">
    <ContentPage.BindingContext>
        <local:ParentViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding Text}" />
        <Label BindingContext="{Binding Child}" Text="{Binding Text2}" />
    </VerticalStackLayout>
</ContentPage>

Result: Compile-time failure:

XFC0045 Binding: Property "Text2" not found on "ContextAndType.ParentViewModel".

Compiled bindings (3)

Okay, let's be clear and specify that the type of the BindingContext for the second label is expected to be ChildViewModel, by adding x:DataType to it:

<?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:ContextAndType"
             x:Class="ContextAndType.MainPage"
             x:DataType="local:ParentViewModel">
    <ContentPage.BindingContext>
        <local:ParentViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding Text}" />
        <Label BindingContext="{Binding Child}" Text="{Binding Text2}" x:DataType="local:ChildViewModel" />
    </VerticalStackLayout>
</ContentPage>

Result: Compile-time failure:

XFC0045 Binding: Property "Child" not found on "ContextAndType.ChildViewModel".

Compiled bindings (4)

I've found a workaround: specify the source of the BindingContext Binding:

<?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:ContextAndType"
             x:Class="ContextAndType.MainPage"
             x:DataType="local:ParentViewModel">
    <ContentPage.BindingContext>
        <local:ParentViewModel />
    </ContentPage.BindingContext>

    <VerticalStackLayout>
        <Label Text="{Binding Text}" />
        <Label BindingContext="{Binding Child, Source={RelativeSource AncestorType={x:Type local:ParentViewModel}}}"
               Text="{Binding Text2}" x:DataType="local:ChildViewModel" />
    </VerticalStackLayout>
</ContentPage>

Result: Labels of "ParentText" then "ChildText2".

So this works - but it feels clunky to have to specify it.

Link to public reproduction project repository

https://github.com/jskeet/DemoCode/tree/main/MauiBugDemos/ContextAndType

Version with bug

8.0.7 SR2

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

Android, Windows, I was not able test on other platforms

Affected platform versions

No response

Did you find any workaround?

  1. Specifying the Source for the Binding in the BindingContext
  2. Using dynamic bindings

Relevant log output

No response

@jskeet jskeet added the t/bug Something isn't working label Mar 25, 2024
@jsuarezruiz jsuarezruiz added the area-xaml XAML, CSS, Triggers, Behaviors label Mar 26, 2024
@kevinxufei
Copy link
Collaborator

Verified this issue with Visual Studio 17.10 preview2 (8.0.14 &8.0.3). Can repro on Android platform with sample project(Compiled bindings (2)).

@kevinxufei kevinxufei added s/verified Verified / Reproducible Issue ready for Engineering Triage s/triaged Issue has been reviewed labels Mar 26, 2024
@simonrozsival
Copy link
Member

Hello @jskeet! Thanks for reporting this issue. I can repro this issue also on the current net9.0 branch.

The workaround you found works because in .NET 8, XamlC can't compile bindings with relative source. The binding will be a dynamic binding and not a compiled binding. This should change in .NET 9 and this workaround wouldn't work anymore. You should probably use this code instead:

<VerticalStackLayout>
    <Label Text="{Binding Text}" />
    <Label BindingContext="{Binding Child, x:DataType=local:ParentViewModel}" Text="{Binding Text}" x:DataType="local:ChildViewModel" />
    <!-- also works: -->
    <Label BindingContext="{Binding Child}" Text="{Binding Text2, x:DataType=local:ChildViewModel}" />
</VerticalStackLayout>

@StephaneDelcroix Is this the indended behavior of XamlC? It seems like a bug. BindableObject has special handling for BindableContextProperty and XamlC doesn't seem to behave the same way when it resolves the x:DataType.

@jskeet
Copy link
Author

jskeet commented Mar 26, 2024

@simonrozsival: Thanks for the extra information, that's really useful. I'd looked for a way of specifying a DataType just for the binding, but had missed it. Sounds like that's a better fix - I'll apply it to my real project tonight.

Presumably if I'm binding to multiple properties (and don't want to repeat the ChildViewModel data type) I could use that in the binding for the BindingContext instead, and use x:DataType for the element as a whole?

<Label BindingContext="{Binding Child, x:DataType=local:ParentViewModel}"
            x:DataType="local:ChildViewModel"
            Text="{Binding Text2}"
            Visible="{Binding AnotherPropertyInChildViewModel}" 
/>

Of course, in an ideal world I wouldn't need to specify the data type at all for the label - if BindingContext could infer ParentViewModel as the source of the binding, and then everything else infer ChildViewModel based on the BindingContext binding, that would be great. I don't know if that's even slightly realistic though :)

@simonrozsival
Copy link
Member

I could use that in the binding for the BindingContext instead, and use x:DataType for the element as a whole?

Yes, exactly.

Of course, in an ideal world I wouldn't need to specify the data type at all for the label - if BindingContext could infer ParentViewModel as the source of the binding, and then everything else infer ChildViewModel based on the BindingContext binding, that would be great. I don't know if that's even slightly realistic though :)

That would certainly be great but I'm afraid that's outside of the scope at the moment. @StephaneDelcroix might know more details about future plans.

@simonrozsival simonrozsival self-assigned this Mar 26, 2024
@jskeet
Copy link
Author

jskeet commented Mar 26, 2024

@simonrozsival: I've just tried specifying the DataType in the binding:

        <Label BindingContext="{Binding Child}"
               Text="{Binding Text2, x:DataType=local:ChildViewModel}"/>

This leads to an error:

XLS0413 The property 'DataType' was not found in type 'BindingExtension'.

I'm currently using .NET 8 - is that something that you'd only expect to work in .NET 9? (If so, I'll stick with the dynamic binding as per my original workaround.)

Apologies if I've missed some other change required to make this work.

@simonrozsival
Copy link
Member

@jskeet I tested it on the .NET 9 branch, so it is possible that this doesn't work in .NET 8 (although I was convinced it would work). I will try to test with .NET 8 later today and I will let you know.

@simonrozsival
Copy link
Member

@jskeet It should work with .NET 8.

@PureWeen @mattleibow the XLS0413 error above isn't coming from MAUI. Could this be coming from WinUI XAML parser or from some VS tooling? I'm not seeing it on macOS with VS Code.

@jskeet
Copy link
Author

jskeet commented Mar 26, 2024

Thanks, will try a command line build. The error comes in latest stable VS - can give precise versions when I'm back at my desk if that's useful.

@jskeet
Copy link
Author

jskeet commented Mar 26, 2024

Ah - my mistake, it wasn't a build error, but a VS error that only shows up when the XAML file is in an editor.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-xaml XAML, CSS, Triggers, Behaviors fixed-in-8.0.40 fixed-in-9.0.0-preview.4.10690 platform/android 🤖 platform/windows 🪟 s/triaged Issue has been reviewed s/verified Verified / Reproducible Issue ready for Engineering Triage t/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants