Nested list UI not updating after parent list property changes - Xamarin.Forms

525 views Asked by At

Although this question seems to be asked for multiple times, I can't seem to find the right solution, or put the pieces together, to solve the issue.

I have a CollectionView with inside the collection view a Bindable.Stacklayout.

The nested Stacklayout contains a Checkbox and the visibility of that Checkbox is being set by a property of the parent list (the CollectionView) datasource.

This initially works fine on load, however, as soon as the parent property changes (which also handles the visibility of the nested stacklayout) then the UI of the nested stacklayout is not updated. The question is, how can I achieve this?

The XAML:

<CollectionView
    ItemsSource="{Binding RoomsData}"
    SelectionMode="None">

    <CollectionView.ItemTemplate>
        <DataTemplate>
            <xct:Expander
                x:Name="RoomExpander"
                IsExpanded="{Binding IsExpanded}">
                
                <xct:Expander.Header>
                    <! -- omitted code -->
                </xct:Expander.Header>
                
                <xct:Expander.Content>

                    <StackLayout                                
                        BindableLayout.ItemsSource="{Binding Elements}">
                        
                        <BindableLayout.ItemTemplate>
                            <DataTemplate>
                                <Grid>

                                    <!-- omitted / simplified code a bit for readability -->
                                    <CheckBox IsVisible="{Binding RoomsData.ElementsVisible}" />
                                    
                                </Grid>

                            </DataTemplate>
                        </BindableLayout.ItemTemplate>
                                                        
                    </StackLayout>
                                                
                </xct:Expander.Content>

            </xct:Expander>

        </DataTemplate>
    </CollectionView.ItemTemplate>
</CollectionView>

Model(s):

// Main datasource
public IList<RoomModel> RoomsData
{
    get { return _roomsData; }
    set { SetProperty(ref _roomsData, value); }
}

public class RoomModel : ObservableObject
{
    // Other properties omitted.. 

    private bool _elementsVisible;

    public bool ElementsVisible
    {
        get { return _elementsVisible; }
        set { SetProperty(ref _elementsVisible, value); }
    }

    public IList<ElementModel> Elements { get; set; }
}

public class ElementModel : ObservableObject
{
    // Other properties omitted.. 
}

*SetProperty contains the NotifyPropertyChanged logic

At a certain point the ElementsVisible property changes from false to true or vice versa, I would then expect that the checkboxes of the nested stacklayout changes visibility, but nothing happens. Should I notify the Elements nested datasource to make this happen?

1

There are 1 answers

1
Cherry Bu - MSFT On BEST ANSWER

As Jason's opinion, you have some problems for Xamarin.Forms Relative Bindings, I do one sample that you can take a look. If I change ElementsVisible, the UI will update.

<StackLayout>
        <CollectionView
            x:Name="collectionview"
            ItemsSource="{Binding RoomsData}"
            SelectionMode="None">
            <CollectionView.ItemTemplate>
                <DataTemplate>
                    <Expander x:Name="expander" IsExpanded="{Binding IsExpanded}">
                        <Expander.Header>
                            <Label
                                FontAttributes="Bold"
                                FontSize="Medium"
                                Text="test" />
                        </Expander.Header>
                        <StackLayout BindableLayout.ItemsSource="{Binding Elements}">
                            <BindableLayout.ItemTemplate>
                                <DataTemplate>
                                    <StackLayout>
                                        <!--  omitted / simplified code a bit for readability  -->
                                        <CheckBox IsVisible="{Binding Path=BindingContext.ElementsVisible, Source={x:Reference expander}}" />
                                        <Label Text="{Binding str}" />
                                    </StackLayout>
                                </DataTemplate>
                            </BindableLayout.ItemTemplate>
                        </StackLayout>
                    </Expander>

                </DataTemplate>
            </CollectionView.ItemTemplate>
        </CollectionView>

        <Button
            x:Name="btn1"
            Clicked="btn1_Clicked"
            Text="change data" />
    </StackLayout>
 public partial class Page11 : ContentPage
{
    public ObservableCollection<RoomModel> RoomsData { get; set; }
    public Page11()
    {
        InitializeComponent();
        RoomsData = new ObservableCollection<RoomModel>()
        {
            new RoomModel(){IsExpanded=true,ElementsVisible=true,Elements=new ObservableCollection<ElementModel>(){

                new ElementModel(){str="test 1"},new ElementModel(){str="test 12"},new ElementModel(){str="test 13"}}
            },
             new RoomModel(){IsExpanded=true,ElementsVisible=true,Elements=new ObservableCollection<ElementModel>(){

                new ElementModel(){str="test 2"},new ElementModel(){str="test 21"},new ElementModel(){str="test 23"}}
            },
              new RoomModel(){IsExpanded=true,ElementsVisible=true,Elements=new ObservableCollection<ElementModel>(){

                new ElementModel(){str="test 3"},new ElementModel(){str="test 31"},new ElementModel(){str="test 32"}}
            },
               new RoomModel(){IsExpanded=true,ElementsVisible=true,Elements=new ObservableCollection<ElementModel>(){

                new ElementModel(){str="test 4"},new ElementModel(){str="test 41"},new ElementModel(){str="test 43"}}
            }
        };

        this.BindingContext = this;
    }

    private void btn1_Clicked(object sender, EventArgs e)
    {
        RoomModel room = RoomsData.FirstOrDefault();
        room.ElementsVisible = false;
        collectionview.ItemsSource = RoomsData;
    }
}

public class RoomModel:ViewModelBase
{
    // Other properties omitted.. 
    private bool _IsExpanded;
    public bool IsExpanded
    {
        get { return _IsExpanded; }
        set
        {
            _IsExpanded = value;
            RaisePropertyChanged("IsExpanded");
        }
    }
    private bool _elementsVisible;
    public bool ElementsVisible
    {
        get { return _elementsVisible; }
        set
        {
            _elementsVisible = value;
            RaisePropertyChanged("ElementsVisible");
        }
    }
    public ObservableCollection<ElementModel> Elements { get; set; }
}
public class ElementModel 
{
    public string str { get; set; }
}

The ViewModelBase is class that implementing INotifyPropertyChanged.

 public class ViewModelBase : INotifyPropertyChanged
{
   
    public event PropertyChangedEventHandler PropertyChanged;
   
    public void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}