Populating a Summary List with Data

As you've seen, you populate the DataGrid and ListBox controls in exactly the same manner as one another, by assigning/binding a collection or collection view to their ItemsSource properties.

Let's now look into this topic further, and investigate the type of data that these controls will accept. We'll introduce the ObservableCollection<T> type, and then introduce the concept of collection views that will allow us to manipulate the data displayed in the summary list without affecting the underlying collection. We'll then demonstrate how you can go about wiring these controls up to this data.

images Note A key concept in Silverlight is that a view should “pull” data into itself, rather than have data “pushed” into it from the code-behind. Although Silverlight enables you to “push” data into a view, as a general rule and best practice, you should take the “pull” approach when populating a view with data, which is the sole approach that we'll take from now on. If you're used to “pushing” data into views, a common practice with technologies such as Windows Forms, this will require you to take a different mindset when you are designing your application, and can take a little getting used to. However, after you get the hang of it, you won't want to go back to your old ways.

The ObservableCollection<T> Type

Silverlight supports many of the common generic collection types you might be used to from the full .NET Framework, including List, Dictionary, LinkedList, Stack, and Queue. Other nongeneric collections, such as the ArrayList and Hashtable, aren't implemented in Silverlight, but you can find an appropriate alternative among the generic collections.

However, the most important collection type that you find yourself using in Silverlight is the ObservableCollection<T> collection. This is a generic collection type, unique to Silverlight and WPF, that implements the INotifyCollectionChanged interface, which exposes a CollectionChanged event—raised when items are added or removed from the collection.

This behavior is incredibly useful because Silverlight controls such as the ListBox, DataGrid, and ComboBox listen for this event when their ItemsSource property is bound to an ObservableCollection<T> collection, and automatically update themselves when the collection has changed. This means that you can update the collection in code, and the changes will be automatically propagated to the user interface. So when you add an item to the collection, it will be automatically added to the control's items, and when you remove an item from the collection, it will be automatically removed from the control's items. This means that you don't need to write code to update the control when an item is added to or removed from the collection, and the code that is modifying this collection doesn't actually need to know that the collection is even bound to a control! Therefore, the ObservableCollection<T> collection can help facilitate a clean separation of concerns between layers in your application, making it a key part of Silverlight's data binding ecosystem—particularly when you come to implement the MVVM (Model-View-ViewModel) design pattern, which we'll cover in Chapter 13.

Collection Views

Another important data binding concept related to collections is that of collection views. A collection view is a wrapper around a collection that allows the collection to be manipulated in the user interface, without altering the underlying collection. Think of it as a “view of a collection.” Typical manipulations you might want to perform include filtering, sorting, grouping, and paging the collection's items. For example, you might want to bind a collection to a ListBox, and enable the user to dynamically filter the items displayed by entering some text into a TextBox. Instead of adding/removing items from the bound collection, you can wrap the collection in a collection view, bind the control to this collection view instead of directly to the collection, and then simply apply a filter to the collection view. The collection view will expose only the items from the underlying collection that conform to the filter, and thus, the bound control will only display these items.

Another key feature of collection views is their current record pointer, which tracks the current item in the collection, enabling multiple controls bound to the same collection view to be kept synchronized. Collection views expose a number of methods and properties that you can use to move this pointer and navigate through the items in the collection. We'll investigate this behavior further in Chapter 11, staying focused on the filtering/sorting/grouping/paging features of collection views in this chapter.

images Note Collection views all implement the ICollectionView interface. Some also implement the IEditableCollectionView interface, which allows the underlying collection to be edited via the collection view, and some also implement the IPagedCollectionView interface, which provides paging capabilities to the collection view.

Like the ObservableCollection<T> collection, collection views help facilitate a clean separation of concerns between layers in your application, and are particularly useful when you come to implement the MVVM design pattern, which we'll cover in Chapter 13.

images Note Some controls have built-in behavior that can manipulate the output of collection views. A good example of this is the DataGrid control. It allows you to sort its rows by clicking a column header, and it does this without modifying the source collection. It simply tells the collection view that wraps it how the items should be sorted. If the DataGrid control is bound directly to a collection, it internally wraps it in a collection view, and binds to that instead. Therefore, the DataGrid control can still manipulate the items without modifying the collection that it is bound to.

There are a number of different types of collection views in Silverlight, and the type you use will really depend on the given scenario. Let's take a look at the most common collection views that you will use, and the scenarios in which you might use them.

The ListCollectionView/EnumerableCollectionView Collection Views

