Entities can implement the INotifyPropertyChanged
interface to
communicate with the target controls to notify them when a property
value has changed. Lists of items can also receive notifications when
the contents of the list have changed. The ObservableCollection<T>
is a
special collection that works well with XAML-based bindings to notify
binding targets when a change has been made to a list of items in the
ObservableCollection<T>
.
The ObservableCollection<T>
, part of the
System.Collections.ObjectModel
namespace, implements both the INotifyPropertyChanged
and INotifyCollectionChanged
interfaces. When a
class inherits from the ObservableCollection<T>
, the derived
class also gets the benefit of the INotifyPropertyChanged
interface. This
allows the derived class to raise the PropertyChanged
event for the ObservableCollection<T>
-derived class
when a property on the collection has changed.
Note
Although this section of the chapter focuses on using the
ObservableCollection<T>
for
list-based binding and notifications, any collection class that
implements the INotifyCollectionChanged
interface will
also reap the benefits discussed here.
Notice that in Figure 4-5, where the members of
ObservableCollection<T>
are
displayed, the PropertyChanged
event is listed as a protected event, which allows classes that
inherit from ObservableCollection<T>
to
implement the event. In fact, most of the members of the ObservableCollection<T>
collection are
protected, as they exist to help
a derived class implement and manage the list of items using
the ObservableCollection<T>
. The only
two public members are the constructor and the CollectionChanged
event.
The ObservableCollection<T>
also
implements the INotifyCollectionChanged
interface. This
interface contains a single member which is the CollectionChanged
event. The ObservableCollection<T>
raises this
event automatically whenever an item in the ObservableCollection<T>
is added or
removed from the list. Binding targets in Silverlight receive these
notifications and allow list-based controls to update themselves upon
receiving a change notification from one of these interfaces when one
of their change events fires.
You can bind list-based controls directly to an object source or
a list of objects, such as a List<T>
or an ObservableCollection<T>
. This is
especially useful for binding a list of objects to a list-based
control, such as the ListBox
or
DataGrid
, or a third-party
list-based control, when these controls must be updated when their
bound lists change. The ObservableCollection<T>
exists to help
fill in the gaps of notifying binding targets where a regular
list-based object does not notify the binding targets of
changes.
For example, Figure 4-6 shows a list of
products contained within a List<T>
. This List<Product>
is set to the DataContext
for the ListBox
’s container control (in this case,
the grid layout control). The ListBox
is loaded with a List<Product>
containing four Product
objects. Example 4-9 shows the four
Product
objects created in the
ProductView.xaml.cs code file. When
items are added to this List<Product>
, no notifications are
sent and the data-bound targets never update the ListBox
contents.
Example 4-9. Creating a list of product entities
C#
private List<Product> CreateProductList()
{
List<Product> products = new List<Product>
{
new Product
{
ProductId = 70, ProductName = "Outback Lager",
QuantityPerUnit = "24 - 355 ml bottles",
UnitsInStock = 15, UnitPrice = 15,
UnitsOnOrder = 10, ReorderLevel = 30,
Discontinued = false
},
new Product
{
ProductId = 71, ProductName = "Flotemysost",
QuantityPerUnit = "10 - 500 g pkgs.",
UnitsInStock = 25, UnitPrice = (decimal)21.5,
UnitsOnOrder = 0, ReorderLevel = 0,
Discontinued = false
},
new Product
{
ProductId = 35, ProductName = "Steeleye Stout",
QuantityPerUnit = "24 - 12 oz bottles",
UnitsInStock = 20, UnitPrice = (decimal)18,
UnitsOnOrder = 0, ReorderLevel = 15,
Discontinued = false
},
new Product
{
ProductId = 53, ProductName = "Perth Pasties",
QuantityPerUnit = "48 pieces",
UnitsInStock = 0, UnitPrice = (decimal)32.80,
UnitsOnOrder = 0, ReorderLevel = 0,
Discontinued = true,
DiscontinuedDate = new DateTime(1996, 7, 4)
}
};
return products;
}
VB
Private Function CreateProductList() As List(Of Product)
Dim products As List(Of Product) = New List(Of Product) _
(New Product() {New Product With _
{.ProductId = 70, .ProductName = "Outback Lager", _
.QuantityPerUnit = "24 - 355 ml bottles", .UnitsInStock = 15, _
.UnitPrice = 15, .UnitsOnOrder = 10, .ReorderLevel = 30, _
.Discontinued = False}, _
New Product With {.ProductId = 71, .ProductName = "Flotemysost", _
.QuantityPerUnit = "10 - 500 g pkgs.", .UnitsInStock = 25, _
.UnitPrice = CDec(21.5), .UnitsOnOrder = 0, .ReorderLevel = 0, _
.Discontinued = False},
New Product With {.ProductId = 35, .ProductName = "Steeleye Stout", _
.QuantityPerUnit = "24 - 12 oz bottles", .UnitsInStock = 20, _
.UnitPrice = CDec(18), .UnitsOnOrder = 0, .ReorderLevel = 15, _
.Discontinued = False}, _
New Product With {.ProductId = 53, .ProductName = "Perth Pasties", _
.QuantityPerUnit = "48 pieces", .UnitsInStock = 0, _
.UnitPrice = CDec(32.80), .UnitsOnOrder = 0, .ReorderLevel = 0, _
.Discontinued = True, .DiscontinuedDate = New DateTime(1996, 7, 4)} _
})
Return products
End Function
When a user clicks the Add Product button, a new Product
instance is created and is added to
the List<Product>
that is
bound to the DataContext
. When this
new Product
instance is created the
List<Product>
expands to
contain it; however, the ListBox
whose ItemsSource
is bound to the
same DataContext
does not add the
new Product
to its items
collection. As far as the user can tell, the Add Product button did
not add a new Product
instance at
all. The code for adding a new Product
instance simply creates a fake new
Product
with some dummy values, as
shown in Example 4-10.
When the Product
is added to
the productList
, which is set to
the DataContext
of the ListBox
, the ListBox
is not made aware of the newly added
item. The ListBox
is fully capable
of adding the new item to its contents without refreshing the list.
However, for the ListBox
to add the
new item to its list of items, the data source list must raise the
CollectionChanged
event. The
CollectionChanged
event is a member
of the INotifyCollectionChanged
interface,
which the ObservableCollection<T>
implements
inherently. The List<T>
does
not implement any notifications on its own.
Note
The complete code for List<T>
is in ProductView.xaml.cs in the sample code
for this chapter.
Example 4-10. Adding a product to the list
C#
private void btnAddProduct_Click(object sender, RoutedEventArgs e)
{
Product product = new Product { Discontinued = false,
DiscontinuedDate = new DateTime(2008, 7, 11), ProductId = 1111,
ProductName = "Test Product", QuantityPerUnit = "1",
ReorderLevel = 10, UnitPrice = 5, UnitsInStock = 20,
UnitsOnOrder = 0 };
productList.Add(product);
}
VB
Private Sub btnAddProduct_Click(ByVal sender As Object, ByVal e As RoutedEventArgs)
Dim product As Product = New Product With {.Discontinued = False, _
.DiscontinuedDate = New DateTime(2008, 7, 11), _
.ProductId = 1111, .ProductName = "Test Product", _
.QuantityPerUnit = "1", .ReorderLevel = 10, .UnitPrice = 5, _
.UnitsInStock = 20, .UnitsOnOrder = 0}
productList.Add(product)
End Sub
When a user selects an item from the ListBox
and clicks the Remove Product
button, the item is indeed removed from the data source List<Product>
that is set to the
DataContext
.
However, once again the List<Product>
does not raise the
CollectionChanged
event, so the
ListBox
does not remove the
selected item from its items collection. The ListBox
’s ItemsSource
property is the target of a
binding operation. The bound item has been changed (an item was
removed from the List<Product>
). However, the data
source in this case does not raise the CollectionChanged
event, so the ListBox
is never told
that one of its items should be removed.
You can modify this example by using an ObservableCollection<T>
instead of a
List<T>
to contain the set of
products. You could modify the code in Example 4-9 slightly to create an
instance of an ObservableCollection<Product>
and to
return that instance from the CreateProductList
method. The code in Example 4-11 shows the first few
lines of the newly revised CreateProductList
method to handle the
ObservableCollection<Product>
.
Example 4-11. Creating an ObservableCollection
C#
private ObservableCollection<Product> CreateProductOC()
{
ObservableCollection<Product> products = new ObservableCollection<Product>
VB
Private Function CreateProductOC() As ObservableCollection(Of Product)
Dim products As ObservableCollection(Of Product) = _
New ObservableCollection(Of Product)
The ObservableCollection<T>
implements the
INotifyCollectionChanged
interface,
which automatically raises the CollectionChanged
event when an item is
added or removed from the
ObservableCollection<T>
’s
internal collection. List-based data-bound targets listen for the
event. When you change the code for this example to use an ObservableCollection<Product>
instead
of a List<Product>
and the
user adds or removes an item
from the ListBox
, the ListBox
listens for the event and adds or
removes the item as directed. For example, if the user clicks on the
Add Product button several times, the ObservableCollection<Product>
will add
several new Product
instances to
the collection, each raising the CollectionChanged
event, which causes the
ListBox
to add the new items to its
items collection. To the user, it simply looks like the Add Product
button works.
When the user selects an item from the ListBox
and clicks the Remove Product
button, the code in Example 4-12 is executed
and the item is removed from the ObservableCollection<Product>
.
The ListBox
listens for the
CollectionChanged
event once again
and updates the ListBox
’s items
collection. To the user, the ListBox
simply removed the selected item. As
you can see, it is very easy to implement the ObservableCollection<T>
and take
advantage of its notifications.
Note
You can find the complete code for ObservableCollection<T>
in ProductViewOC.xaml.cs in the sample
code for this chapter.
Example 4-12. Removing a product
C#
private void btnRemoveProduct_Click(object sender, RoutedEventArgs e)
{
Product product = lstProducts.SelectedItem as Product;
if (product == null)
return;
if (productList.Contains(product))
productList.Remove(product);
}
VB
Private Sub btnRemoveProduct_Click(ByVal sender As Object, _
ByVal e As RoutedEventArgs)
Dim product As Product = TryCast(lstProducts.SelectedItem, Product)
If product Is Nothing Then
Return
End If
If productList.Contains(product) Then
productList.Remove(product)
End If
End Sub
Get Data-Driven Services with Silverlight 2 now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.