AvaloniaUI/Avalonia

11.2+ : Binding of ItemsSource in a UserControl children is broken if the DataContext isn't explicitly set in the UserControl

Whiletru3 opened this issue · 20 comments

Describe the bug

In Avalonia 11.2+, there are some regression regarding the DataContext in UserControl.
When the DataContext of a UserControl is heritated, if a child control is an ItemsControl, the ItemsSource binding remain null.

To Reproduce

Using this example (just put it in the sample folder of avalonia and add it in the solution), as is in 11.1.x you can see 20 images (with random different sizes) with one red recrangle in it like this :
image

When imported in 11.2 (here in 11.2.2),
image

The ItemSource of the ListBox is null

In the sample, if you set the DataContext explicitly in the SingleScrollingViewerPanel (the UserControl), it brings back the images

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:model="clr-namespace:VirtualizationImagesWithEvents.ViewModels"
             xmlns:virtualizationImagesWithEvents="clr-namespace:VirtualizationImagesWithEvents"
             xmlns:layout="clr-namespace:VirtualizationImagesWithEvents.Layout"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="VirtualizationImagesWithEvents.SingleScrollingViewerPanel"
             x:DataType="model:MainWindowViewModel"
             x:CompileBindings="True"
             
             >
  <!--todo put this DataContextBinding in the UserControl
  DataContext="{Binding (model:ViewModelLocator).MainWindowViewModel, Source={StaticResource ViewModelLocator}}"
  -->
  <!--The DataContext of the UserControl need to be set otherwise the listbox ItemsSource will remain null-->

BUT not yet the rectangles.
For the rectangles, in the same file, we need to set the DataContext of the LayoutControl and use an ugly trick (a member of the PageviewModel returning this) or remove the original Binding to point :

 // This PageSource (pointing to this) is used only to be able to trigger the DataContext binding
 public PageViewModel PageSource
 {
     get
     {
         return this;
     }
 }
 <layout:LayoutControl x:Name="ctlLayout"
                             HorizontalAlignment="Center"
                             VerticalAlignment="Top"
                             x:DataType="model:PageViewModel"
                             DataContext="{Binding .}"
                             >
   <!--todo put this DataContextBinding in the LayoutControl
       DataContext="{Binding PageSource }"
   -->
   <!--Here we need to re set the DataContext of the control to a member (pointing to the same object this), otherwise the ItemsControl ItemsSource will stay null, or remove the original binding to point-->
 </layout:LayoutControl>

