mit
mit

Reputation: 119

ObservableCollection CollectionChanged to update datagrid

I have a DataGrid which is bounded to an ObservableCollection. I have a list of data that is dynamic so items are being edited/added/removed from the list. At first, I was clearing and adding the ObservableCollection but then I found out that I can refresh the ObservableCollection and I need to implement CollectionChanged for that but I am not sure how so if any body can give some pointers or sample codes that would be great.

    private List<OrderList> m_OrderListData = new List<OrderList>();
    public List<OrderList> OrderListData
    {
        get => m_OrderListData;
        private set => Set(ref m_OrderListData, value);
    }

    private ObservableCollection<OrderList> m_OrderListDataCollection;
    public ObservableCollection<OrderList> OrderListDataCollection
    {
        get => m_OrderListDataCollection;
        private set => Set(ref m_OrderListDataCollection, value);
    }

    ...
    ...

    m_OrderListDataCollection = new ObservableCollection<OrderList>(m_OrderListData as List<OrderList>);

    ...
    ...


    foreach (OrderListViewModel Order in OrderList)
    {
        OrderListData.Add(new OrderList(Order.Description, Order.OrderId));
    }

This is what i had before

OrderListData.Clear();
foreach (OrderListViewModel Order in OrderList)
{
      OrderListData.Add(new OrderList(Order.Description, Order.OrderId));
}
m_OrderListDataCollection.Clear();
OrderListData.ToList().ForEach(m_OrderListDataCollection.Add);

XAML

                <Label Content="OrderList"/>
                <DataGrid Name="dgOrderList" 
                          AutoGenerateColumns="False" 
                          ItemsSource="{Binding Path=OrderListDataCollection}" 
                          IsReadOnly="True"
                          SelectionMode="Single"
                          SelectionUnit="FullRow">
                    <DataGrid.Columns>
                        <DataGridTextColumn Width="Auto" Header="ID" Binding="{Binding OrderId}"/>
                        <DataGridTextColumn Width="*" Header="Description" Binding="{Binding OrderDescription}"/>
                    </DataGrid.Columns>
                </DataGrid>

EDIT: OrderList Class

public class OrderList : INotifyPropertyChanged
{

    private string m_OrderDescription;
    private string m_OrderId;

    public string OrderDescription
    {
        get => m_OrderDescription;
        set => Set(ref m_OrderDescription, value);
    }

    public string OrderId
    {
        get => m_OrderId;
        set => Set(ref m_OrderId, value);
    }

    #region Constructor
    public OrderList()
    {
    }
    public OrderList(string description, string id)
    {
        m_OrderDescription = description;
        m_OrderId = id;
    }
    #endregion

    #region INotifyPropertyChanged

    /// <summary>Updates the property and raises the changed event, but only if the new value does not equal the old value. </summary>
    /// <param name="PropName">The property name as lambda. </param>
    /// <param name="OldVal">A reference to the backing field of the property. </param>
    /// <param name="NewVal">The new value. </param>
    /// <returns>True if the property has changed. </returns>
    public bool Set<U>(ref U OldVal, U NewVal, [CallerMemberName] string PropName = null)
    {
        VerifyPropertyName(PropName);
        return Set(PropName, ref OldVal, NewVal);
    }

    /// <summary>Updates the property and raises the changed event, but only if the new value does not equal the old value. </summary>
    /// <param name="PropName">The property name as lambda. </param>
    /// <param name="OldVal">A reference to the backing field of the property. </param>
    /// <param name="NewVal">The new value. </param>
    /// <returns>True if the property has changed. </returns>
    public virtual bool Set<U>(string PropName, ref U OldVal, U NewVal)
    {
        if (Equals(OldVal, NewVal))
        {
            return false;
        }

        OldVal = NewVal;
        RaisePropertyChanged(new PropertyChangedEventArgs(PropName));
        return true;
    }

    /// <summary>Raises the property changed event. </summary>
    /// <param name="e">The arguments. </param>
    protected virtual void RaisePropertyChanged(PropertyChangedEventArgs e)
    {
        var Copy = PropertyChanged;
        Copy?.Invoke(this, e);
    }

