0

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?

Nicolas
  • 2,277
  • 5
  • 36
  • 82
  • your binding expression is `{Binding RoomsData.ElementsCheckboxVisible}` but your property is named `ElementsVisible` – Jason Apr 26 '21 at 13:14
  • My bad, that is a typo :) Changed the property name when copying it to here for readability. Changed it now. – Nicolas Apr 26 '21 at 13:31
  • even then `RoomsData` does not have any properties, it is just a collection. You may want to look at relative bindings if you want to bind to a property on the parent – Jason Apr 26 '21 at 13:40
  • `RoomsData` does have other properties, but the same goes for here, I omitted those for readability, else I would have had 20+ properties which do not relate to the actual problem. Though, it would be clearer if I added a comment that omitted the other properties so just added that as extra comment inside the model. – Nicolas Apr 26 '21 at 13:49
  • readability is good, but you shouldn't omit code that is specifically related to the question you're asking – Jason Apr 26 '21 at 13:57
  • I still don't see a `ElementsVisible` on `RoomsData`. Do you really mean the `ElementsVisible` on `RoomModel`? – Jason Apr 26 '21 at 14:38
  • Perhaps that is where it goes wrong. I am assuming that `RoomsData` has access to the `ElementsVisible` property since `RoomsData` is a list of `RoomModel` which contains the `ElementsVisible` property. The binding should perhaps not be `RoomsData.ElementsVisible` but `RoomModel.ElementsVisible` (a.k.a. current list item) but how do I accomplish that in XAML (since I am not using a `SelectedItem` binding or something similar)? – Nicolas Apr 26 '21 at 15:28
  • 1
    you need to use a relative binding as I suggested earlier. https://learn.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/data-binding/relative-bindings – Jason Apr 26 '21 at 15:31
  • @Jason thank you for pointing me in the right direction! I tried a few things to get my relative binding to work, but without any result. Do you have an example which I could use in my case? – Nicolas Apr 26 '21 at 18:43
  • 1
    just the ref I posted. I would expect you want to use a Parent, because you are trying to access a property on the binding of the parent to Elements – Jason Apr 26 '21 at 18:46

1 Answers1

1

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));
        }
    }
}
Cherry Bu - MSFT
  • 10,160
  • 1
  • 10
  • 16
  • Thank you a lot for the example! That indeed did the trick. In the end I was Relative binding to the wrong thing. – Nicolas Apr 27 '21 at 12:06