This is a blocker issue for us to go in 11.2+ :-(

Here is the package to reproduce :
VirtualizationImagesWithEvents.zip

Expected behavior

As the Datacontext id Inherited, the listbox shouldn't be empty and the LayoutControl for each page as well. The behavior should be the same as in 11.1.3

Avalonia version

11.2.1 11.2.2

OS

Windows, macOS

Additional context

No response

image

Removing DataContext="{Binding ., Mode=OneWay} from your project makes it work.

image

If DataContext is inherited, there's no need to set it via a binding.

I'm not sure what sort of behaviour you're expecting here, if the data-context changes what things are bound to, how is binding a datacontext without an explicit source going to work? Changing the data-context would prompt bindings to change, but if the datacontext itself is bound would that prompt itself to change itself?

What you are doing sounds very strange to me. Just remove your DataContext binding and everything works fine, because the datacontext is inherited.

Hi,
I also encountered this bug on a feed reader app (not public) I wrote. However, I postponed reporting it because I failed to make a minimal reproduction, and assumed I must be doing something wrong (which might be the case after all).

This is how it looks on avalonia 11.1.4:
Screenshot 2024-12-11 114231

Trying to upgrade to 11.2.2, without any other change to code:
Screenshot 2024-12-11 114705

Hi, I also encountered this bug on a feed reader app (not public) I wrote. However, I postponed reporting it because I failed to make a minimal reproduction, and assumed I must be doing something wrong (which might be the case after all).

This is how it looks on avalonia 11.1.4:

Trying to upgrade to 11.2.2, without any other change to code:

Are you also using a binding on the DataContext itself?

I haven't seen any defined behaviour of what it means to bind a data-context like this. I don't even have a conceptualization of what ought to be expected when you attempt to bind that which changes the meaning of what things point to on bindings.

So, I would perhaps recommend not doing this and not relying on that kind of behaviour.

Can any of y'all find documentation on what the intended behaviour is for Binding a DataContext property?

No, I am not doing anything with DataContext. The previewer is rendering the contorls correctly, yet it is blank when I run it:

Screenshot 2024-12-11 122824


Screenshot 2024-12-11 122940

No, I am not doing anything with DataContext. The previewer is rendering the contorls correctly, yet it is blank when I run it.

If we can get a minimal (as small as one could possibly get) reproduction, then we can get this fixed.

Hi, I also encountered this bug on a feed reader app (not public) I wrote. However, I postponed reporting it because I failed to make a minimal reproduction, and assumed I must be doing something wrong (which might be the case after all).
This is how it looks on avalonia 11.1.4:
Trying to upgrade to 11.2.2, without any other change to code:

Are you also using a binding on the DataContext itself?

I haven't seen any defined behaviour of what it means to bind a data-context like this. I don't even have a conceptualization of what ought to be expected when you attempt to bind that which changes the meaning of what things point to on bindings.

So, I would perhaps recommend not doing this and not relying on that kind of behaviour.

Can any of y'all find documentation on what the intended behaviour is for Binding a DataContext property?

The binding on itself {Binding .} or {Binding} is commonly use in xaml wpf and even in avalonia repository.
We have a workaround yes, but still it is a regression in Avalonia 11.2+.
It was really hard to reproduce it in avalonia and point this Binding.
I'll try to create an even smaller sample.

The binding on itself {Binding .} or {Binding} is commonly use in xaml wpf and even in avalonia repository. We have a workaround yes, but still it is a regression in Avalonia 11.2+. It was really hard to reproduce it in avalonia and point this Binding. I'll try to create an even smaller sample.

This works for me

<ListBox DataContext="{Binding Path=., Mode=OneWay}" ItemsSource="{Binding Path=Items}" />

image

Correctly, and the ItemsSource is populated.

Interestingly in your sample. LstSingleScrolling_OnDataContextChanged never gets called. 🤔

MrJul commented

Could you please try the build from this PR: #17683? It should solve the issue.

Yay! Thnak you @MrJul. I tried #17683 and This indeed fixes this particular issue for me.

Here is a smaller sample to reproduce. See the MyUserControl.axaml

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
             xmlns:viewModels="clr-namespace:AvaloniaListBoxDemo.ViewModels"
             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
             x:Class="AvaloniaListBoxDemo.MyUserControl"
             x:DataType="viewModels:MainWindowViewModel"
             x:CompileBindings="True"
             >

  <!--
  
  In avalonia 11.1.x, this works out of the box
  
  In Avalonia 11.2.x, the ListBox is empty. To get it worked, we need to set the datacontext in the usercontrol .
        DataContext="{Binding (model:ViewModelLocator).MainWindowViewModel, Source={StaticResource ViewModelLocator}}"
  OR remove the DataContext in the listbox
  
  
  The Grid is mandatory to reproduce the issue, otherwise it works with the DataContext="{Binding .}"
  -->


  <Grid>
    <ListBox  x:Name="lstSingleScrolling"
             ItemsSource="{Binding Pages}"
             DataContext="{Binding .}"
             >

      <!--
      
      -->
      <ListBox.DataTemplates>
        <DataTemplate DataType="viewModels:PageViewModel">
            <TextBlock Text="{Binding Name}"></TextBlock>
        </DataTemplate>
      </ListBox.DataTemplates>
    </ListBox>
  </Grid>
</UserControl>

I'll try it in the PR: #17683
AvaloniaListBoxDemo.zip

@MrJul , I applied your changes in BindingExpression.cs in the 11.2.2 tag, but the issue remains.

Okay, I'm reproducing it now.

Yes, I can confirm that the grid is necessary to replicate the issue.

I'm using an even simpler model and data-context, just showing a list of strings.

<UserControl xmlns="https://github.com/avaloniaui"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             x:Class="ControlCatalog.Debugging.Issue1744"
             xmlns:local="clr-namespace:ControlCatalog.Debugging;assembly=ControlCatalog"
             xmlns:system="clr-namespace:System;assembly=System.Runtime"
             x:DataType="local:Issue17744DataContext"
             x:CompileBindings="True"
             >
  <Grid>
    <ListBox DataContext="{Binding Path=., Mode=OneWay}" ItemsSource="{Binding Path=Items}" >
      <ListBox.DataTemplates>
        <DataTemplate DataType="system:String">
          <TextBlock Text="{Binding}"/>
        </DataTemplate>
      </ListBox.DataTemplates>
    </ListBox>
  </Grid>
</UserControl>

But I noticed something interesting.

image

If I swap the InitializeComponent and DataContext assignment it works fine.

How curious! If I assign the data-context first and then load the xaml it works. If I load the xaml and then assign the data-context it doesn't work.

[Edit]
And I can confirm that the event for DataContextChanged does NOT fire when the problem is observed.

I think I've perhaps found the problem...

image

In the above screenshot, the Owner is the Listbox in question.

The Binding the data-context COUNTS as it being set locally

Which means it's not notified that the data-context has changed

Because locally set values override inherited values.

So, it doesn't raise the property-changed notification. 🤔

Locally set values override inherited values. Then how is binding to a DataContext supposed to work?

Further investigation...

image

As soon as Value here is set (line before current) the DataContext of the ListBox is non-null.

image

So we have a state now where the EffectiveValue of a property has been modified but no value-change notification has been sent or evented.

@Whiletru3 @ can you try my attempted fix in

#17755

And confirm whether or this this appropriately addresses your issue?

@ShadowMarker789 : you mention in your PR some test failures. Do I need to test it anyway ?

@ShadowMarker789 : you mention in your PR some test failures. Do I need to test it anyway ?

Those have been addressed. Those were unit-tests that were helpful in making sure I did not break functionality elsewhere.

Please test when you can.

@ShadowMarker789 : I'll test it next monday, can't do it sooner :-/