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

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

Open
Whiletru3 opened this issue Dec 10, 2024 · 22 comments · May be fixed by #17755

Comments

@Whiletru3
Copy link
Contributor

Whiletru3 commented Dec 10, 2024

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

@Whiletru3 Whiletru3 added the bug label Dec 10, 2024
@ShadowMarker789
Copy link
Contributor

ShadowMarker789 commented Dec 11, 2024

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.

@nsrahmad
Copy link

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

@ShadowMarker789
Copy link
Contributor

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?

@nsrahmad
Copy link

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

@ShadowMarker789
Copy link
Contributor

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.

@Whiletru3
Copy link
Contributor Author

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.

@ShadowMarker789
Copy link
Contributor

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
Copy link
Member

MrJul commented Dec 11, 2024

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

@nsrahmad
Copy link

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

@Whiletru3
Copy link
Contributor Author

Whiletru3 commented Dec 11, 2024

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

@Whiletru3
Copy link
Contributor Author

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

@ShadowMarker789
Copy link
Contributor

ShadowMarker789 commented Dec 11, 2024

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.

@ShadowMarker789
Copy link
Contributor

ShadowMarker789 commented Dec 11, 2024

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?

@ShadowMarker789
Copy link
Contributor

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.

@ShadowMarker789
Copy link
Contributor

@Whiletru3 @ can you try my attempted fix in

#17755

And confirm whether or this this appropriately addresses your issue?

@Whiletru3
Copy link
Contributor Author

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

@ShadowMarker789
Copy link
Contributor

@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.

@Whiletru3
Copy link
Contributor Author

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

@Whiletru3
Copy link
Contributor Author

Hello @ShadowMarker789,

I just test the PR (cloning your repo and branch) in my sample AvaloniaListBoxDemo.zip (as is without modification), and the issue is still there.

There are no Item in the ListBox

Also in my original Sample VirtualizationImagesWithEvents.zip

No images (in the listbox) and no red rectangle (in the ItemsColtrol)

And tried in my real App (using the CI nuget), the issue still there

Did you applied some modification in the samples to get it worked ?

@ShadowMarker789
Copy link
Contributor

Did you applied some modification in the samples to get it worked ?

I must have, because after redownloading and retesting I'm now getting a blank screen same as you.

Looking into it more now...

On the first time the DataContext is set, the Inheritance Children of the MainWindow's DataContext property is null/none.

Which is interesting. And partially explains why I saw different behaviours when I assigned the datacontext before loading the xaml and after loading the xaml.

image
^^ The first round of notifications of the data-context being assigned, with zero children inheriting the data-context ^^

But then after your control (the virtualizationImages:SingleScrollingViewerPanel) joins the logical tree, there's a second round of property changed notifications going through, but it stops abruptly.

image
^^ Second round, the Effective Value of the DataContext has not changed, the system asserts. ^^

And then finally, notification does not traverse the tree, leading to the strange state where the effective value of the list's data-context is correct, but the items-source is incorrect.

image

I'm still yet to pin down EXACTLY what the minimum steps to reproduce this is. I thought I had it before, but you are perfectly correct, my pull request does not resolve the issue.

@ShadowMarker789
Copy link
Contributor

If nothing else, I finally have a failing unit-test.

Should help in getting a fix for this shortly.

image

@ShadowMarker789
Copy link
Contributor

@Whiletru3 - can you check out latest Avalonia Master Branch and repeat your testing? I had some unit-tests that were failing on an older version of the master branch, but they are now passing in the master branch.

When I redownload and relink your project with the Avalonia source latest master I get the following.

image

Is this the issue fixed? Can you confirm fixed on your end?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants