lorenz albert
lorenz albert

Reputation: 1459

DataTemplateSelector is not executed when items are cleared and added to ObservableCollection

in my .Net Maui App I want to display Data stored in an ObservableCollection using a DataTemplateSelector. Therefore I created an example using Microsoft's documentation. https://learn.microsoft.com/en-us/dotnet/maui/fundamentals/datatemplate?view=net-maui-7.0

I have an ObservableCollection<DataBaseItem> DataBaseItemsFiltered; in a ViewModel. In the View I got a CollectionView to display the Data using a DataTemplateSelector.

<CollectionView ItemsSource="{Binding DataBaseItemsFiltered}"
                        ItemTemplate="{StaticResource DBISelector}">

Everything works as expected until I update the ObservableCollection. This is what it looks like after starting the app. Every item owned by "BestCompany" is highlighted (as expected).

Expected result

But if I update/filter the 'ObservableCollection' (For testing purpose just clearing and adding the originial items)

[RelayCommand]
void Filter()
{
    ...
    DataBaseItemsFiltered.Clear();
    foreach (DataBaseItem item in dataBaseItems)
    {
        DataBaseItemsFiltered.Add(item);
    }                
}

This happens.

enter image description here

This behaviour alternates every time I am calling the Filter() function. I found out that only on start up the custom DataTemplateSelector is called for each Item in the collection. Then it is never called again. However if I change the Filter() function to instantiate a new ObservableCollection

DataBaseItemsFiltered = new(dataBaseItems);

It works and while debugging I can see that everytime the DataTemplateSelector is called (as expected) when I call the filter function.

My question: Does anybody how to fix this? Am I using the ObservableCollection in a way that it is not intended for? Is there a workaround to tell the view that the DataTemplateSelector has to be called for each item in the collection view?

If you need more code just tell me. And thank you in advance.


Edit 19.12.2022 Answering ToolMakerSteve's questions.

  1. Yes DataBaseItemsFiltered is a public property (generated by MVVMToolkit)
[ObservableProperty]
ObservableCollection<DataBaseItem> dataBaseItemsFiltered;
  1. Yes. Just tested
  2. The Filter Function is bound to a SearchBar's SearchCommand

                <SearchBar Grid.Column="1" 
                           SearchCommand="{Binding FilterCommand}" 
                           Text="{Binding CompanyFilterText}"/>

Edit 2 19.12.2022 Answering Liqun Shen-MSFT's questions.

Apparently the ObservableCollection (OC) behaves like this:

  1. If items are added and the count is bigger than it was since instanciation of the OC. The DataTemplateSelector will be triggered.

Example: Deleting 1 item from then adding 2 times to the OC will assign the template of the deleted item to the first item being added (not triggering the DataTemplateSelector). For the second item the DataTemplateSelector is triggered (Correct template is being assigend)

  1. Deleting n items and adding n items will assign the templates LIFO like.

Example: Deleting one item with DataTemplate1. Deleting another item with DataTemplate2. Adding a new item => DataTemplate2 is being assigned (no matter what). Adding a new item => DataTemplate1 is being assigned (no matter what).

Upvotes: 3

Views: 1135

Answers (2)

axa
axa

Reputation: 520

I finally found an answer to this.

The DataTemplateSelector will be forced to be reread if the ItemTemplate property of the CollectionView is nullified and then re set (Using the same DataTemplateSelector instance works fine)

The ObservableCollection assigned to the ItemsSource property of the CollectionView will have a CollectionChanged event. Subscribe to it check if the NotifyCollectionChangedAction = Reset (as is issued upon calling ObservableCollection Clear method) Then in the event handler you can null and set the DataTemplate again as described above like so:

myObservableCollection.CollectionChanged += (sender, notifyCollectionChangedEventArgs) =>
{
    if (notifyCollectionChangedEventArgs.Action == NotifyCollectionChangedAction.Reset)
    {
        ItemTemplate = null;
        ItemTemplate = dataTemplate;
    }
};

Now when the list is repopulated, the items get a new DataTemplate just like it did the first time.

Best of all, this is an elegant solution to something that is surely a bug.

Credit this Q/A giving me the idea

Don't forget to like and subscribe!

Upvotes: 0

lorenz albert
lorenz albert

Reputation: 1459

I found a way to somehow get rid of the problem. As a side note: This problem does not occur on android. Cannot check for iOS and MAC.

So the issue seems to be the DataTemplate (or more precisely its child) used in the DataTemplateSelector.


Working version

If the child inherits from Layout (Grid, VerticalStackLayout etc.), everything will work as expected. No matter what, the DataTemplateSelector is triggered when an item is added to the ObservableCollection.

Example: (Using Grid as ChildElement of the DataTemplate)

<DataTemplate x:Key="DataTemplateDefault_Good">
    <Grid>
        <Frame>
            <HorizontalStackLayout x:DataType="local:SomeItem"
                                    Spacing="30">
                <Label Text="{Binding Id}" />
                <Label Text="{Binding Name}" />
            </HorizontalStackLayout>
        </Frame>
    </Grid>
</DataTemplate>

Version not working

Otherwise (child of DataTemplate inherits from View or ContentView) will show the behaviour I described in my question. DataTemplateSelector only triggers, when an items is added to the ObservableCollection and the size of the ObservableCollection is bigger than it ever was since instantiation.

Example: (Using Frame as ChildElement of the DataTemplate)

<DataTemplate x:Key="DataTemplateDefault_Bad">
    <Frame>
        <HorizontalStackLayout x:DataType="local:SomeItem"
                                Spacing="30">
            <Label Text="{Binding Id}" />
            <Label Text="{Binding Name}" />
        </HorizontalStackLayout>
    </Frame>
</DataTemplate>

I'd appreciate any further explanation to this behaviour. Might this be a bug and shall be reported?

Upvotes: 1

Related Questions