The ListCollectionView/EnumerableCollectionView collection views can filter, sort, and group items in their underlying collection in memory. (Note that they don't support paging of the data.) You can't instantiate these collection views directly (their constructors are marked internal), but they can be instantiated with the help of the CollectionViewSource class.

The CollectionViewSource class acts as a collection view “proxy,” which can be used to create a collection view, usually as a means to do so declaratively in XAML. Let's say you want to bind a control to a collection in XAML, but display a filtered/sorted/grouped “view” of that collection, without modifying the collection itself. Wrapping the collection in a collection view enables you to do this. The CollectionViewSource class provides a means of declaratively wrapping a collection in a collection view in XAML, which you can then bind to.

You define the CollectionViewSource as a resource, assign a collection to its Source property, and it will provide you a corresponding collection view from its View property as either a ListCollectionView or an EnumerableCollectionView, depending on the type of collection that is being wrapped, which you can then bind to. The CollectionViewSource class allows you to declaratively specify how the items from the collection should be filtered, grouped, and sorted (but not paged), via properties such as Filter, GroupDescriptions, and SortDescriptions.

images Note You can also use the CollectionViewSource class to create a collection view in code. However, as a general rule, it is primarily used to create collection views declaratively in XAML.

Use the ListCollectionView/EnumerableCollectionView collection views when you encounter one of the following situations:

  • You have all the data loaded client-side.
  • You need to wrap a collection in a collection view declaratively in XAML.
  • You don't need to page the data in the user interface.

The PagedCollectionView Collection View

Unlike the ListCollectionView/EnumerableCollectionView collection views, a PagedCollectionView can be instantiated directly, and also supports paging of the data. A PagedCollectionView is often used when you are following the MVVM design pattern, and the view model wants to have some control over the filtering/sorting/grouping/paging behavior of the collection view.

images Note To use the PagedCollectionView collection view, your project needs a reference to the System.Windows.Data.dll assembly.

Use the PagedCollectionView collection view when you encounter one of the following situations:

  • You have all the data loaded client-side (that is, server-side manipulation of data is not necessary).
  • You'd like some control over how the items are filtered/sorted/grouped/paged in your view model class.
  • You need to page the data in the user interface.

The DomainDataSourceView Collection View

The DomainDataSourceView collection view is exposed by the DomainDataSource control via its DataView property, and provides a view over the collection of entities requested from the server. You normally won't interact directly with this collection view, but it's worth knowing that it exists. You can't create an instance of a DomainDataSourceView object in your code; only the DomainDataSource control can create this collection view (the two are tightly coupled together). You can apply filtering, sorting, grouping, and paging criteria to a DomainDataSource control, and this collection view will display the data accordingly is performed on the server.

The DomainCollectionView Collection View

The DomainCollectionView collection view was introduced as part of the WCF RIA Services Toolkit to help make RIA Services more MVVM friendly. It essentially provides the same behavior as the DomainDataSource control (loading of data via RIA Services, and server-side filtering, sorting, and paging of data), but in a manner that allows for a cleaner separation of concerns between your view and the domain context, such that the view does not need to know anything about how the data is obtained.

images Note To use the DomainCollectionView collection view, you need to have the WCF RIA Services Toolkit installed (discussed in Chapter 4, in the section “WCF RIA Services Toolkit”), and your project needs a reference to the Microsoft.Windows.Data.DomainServices.dll assembly.

Whereas the CollectionViewSource collection view proxy and PagedCollectionView collection view manipulate the data in memory on the client, the DomainCollectionView collection view is a little bit different in that it handles loading data from the server (via RIA Services), and actually manipulates the data on the server, in the same manner as the DomainDataSource control. This is particularly useful when you're paging the data, as the server will return only the data for the current page, saving a lot of network traffic between the server and the client if there are many records on the server. When the client changes the filtering/sorting/grouping of the collection, or wants a new page of data, the DomainCollectionView will request a new page of results from the server, and populates its source collection when the data is returned.

There are three key components involved when using the DomainCollectionView. There's the DomainCollectionView itself, the source collection that it wraps, and a “loader,” which is the class that actually handles the communication with the server and updates the source collection. The DomainCollectionView acts as a bridge between the user interface and this loader. For example, say that the user interface wants a new page of data. The user interface will ask the DomainCollectionView for the new page, which will then ask the loader for the new page, which will request the page of data from the server. When the response is received from the server, the loader will populate the source collection with that data, which gets fed back to the user interface via the DomainCollectionView. The diagram in Figure 6-6 might help you understand the relationship between these components.

images

Figure 6-6. The relationship between the source collection, the loader, and the DomainCollectionView.

The WCF RIA Services Toolkit comes with a default loader implementation, named DomainCollectionViewLoader. This loader merely passes the work of loading the data back to you. It takes method delegates as parameters to its constructor and calls those methods when data needs to be loaded or has been loaded, so it isn't particularly intelligent.

images Note You can create your own custom loader if you want a smarter, less generic loader than the DomainCollectionViewLoader. Kyle McClellan, from the RIA Services team, has information on how to do so on his blog here at http://blogs.msdn.com/b/kylemc/archive/2011/05/13/writing-a-custom-collectionviewloader-for-the-domaincollectionview-and-mvvm.aspx, but we'll stick with the default loader implementation for now.

As the class DomainCollectionViewLoader simply offloads the work of loading the data requested by the user interface back to you, it's up to you to write the logic to load the data from the domain context (demonstrated in Chapter 5). When creating the query to the server, you will need to apply the state of the DomainCollectionView—that is, the sorting/grouping criteria and current page number—to the query. To help you with this, the CollectionViewExtensions class, found in the WCF RIA Services Toolkit, contains some extension methods for the EntityQuery class that can apply the state of the collection view to the query for you. We'll look at how you do this in the DomainCollectionView approach section of the “Paging the Summary List” workshop.

Use the DomainCollectionView collection view when you encounter one of the following scenarios:

  • You are using RIA Services to obtain data from the server.
  • You're following the MVVM design pattern, populating controls with data by binding them to a data exposed from a view model.

Binding a Summary List to a DomainDataSource Control

In Chapter 5, you saw how you could easily wire up a DataGrid control to a DomainDataSource control and populate it with data exposed from the server via RIA Services by dragging an entity from the Data Sources tool window in Visual Studio. (See the section “Consuming Data Declaratively in XAML via the DomainDataSource Control” in Chapter 5.) In summary, you can bind a DataGrid/ListBox control to a DomainDataSource control by binding the DataGrid/ListBox's ItemsSource property to the DomainDataSource control's Data property using ElementName binding, as follows:

<riaControls:DomainDataSource Name="productSummaryDDS"
                      AutoLoad="True"
                      QueryName="GetProductSummaryList"
                      LoadedData="productSummaryDDS_LoadedData">
    <riaControls:DomainDataSource.DomainContext>
        <my:ProductSummaryContext />
    </riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>

<sdk:DataGrid ItemsSource="{Binding ElementName=productSummaryDDS, Path=Data}" />

images Note Populating a view with data from the server by binding controls to a DomainDataSource control is an easy way to prototype an application or get a simple application up and running quickly. However, when it comes to building robust applications, you are generally better off avoiding this approach and obtain data via the DomainCollectionView collection view instead.

Binding a Summary List to a View Model Class

As a best practice, each view will have a “view model” class that will expose data to it, which the view can bind to. This is a key concept in the MVVM design pattern, which will be discussed in depth in Chapter 13. In the meantime, you should still start getting familiar with coding in this manner now, even if you don't understand all the MVVM design pattern concepts yet.

The Basic Process

The process of creating a simple “view model” class and binding your view to it is really quite a straightforward process. Let's say you have a view named ProductListView. Commonly, you'll have a corresponding view model class named ProductListViewModel, which will contain all or most of the logic for the ProductListView view. Most view logic should go into this class instead of being written in the code-behind. The view model class will expose data (via properties) and operations (generally via commands) to the view. An instance of the view model class can then be assigned to its DataContext property, enabling controls in the view to bind to its properties.

images Note Generally, your view model class will inherit from a base view model class that provides logic commonly implemented by view model classes. However, for now we're keeping things simple; we'll look at creating a base view model class in Chapter 13.

Images Workshop: Creating and Binding to the View Model Class

Now that you understand the basic concept of binding a view to a view model, let's look at how you do this in practice.

  1. In the DataGrid/ListBox workshops earlier in this chapter, we (re)created a view named ProductListView.xaml. If you haven't done one of these workshops, do so now, as we'll be using this view and the DataGrid/ListBox control that we configured to display our summary list.
  2. Create a new class named ProductListViewModel in the same folder as the view (the Views folder).
    namespace AdventureWorks.Views
    {
        public class ProductListViewModel
        {
        }
    }
  3. Add the following using statements to the top of the class's file:
    using System.Collections.Generic;
    using AdventureWorks.Web.Services;
    using AdventureWorks.Web.Models;
  4. We want to expose a collection of ProductSummary entities (which we created and exposed from a domain service named ProductSummaryService in Chapter 4) from our view model, so add the following property to the class to do so:
    public IEnumerable<ProductSummary> Products { get; set; }
  5. We need to populate this collection with data from the server. For now, we'll use the ProductSummaryContext domain context to get the complete list of ProductSummary entities from the server. Create a constructor for the class that loads the data and assigns the results to the property, like so:
    public ProductListViewModel()
    {
        var context = new ProductSummaryContext();
        var qry = context.GetProductSummaryListQuery();
        var op = context.Load(qry);
        Products = op.Entities;
    }
  6. Your entire class should be as follows:
    using System.Collections.Generic;
    using AdventureWorks.Web.Services;
    using AdventureWorks.Web.Models;

    namespace AdventureWorks.Views
    {
        public class ProductListViewModel
        {
            public IEnumerable<ProductSummary> Products { get; set; }

            public ProductListViewModel()
            {
                ProductSummaryContext context = new ProductSummaryContext();
                var qry = context.GetProductSummaryListQuery();
                var op = context.Load(qry);
                Products = op.Entities;
            }
        }
    }

    images Note The preceding example demonstrates using RIA Services to access data from the server, but it is equally applicable to whatever approach you might be using. Simply populate the collection with data using your chosen approach.

  7. Your view model class is now ready for use by the view. In the view's constructor (in the code-behind), create an instance of the view model class and assign it to the view's DataContext property, as follows:
    public ProductListView()
    {
        InitializeComponent();

        this.DataContext = new ProductListViewModel();
    }

    images Note You can actually instantiate the ProductListViewModel class and assign it to the view's DataContext property declaratively in XAML. For now, however, we'll do this in the code-behind, as doing so in XAML adds some complexity, such as checks for design-time/runtime mode, and so on. We look at how you can instantiate an object and assign it to the view's DataContext property declaratively in XAML in Chapter 13.

  8. We can now bind controls in our view to this view model. For example, we can bind the ItemsSource property of a DataGrid control to the Products property on the view model, like so:
    <sdk:DataGrid ItemsSource="{Binding Products}" />
  9. Run your application, and navigate to the ProductListView.xaml view. The summary list control will be populated with all the records from the server.

Wrapping the Collection in a Collection View

As you've seen, wrapping a collection in a collection view allows you to display a filtered, sorted, grouped, and paged “view” of the collection without actually modifying the collection. In the previous workshop, the summary list consumed the collection directly, but let's now wrap it in a collection view so we can manipulate the display of the collection in the view. We'll look at how you wrap a collection using each of the collection views detailed earlier. (See those definitions for when you should use each type of collection view.)

Images Workshop: Using the CollectionViewSource Class

As you previously saw, the CollectionViewSource collection view proxy allows you to wrap a collection in a collection view declaratively in XAML. Let's use it to wrap the Products collection exposed by our view model.

  1. Add a resources element to the ProductListView view's root element, like so:
    <navigation:Page.Resources>

    </navigation:Page.Resources>
  2. Define a CollectionViewSource resource for the view, named productCollectionView, and bind its Source property to the Products collection on our view model, as follows:
    <navigation:Page.Resources>
        <CollectionViewSource x:Key="productCollectionView" Source="{Binding Products}" />
    </navigation:Page.Resources>
  3. Now bind the summary list's ItemsSource property to this resource, like this:
    <sdk:DataGrid ItemsSource="{Binding Source={StaticResource productCollectionView}}" />
  4. Now run your application. The summary list should be populated as normal. We'll look at how to filter, sort, and group this data using the collection view in later workshops.

images Note You might have noted that the collection view is being exposed from the CollectionViewSource class's View property, but we're not explicitly binding to its View property. This is because the binding engine recognizes that it is binding to a CollectionViewSource, and assumes that it's not the CollectionViewSource itself that you want to bind to, so it automatically drills down to its View property and binds to that instead. The CollectionViewSource essentially becomes transparent when binding to it, such that you are binding to the collection view that it exposes, rather than the CollectionViewSource itself. You can turn off this View property drilling behavior for the CollectionViewSource by setting the BindsDirectlyToSource property on the property bindings to True.

Images Workshop: Wrapping Data in a PagedCollectionView

To wrap a collection in a PagedCollectionView, you will need to do so in code. Therefore, we'll need to wrap the collection in a PagedCollectionView inside our view model, and expose it as a property from the view model to which the summary list can bind. Let's use it to wrap the Products collection exposed by our view model.

  1. Add a reference to the System.Windows.Data.dll assembly to your project.
  2. Add the following using statement to the top of the ProductListViewModel class's file:
    using System.Windows.Data;
  3. Add a new property to the ProductListViewModel class, named ProductCollectionView, of type PagedCollectionView, like so:
    public PagedCollectionView ProductCollectionView { get; set; }
  4. Now wrap the collection in a PagedCollectionView in the view model's constructor, and assign the result to the ProductCollectionView property, as follows:
    public ProductListViewModel()
    {
        ProductSummaryContext context = new ProductSummaryContext();
        var qry = context.GetProductSummaryListQuery();
        var op = context.Load(qry);
        Products = op.Entities;

        ProductCollectionView = new PagedCollectionView(Products);
    }
  5. In the ProductListView.xaml view, we can now bind the summary list's ItemsSource property to the ProductCollectionView property on the view model, like this:
    <sdk:DataGrid ItemsSource="{Binding ProductCollectionView}" />
  6. Now run your application. The summary list should be populated as normal. We'll look at how to filter, sort, group, and page this data using the collection view in later workshops.
Images Workshop: Obtaining Data via a DomainCollectionView

Like the PagedCollectionView, you will need to configure the DomainCollectionView in code. We'll do this inside our view model, and expose it as a property from the view model to which the summary list can bind. Let's use it to expose a collection of ProductSummary entities from the server from our view model.

  1. Ensure that you have the WCF RIA Services Toolkit (discussed in Chapter 4) installed, and add a reference to the Microsoft.Windows.Data.DomainServices.dll to your Silverlight project.
  2. Add the following using statements to the top of the ProductListViewModel class's file:
    using System.ServiceModel.DomainServices.Client;
    using Microsoft.Windows.Data.DomainServices;
  3. Add a new property to the ProductListViewModel class, named ProductCollectionView, of type DomainCollectionView, like so:
    public DomainCollectionView ProductCollectionView { get; set; }
  4. Add the following field to your view model class:
    private ProductSummaryContext _context = new ProductSummaryContext();

    Since filtering/sorting/grouping/paging the data in the DomainCollectionView will each result in a request to the server, this will maintain a single instance of the domain context for use by each request.

  5. Add the following two methods to your view model class:
    private LoadOperation<ProductSummary> LoadProductSummaryList()
    {
        EntityQuery<ProductSummary> query = _context.GetProductSummaryListQuery();
        return _context.Load(query);
    }

    private void OnLoadProductSummaryListCompleted(LoadOperation<ProductSummary> op)
    {
        if (op.HasError)
        {
            // NOTE: You should add some logic for handling errors here, and mark
            //       the error as handled.
            // op.MarkErrorAsHandled();
        }
        else if (!op.IsCanceled)
        {
            ((EntityList<ProductSummary>)Products).Source = op.Entities;
        }
    }

    The LoadProductSummaryList method performs the request to the server (via the domain context) for data, and the OnLoadProductSummaryListCompleted method populates the source collection with the entities returned from the server. Note that the OnLoadProductSummaryListCompleted method should contain some logic for handling errors (as per the comment), but for the purpose of this workshop, we'll leave exceptions unhandled.

  6. Clear the contents of the view model's constructor, and add the following code (in bold) to it:
    public ProductListViewModel()
    {
        Products = new EntityList<ProductSummary>(_context.ProductSummaries);

        var collectionViewLoader = new DomainCollectionViewLoader<ProductSummary>(
                    LoadProductSummaryList, OnLoadProductSummaryListCompleted);

        ProductCollectionView =
            new DomainCollectionView<ProductSummary>(collectionViewLoader, Products);

        ProductCollectionView.Refresh();

    }

    The preceding code creates a new EntityList<ProductSummary> collection and sets its backing entity set to that exposed from the domain context. It then instantiates the loader object, passing it delegates to the load and load completed methods we created in the previous step. We then instantiate the DomainCollectionView object, passing its constructor the loader and the source collection, connecting the three together. Finally, we call the Refresh method on the DomainCollectionView, which will make it ask the loader to populate the source collection with data from the server.

  7. In the ProductListView.xaml view, we can now bind the summary list's ItemsSource property to the ProductCollectionView property on the view model, like so:
    <sdk:DataGrid ItemsSource="{Binding ProductCollectionView}" />
  8. Now run your application. The summary list should be populated as normal. We'll look at how to filter, sort, group, and page this data using the collection view in later workshops.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset