0

I have this scenario: (NOTE CODE CHANGED)

  1. I have a user control that has a bindable property DataSource of type ObservableCollection.
  2. I subscribe to the CollectionChanged event of the Observable collection in my user control.
  3. I also subscribe to the BindingContextChanged event of the user control.
  4. When my page hosting the user control loads, I set the bindable property DataSource to an empty ObservableCollection and I set the BindingContext of control to the same thing. The BindingContextChanged event fires in my user control and the property set executes for my DataSource property.
  5. If I manually add items to my observable collection using the .Add() method, the CollectionChanged event in my user control fires.
  6. However, if I set the bound observable collection to a new observable collection, no events fire in my user control letting me know the contents of the observable collection changed.
  7. If I re-set the binding context property of the user control to the new observable collection, the BindingContextChanged event fires, but my bound DataSource property still does not contain the new list data.

The way my actual application works is that I make call to a server and retrieve the current state of the list as it sits on the server, i.e. a complete replace of the existing list.

The challenge now seems to be that the Set of the DataSource property in my usercontrol is never being called. Any ideas?

Here is my code DataGrid XAML

<?xml version="1.0" encoding="UTF-8"?>
<ContentView xmlns="http://xamarin.com/schemas/2014/forms" 
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ObeservableCollectionSample.DataGrid">
    <ContentView.Content>
        <StackLayout Orientation="Vertical" HorizontalOptions="Center" VerticalOptions="Fill" Spacing="0">
            <ScrollView Orientation="Both" HorizontalOptions="Center" VerticalOptions="Fill" Margin="0,0,0,0" 
                        HorizontalScrollBarVisibility="Default" VerticalScrollBarVisibility="Default" Padding="0,0,0,16" >
                <Grid x:Name="grdDataGrid" RowSpacing="0" ColumnSpacing="0" IsVisible="false" >
                </Grid>
            </ScrollView>
            <Label x:Name="lblNoDataMessage" HorizontalOptions="Center" VerticalOptions="Start" IsVisible="false"/>
        </StackLayout>
    </ContentView.Content>
</ContentView>

Here is the code behind for the user control:

using System.Collections.ObjectModel;
using System.Diagnostics;

using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace ObeservableCollectionSample
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class DataGrid : ContentView
    {

        public static readonly BindableProperty DataSourceProperty = BindableProperty.Create(nameof(DataSource), typeof(ObservableCollection<string>), typeof(DataGrid));
        public ObservableCollection<string> DataSource
        {
            get
            {
                return (ObservableCollection<string>)GetValue(DataSourceProperty);
            }
            set
            {
                SetValue(DataSourceProperty, value);

                if (value == null)
                {
                    //TODO Clear datagrid 
                }
                else
                {
                    //going to change this to subscribe to the collection changed events 
                    DataSource.CollectionChanged += DataSource_CollectionChanged; 
                }
            }
        }

        private void DataSource_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
                Debug.WriteLine("DataSource_CollectionChanged Event Raised");
        }

        public DataGrid()
        {   
            InitializeComponent();
            this.BindingContextChanged += DataGrid_BindingContextChanged;
        }

        private void DataGrid_BindingContextChanged(object sender, System.EventArgs e)
        {
            Debug.WriteLine("DataGrid_BindingContextChanged Event Raised");
        }
    }
}

Then my test page simply contains the user control plus some button to test changing the collection:

Test Page XAML

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:MyDataGrid="clr-namespace:ObeservableCollectionSample;assembly=ObeservableCollectionSample"
             x:Class="ObeservableCollectionSample.MainPage">

    <StackLayout>
        <StackLayout Orientation="Vertical" HorizontalOptions="Fill" VerticalOptions="StartAndExpand">
        <Frame BackgroundColor="#2196F3" Padding="24" CornerRadius="0">
            <Label Text="Welcome to Xamarin.Forms!" HorizontalTextAlignment="Center" TextColor="White" FontSize="36"/>
        </Frame>
                    <MyDataGrid:DataGrid x:Name="grdData" VerticalOptions="FillAndExpand" HorizontalOptions="Center" 
                             DataSource="{Binding DataList}"/>
        </StackLayout>
        <StackLayout Orientation="Horizontal" HorizontalOptions="Center" VerticalOptions="End">
            <Button x:Name="btnAdd1" Text="Add List 1" HorizontalOptions="Center" VerticalOptions="Center" Clicked="btnAdd1_Clicked"/>
            <Button x:Name="btnAdd2" Text="Add List 2" HorizontalOptions="Center" VerticalOptions="Center" Clicked="btnAdd2_Clicked"/>
        </StackLayout>
    </StackLayout>
</ContentPage>

Test page code behind:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xamarin.Forms;

namespace ObeservableCollectionSample
{
    public partial class MainPage : ContentPage
    {
                private ObservableCollection<string> iobj_DataList = new ObservableCollection<string>(); 
    public ObservableCollection<string> DataList
    {
        get
        {
            return iobj_DataList;
        }
        set
        {
            iobj_DataList= value;
            NotifyPropertyChanged();
        }
    } 
        public MainPage()
        {
            InitializeComponent();
            grdData.BindingContext = this;
        }

        private void btnAdd1_Clicked(object sender, EventArgs e)
        {
            iobj_DataList.Add("String 1");
            iobj_DataList.Add("String 2");
            iobj_DataList.Add("String 3");
            NotifyPropertyChanged("DataList");
        }

        private void btnAdd2_Clicked(object sender, EventArgs e)
        {
            List<string> myList = new List<string>();
            myList.Add("String 4");
            myList.Add("String 5");
            myList.Add("String 6");

            //I would like to have this line raise the needed events in my control 
            DataList = new ObservableCollection<string>(new ObservableCollection<string>(myList));

           
        }
    public event PropertyChangedEventHandler PropertyChanged;

    // This method is called by the Set accessor of each property.
    // The CallerMemberName attribute that is applied to the optional propertyName
    // parameter causes the property name of the caller to be substituted as an argument.
    private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
    }
}
George M Ceaser Jr
  • 1,497
  • 3
  • 23
  • 52
  • 1
    `CollectionChanged` only fires when objects are added or removed from a collection. It does not fire when the property pointing to the collection is changed,. For that you should use `PropertyChanged` for the property referencing the collection. – Jason Mar 25 '22 at 16:03
  • Jason, I am not sure exactly what you mean by "For that you should use PropertyChanged for the property referencing the collection". However, I have tried calling NotifyPropertyChanged("iobj_DataList"); from my hosting page and no events fire in my user control – George M Ceaser Jr Mar 25 '22 at 16:14
  • 1
    this is not data binding - `grdData.DataSource = iobj_DataList;`, and `iobj_DataList` is not a C# property so using `PropertyChanged` won't do anything – Jason Mar 25 '22 at 16:21

1 Answers1

1

This can easily be done, without events or data binding.

Your control exposes a property, DataSource.
When you set iobj_DataList, that should become DataSource.
Do this in iobj_DataLists setter.
Then DataSource setter can do whatever you want.

Usage in test page:

public MainPage()
{
    InitializeComponent();
    iobj_DataList = new ObservableCollection<string>();
}

public ObservableCollection<string> iobj_DataList
{
    get => grdData.DataSource;
    set => grdData.DataSource = value;
}

...
    iobj_DataList = new ObservableCollection<string>(myList);

In user control:

public ObservableCollection<string> DataSource
{
    ...
    // This setter runs whenever `grdData.DataSource = value;` runs.
    set
    {
        SetValue(DataSourceProperty, value);

        ...
    }
}

Whenever iobj_DataList is set, its setter sets grdData.DataSource, therefore running DataSource setter. Put whatever logic you need in DataSource setter.

This is plain-old-c#-property-setter logic. Would work even if DataSource was not a BindableProperty.

ToolmakerSteve
  • 18,547
  • 14
  • 94
  • 196