listviewdata-bindingmaui

MAUI BindingContext does not work when set to a Binding


On my main page I have a MainViewModel that contains some properties, one of which is EffectsCtrl (another view model, instance of EffectsControl class), which contains some logic associated with managing the list of effects. To display this list I have the following ListView on my main page:

<ContentPage
  xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
  xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
  xmlns:local="clr-namespace:Effects"
  x:Class="Effects.MainPage"
  xmlns:vm="clr-namespace:Effects.ViewModel"
  x:DataType="vm:MainViewModel">
  ...
  <ListView
    ItemsSource="{Binding EffectsCtrl.Effects}"
    HasUnevenRows="True"
    VerticalOptions="FillAndExpand">
    <ListView.ItemTemplate>
      ...
    </ListView.ItemTemplate>
  </ListView>
</ContentPage>

The above works fine - list properly displays and updates on changes.

But at some point I changed it to this

<ListView
  BindingContext={Binding EffectsCtrl}
  ItemsSource="{Binding Effects}"
  HasUnevenRows="True"
  VerticalOptions="FillAndExpand">
  ...
</ListView>

and to my great surprise I was shown a tip by my IDE (Visual Studio) that Effects 'Member not found in data context MainViewModel' and upon launching the build failed with an error 'Binding: Property "Effects" not found on "Effects.ViewModel.MainViewModel"'. At this point I was a little confused and curious so I tried some other combinations like

<ListView
  BindingContext={Binding EffectsCtrl}
  ItemsSource="{Binding EffectsCtrl.Effects}"
  HasUnevenRows="True"
  VerticalOptions="FillAndExpand">
  ...
</ListView>

and this compiles fine without warnings from IDE or build errors but the list does not display at all - I assume due to wrong binding because I checked the EffectsCtrl in debugger and everything is fine and elements are added to the list as usual. Also there are no warnings or errors displayed by the debugger.

And this brings me to my question: is this a bug or do I misunderstand something about binding? When I was making the first change I assumed that when I change the ListView's BindingContext it will affect the binding on its ItemsSource field. And I'm even more confused because the 3rd case seems to somewhat confirm my assumption. So can someone confirm this is a bug or provide me with an explanation why the 2nd case is invalid?

EDIT: I did some more testing and when x:DataType="vm:MainViewModel" from the ContentPage declaration is removed the 2nd case works as intended. Why is that?


Solution

  • I found the answer myself in the Compiled bindings documentation. It turns out the x:DataType="vm:MainViewModel" is the problem, although this behavior is absolutely correct and the problem is due to my misunderstanding. This attribute set on the ContentPage causes the compilation of the bindings. And as the abovementioned documentation states

    Set an x:DataType attribute on a VisualElement to the type of the object that the VisualElement and its children will bind to.

    This means that every element in the ContentPage is expected to have BindingContext set to an instance of MainViewModel (unless another x:DataType attribute says otherwise, I assume). Therefore when binding to Effects is used in the 2nd case, even though BindingContext is set to EffectsCtrl, the compiler throws an error - it assumes the BindingContext will be an instance of MainViewModel (and not EffectsControl instance) and it indeed does not contain the Effects property.

    I came with 2 possible solutions to this:

    1st solution:

    Just switch to the runtime validation by removing the x:DataType="vm:MainViewModel" from the ContentPage attributes. But such an action has some negative consequences - you lose the compile-time bindings validation, the runtime validation impacts performance and the IDE won't provide auto-completion (at least Visual Studio). The code then looks like this:

    <ContentPage
      xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:local="clr-namespace:Effects"
      x:Class="Effects.MainPage"
      xmlns:vm="clr-namespace:Effects.ViewModel">
      ...
      <ListView
        BindingContext="{Binding EffectsCtrl}"
        ItemsSource="{Binding Effects}"
        HasUnevenRows="True"
        VerticalOptions="FillAndExpand">
        <ListView.ItemTemplate>
          ...
        </ListView.ItemTemplate>
      </ListView>
    </ContentPage>
    

    The MainViewModel is passed by dependency injection and everything works fine.

    2nd solution:

    To keep the compiled bindings I just added the x:DataType="vm:EffectsControl" attribute to the ListView but then I couldn't use the BindingContext="{Binding EffectsCtrl}" attribute because there is no EffectsCtrl property in the EffectsCtrl itself. So instead of that I wrapped the ListView in a new ContentView class like this:

    <ContentView 
      xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      x:Class="Effects.EffectsListView"
      xmlns:vm="clr-namespace:Effects.ViewModel"
      x:DataType="vm:EffectsControl">
    
      <ListView
        ItemsSource="{Binding Effects}"
        HasUnevenRows="True"
        VerticalOptions="FillAndExpand">
        
        <ListView.ItemTemplate>
          ...
        </ListView.ItemTemplate>
      </ListView>
    </ContentView>
    

    and replaced the list on the main page with the new class

    <ContentPage
      xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:local="clr-namespace:Effects"
      x:Class="Effects.MainPage"
      xmlns:vm="clr-namespace:Effects.ViewModel"
      x:DataType="vm:MainViewModel">
      ...
      <local:EffectsListView BindingContext="{Binding EffectsCtrl}"/>
    </ContentPage>
    

    I find this behavior not very intuitive but now as I understand the concept I think this question is a bit silly. Anyway I hope this helps some lost beginner like myself.