Lists and Notifications

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>.

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.

ObservableCollection<T> members

Figure 4-5. ObservableCollection<T> members

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.

Changing a List<T>

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.

ProductView showing updated reorder level

Figure 4-6. ProductView showing updated reorder level

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.

Changing an ObservableCollection<T>

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.