Resource Page DescriptionThis page shows how to get the features of a List (e.g. serializable, complex search capabilities) with the databinding screen updating 'magic' of an ObservableCollection.
The problem
You want to use generic lists (List<T>) to handle collections within your application.
- Because they are serializable (e.g. you can pass a List<T> of data contracts to/from a WCF service)
- They have extensive search capability (e.g. you can find / replace items using Predicates)
- I'm sure there are some other good reasons ...
However, if you databind a List<T> to an ItemSource in a WPF control (e.g. ListView) then adding or updating entries in the
List<T> does not cause the display to update (at least not without a bit of a hack to fool the UI into thinking that the
bound property (DependencyProperty or property using INotifyPropertyChanged) holding the List<T> has changed).
The normal solution is to make the databound property an ObservableCollection<T> (which can take a List<T> in it's constructor).
As you add / remove / update items in this collection within the application, the screen automagically updates.
However, to transfer data back to the service layer you need to copy the items back into a List<T> - this is because the ObservableCollection
constructor simply copies the items in the List<T> to it's own inner list (i.e. it does not retain a reference to the original List<T> passed in).
The solution
We want an ObservableCollection<T> which maintains a reference to the original List<T> passed into it's constructor, then any changes made
to items in this collection will be automatically applied to the List<T>.
We can then simply pass the inner list or a (data contract containing the inner list) back though the service layer to update the database.
Here is the code for a ListWrappingObservableCollection<T>:
using System;
using System.Collections.Generic;
using System.Text;
using System.Collections.ObjectModel;
using System.Reflection;
namespace WpfSamples
{
/// <summary>
/// WPF bindable collection which overrides the behaviour of ObservableCollection which 'copies' the items in a list to it's inner list.<br/>
/// We want the list to remain intact so that any references to it are maintained.
/// </summary>
/// <typeparam name="T"></typeparam>
public class ListWrappingObservableCollection<T> : ObservableCollection<T>
{
public ListWrappingObservableCollection(List<T> innerList) : base()
{
if (innerList == null)
throw new ArgumentNullException("innerList");
// Get a reference to the private items variable in Collection<T> and set it to the passed in List<T>
FieldInfo field = typeof(Collection<T>).GetField("items", System.Reflection.BindingFlags.NonPublic
| System.Reflection.BindingFlags.Instance);
if (field != null)
field.SetValue(this, innerList);
}
/// <summary>
/// Gets the inner list.
/// </summary>
/// <value>The inner list.</value>
public List<T> InnerList
{
get { return this.Items as List<T>; }
}
}
}
When manipulating items in the collection - use the appropriate methods of the ListWrappingObservableCollection itself (not the InnerList) - this will indirectly
update the inner List<T> and trigger the 'magic' which updates the item in the UI.
The following code snippet defines a simplified data contract for an Order which you could use to pass the data needed to maintain an order and it's
items between application layers.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace WpfSamples
{
[Serializable]
[DataContract]
public class Order : INotifyPropertyChanged
{
private int orderId;
[DataMember]
public int OrderId
{
get { return orderId; }
set
{
orderId = value;
OnPropertyChanged("OrderId");
}
}
private bool isDeleted;
[DataMember]
public System.Boolean IsDeleted
{
get { return isDeleted; }
set
{
isDeleted = value;
OnPropertyChanged("IsDeleted");
}
}
private List<OrderItem> orderItems;
[DataMember]
public List<OrderItem> OrderItems
{
get { return orderItems; }
set
{
orderItems = value;
if (orderItems != null)
this.OrderItemsBindable = new ListWrappingObservableCollection<OrderItems>(orderItems);
else
this.OrderItemsBindable = null;
OnPropertyChanged("OrderItems");
}
}
private ListWrappingObservableCollection<OrderItem> orderItemsBindable;
public ListWrappingObservableCollection<OrderItem> OrderItemsBindable
{
get { return orderItemsBindable; }
private set
{
orderItemsBindable = value;
OnPropertyChanged("OrderItemsBindable");
}
}
/// <summary>
/// Called when [property changed].
/// </summary>
/// <param name="name">The name of the property which changed.</param>
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#region INotifyPropertyChanged Members
[field: NonSerialized]
public event PropertyChangedEventHandler PropertyChanged;
#endregion INotifyPropertyChanged Members
}
}
In the UI, simply databind to the OrderItemsBindable property and add / update items in this collection and the OrderItems property will
automatically get updated as appropriate.
The Order data contract can then be passed back through the application layers (e.g. to update a database).