    /// <summary>
    /// Raised when a property on this object has a new value.
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    protected virtual void VerifyPropertyName(string PropertyName)
    {
        // Verify that the property name matches a real,
        // public, instance property on this object.
        if (TypeDescriptor.GetProperties(this)[PropertyName] == null)
        {
            string ErrorMsg = "Invalid Property Name: " + PropertyName + "!";

            if (ThrowOnInvalidPropertyName)
            {
                throw new Exception(ErrorMsg);
            }

            Debug.Fail(ErrorMsg);
        }
    }

    /// <summary>
    /// Returns whether an exception is thrown, or if a Debug.Fail() is used
    /// when an invalid property name is passed to the VerifyPropertyName method.
    /// The default value is false, but subclasses used by unit tests might
    /// override this property's getter to return true.
    /// </summary>
    protected virtual bool ThrowOnInvalidPropertyName { get; } = true;

Upvotes: 5

Views: 5841

Answers (3)

Oceans
Oceans

Reputation: 3509

If you just bind the ObservableCollection to the Source of your DataGrid then it should work as intended.
When ever a new item is added or an item is being removed then your view will get notified to update his data.

To track actual changes you do not need to implement a CollectionChanged event of your list, but you'll have to make the actual objects inside the list observable. To make an object observable, you have to implement the INotifyPropertyChangedinterface.
Once the objects are observable, and a property sends out a PropertyChangednotification, the observable collection will catch this.

Here is some quick sample code to get you started:

1. Make your own implementation for an observable object

public class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propName));
        }
    }

    protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null)
    {
        if (Equals(storage, value)) return false;
        storage = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}

2. Have your actual object class extend this implementation, and make sure the necessary properties send out a PropertyChanged notification

public class Order : ObservableObject 
{
    private long _orderId;
    public long OrderId
    {
        get { return _orderId; }
        set { SetProperty(ref _orderId, value); }
    } 

    private string _description;
    public string Description
    {
        get { return _description; }
        set { SetProperty(ref _description, value); }
    } 
}

3. Make sure your ViewModel has an ObservableCollection

(The ViewModel is supposed to be observable as well. Which I hope is already the case for you, else it would be pretty hard to have MVVM work.)

public class MyViewModel : ObservableObject 
{

    public MyViewModel()
    {
        //this is just an example of some test data:
        var myData = new List<Order> {
            new Order { OrderId = 1, Description = "Test1"},
            new Order { OrderId = 2, Description = "Test2"},
            new Order { OrderId = 3, Description = "Test3"}
        };
        //Now add the data to the collection:
        OrderList = new ObservableCollection<Order>(myData);

    }
    private ObservableCollection<Order> _orderList;
    public ObservableCollection<Order> OrderList
    {
        get { return _orderList; }
        set { SetProperty(ref _orderList, value); }
    } 
}

4. Bind the DataGrid's source to the ObservableCollection

<DataGrid Name="dgOrderList" 
        AutoGenerateColumns="False" 
        ItemsSource="{Binding OrderList, Mode=TwoWay}" 
        IsReadOnly="True"
        SelectionMode="Single"
        SelectionUnit="FullRow">
    <DataGrid.Columns>
        <DataGridTextColumn Width="Auto" Header="ID" Binding="{Binding OrderId}"/>
        <DataGridTextColumn Width="*" Header="Description" Binding="{Binding OrderDescription}"/>
    </DataGrid.Columns>
</DataGrid>

Upvotes: 9

Daniele Sartori
Daniele Sartori

Reputation: 1703

You need first to implement INotifyPropertyChanged in your OrderList class

    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));
        }
    }

then you can just use the collection as it is. The CollectionChangedEvent is used if you want to make your custom event, but by default the ObservableCollection already notifies the UI on the change of the number of their items. You just need to notify the UI about the change on the single item

EDIT: Use the collection as a property in a viewmodel that implements also INotifyPropertyChanged

private ObservableCollection<MyItem> _myCollection = new ObservableCollection<MyItem>();

public ObservableCollection<MyItem> MyCollection
{
    get {return _myCollection;}
    set  
        {
           _myCollection = value;
           OnPropertyChanged("MyCollection");
        }
}

Upvotes: 0

DonBoitnott
DonBoitnott

Reputation: 11025

You should be adding to the bound collection directly. Adding to OrderListData doesn't affect the one you are binding to:

OrderListDataCollection.Add(new OrderList(Order.Description, Order.OrderId));

Honestly, the other one seems worthless, at least in as much as it affects your bound control. All it did was initialize the ObservableCollection. It does not serve as an on-going source for data.

Upvotes: 0

Related Questions