Loading Data and Binding Controls in WPF with CollectionViewSource

Loading Data and Binding Controls in WPF with CollectionViewSource

Rate This
  • Comments 20

When designing WPF Windows with data (or as I usually refer to them WPF "Forms") we have many options on how we want to load the data and bind our controls. Depending on where the data is coming from and how it's being used there are a lot of possibilities.

DataContext "Direct"

Suppose we have the following simple window defined and we've set up data binding on our text boxes to the corresponding properties on a Customer object.

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="152" Width="300" Name="Window1">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="95*" />
            <ColumnDefinition Width="183*" />
        </Grid.ColumnDefinitions>
        <StackPanel Name="StackPanel1" Grid.Column="0">
            <Label Height="28" Name="Label1" Width="Auto">Customer ID</Label>
            <Label Height="28" Name="Label2" Width="Auto">Contact Name</Label>
            <Label Height="28" Name="Label3" Width="Auto">Company Name</Label>
        </StackPanel>
        <StackPanel Name="StackPanel2" Grid.Column="1">
            <TextBox Height="28" Name="TextBox1" Width="Auto" Text="{Binding Path=CustomerID}"/>
            <TextBox Height="28" Name="TextBox2" Width="Auto" Text="{Binding Path=ContactName}"/>
            <TextBox Height="28" Name="TextBox3" Width="Auto" Text="{Binding Path=CompanyName}"/>
        </StackPanel>
    </Grid>
</Window>

One technique to load the data onto the form is to directly set the DataContext property of a Window or container at runtime and all the contained controls will inherit the same DataContext:

Class Window1 

    Private Sub Window1_Loaded() Handles MyBase.Loaded

        'Returns List(Of Customer) and sets the Window.DataContext
        Me.DataContext = CustomerFactory.GetCustomers()

    End Sub
End Class

In our simple example above this means that when we run the form any controls on the window will bind to the Customer object. If we set the StackPanel2.DataContext property, then only the controls in the StackPanel will bind to the Customer object. This is a handy way of setting up binding on containers of controls.

Using ObjectDataProvider

Another way to set up binding is to use an ObjectDataProvider in the Window.References section of our XAML which specifies a method to call on a specific type in our project and uses the results as the source of data. Using this technique loads the data at design time as well.

To do this we remove our code in the Loaded event above and instead just specify the XAML to set up the ObjectDataProvider. First we need to add a namespace for our local project and then we can set up the ObjectDataProvider in the Window's resources section. Finally we specify the binding to the ObjectDataProvider by setting the DataContext of the grid container control. All the controls on the form are contained in this grid:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:WPFCollectionViewSource"  
    Title="Window1" Height="152" Width="300" Name="Window1">
    <Window.Resources>
        <ObjectDataProvider x:Key="CustomerData"
                   MethodName="GetCustomers"
                   ObjectType="{x:Type local:CustomerFactory}"/>
    </Window.Resources>
    <Grid DataContext="{Binding Source={StaticResource CustomerData}}" >
.
.
.
 

This has the immediate effect of loading the data onto our form in the designer.

wpfcolviewsrc1

 

Loading the data into the designer can be a good thing if the data source is a local class contained in the project because it gives visual queues as to what the form will actually look like when it runs. However, if you are accessing data in a database, especially if it's not local to your machine, then this can cause potential performance problems at design time. In this case I recommend loading the data at run time only.

Master-Detail Binding

In this simple example we can set the DataContext directly in the Loaded event like we did initially. But things start to get tricky if you have a more complex form. Consider this master/details form that contains a ListView set up in GridView mode specifying DataTemplates for how to bind the columns (similar to how I showed in this video). One of the columns in this GridView is a Lookup list to the employee table (a foreign key relationship to reference data).

<ListView Name="lstDetails" Grid.Row="1" Grid.ColumnSpan="2"
          IsSynchronizedWithCurrentItem="True">
        <ListView.View>
        <GridView>
            <GridViewColumn Header="Order Date" Width="100">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Name="txtOrderDate" 
                                 Text="{Binding Path=OrderDate}" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Ship Date" Width="100" >
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <TextBox Name="txtShipDate" 
                                 Text="{Binding Path=ShippedDate}" />
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
            <GridViewColumn Header="Employee" Width="115" >
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <ComboBox IsEditable="False" 
                          Name="cboEmployee" 
IsSynchronizedWithCurrentItem="False"
SelectedValue="{Binding Path
=EmployeeID}" DisplayMemberPath="LastName" SelectedValuePath="EmployeeID" /> </DataTemplate> </GridViewColumn.CellTemplate> </GridViewColumn> </GridView> </ListView.View> </ListView>

For a ListView and a Combobox we need to specify the data source of the lists by setting the ItemsSource property. For the ListView it's easy to specify this in the code behind because we have a reference to the lstDetails control. We can also just simply set it here in XAML to the "Orders" collection on the Customer (master). We can set it up this way because my customer objects contain a collection called "Orders" that contain the orders for that customer.

<ListView Name="lstDetails" Grid.Row="1" Grid.ColumnSpan="2"
              IsSynchronizedWithCurrentItem="True"
              ItemsSource="{Binding Path=Orders}">

 

Note that if we're still using the ObjectDataProvider technique then when specifying this ItemsSource on the lstDetails the designer will populate the GridView with data, which is something we probably don't want to do if the data is stored in a remote database. So we could just set the DataContext in the code behind like in the first example, however, we still need to set up the embedded Combobox in that GridView. Unfortunately it's not exposed as a field of the Window class in our code behind because it's a DataTemplate. There are ways to get at this DataTemplate in code but there's a much easier way to set up our binding using what's called a CollectionViewSource.

Using the CollectionViewSource

A CollectionViewSource is a proxy for the CollectionView which manages the currency (the position) in the list of objects (or rows if using a DataTable). It has a property called Source which can be set in our code behind. This way, we can set up CollectionVieSources in XAML for all our data lists and bind them to the corresponding controls all in XAML. Then at runtime in our code we set the Source properties and only at that time does the data pull from the database.

We need three CollectionViewSource's in our Master/Detail example, one for the Customers (MasterView) one for Orders (DetailView) and one for the list of Employees (EmployeeLookup). On the DetailView we specify the Source as the MasterView with a Path set here to 'Orders'. This is similar to how we chain master/detail BindingSources in Winforms development.

So our XAML will be changed to:

<Window x:Class="Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="242" Width="367" Name="Window1">
<Window.Resources>
    <CollectionViewSource x:Key="MasterView" />
    <CollectionViewSource x:Key="DetailView" 
Source="{Binding Source={StaticResource MasterView}, Path='Orders'}"
/> <CollectionViewSource x:Key="EmployeeLookup" /> </Window.Resources> <Grid DataContext="{Binding Source={StaticResource MasterView}}" > .
.
.
<ListView Name="lstDetails" Grid.Row="1" Grid.ColumnSpan="2" IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding Source={StaticResource DetailView}}"> .
.
.
<
ListView.View> <GridView>
.
.
.
<GridViewColumn Header="Employee" Width="115" > <GridViewColumn.CellTemplate> <DataTemplate> <ComboBox IsEditable="False" Name="cboEmployee"
IsSynchronizedWithCurrentItem="False"
SelectedValue="{Binding Path=EmployeeID}" ItemsSource="{Binding Source={StaticResource EmployeeLookup}}" DisplayMemberPath="Name" SelectedValuePath="EmployeeID" Margin="-6,0,-6,0"/> </DataTemplate> </GridViewColumn.CellTemplate> .
.
.

Now we just set the Source properties of our MasterView and EmployeeLookup in our code behind. It's easy to grab references to the CollectionViewSources by accessing the Window.Resources dictionary.

Class Window1 

    Private Sub Window1_Loaded() Handles MyBase.Loaded

        'Returns List(Of Customer) and sets the Master CollectionViewSource,
        ' the DetailView already has it's source set to the MasterView with 
        ' the child path specified in the XAML.
        Dim masterViewSource = CType(Me.Resources("MasterView"), CollectionViewSource)
        masterViewSource.Source = CustomerFactory.GetCustomers()

        'Returns List(Of Employee and sets the EmployeeLookup CollectionViewSource
        Dim employeeViewSource = CType(Me.Resources("EmployeeLookup"), CollectionViewSource)
        employeeViewSource.Source = EmployeeFactory.GetEmployees()

    End Sub

End Class

This separation also allows us to also easily swap out our data source collections at runtime if necessary.

wpfcolviewsrc2

I show an example of how to use CollectionViewSources and create a fully functional master/detail WPF form in this video and this sample so take a look.

Enjoy!

Leave a Comment
  • Please add 1 and 2 and type the answer here:
  • Post
  • Hi Beth,

    Awesome examples! thanks.

    What about many to many relations with a link table.  Any examples / solutions?

    Regards

    Jiri

  • Hi Beth,

    Thanks for this very good article! The binding (using CollectionViewSource) is quite simple for one-to-many relationship scenario. What about many-to-many relations? I tried to implement an example with many-to-many relations. But it doesn't work with this code.

    <CollectionViewSource x:Key="OrgView" />

           <CollectionViewSource x:Key="ContactView"

                                 Source="{Binding Source={StaticResource OrgView},

                                 Path='lnkOrgContact'}" />

    lnkOrgContact is link table between Orgs and Contacts tables.

    Org has no problem but ContactView cannot be loaded by navigation property.

    Can you give us a suggestion or an example with many to many relations?

    thanks,

    Tom

  • Hi beth,

    How about "finding" in scenario using CollectionViewSource binding? How can I implement "Find", "Find next" buttons? I have tried to use ListCollectionView.Filter method. But it doesn't work!

  • Sorry. Filter works for master data but not for details data!

    How can I implement a filter to lookup master record by an attribute of details record?

    Thanks

  • How can we use collectionview to select multiple items?Currentitem is only for single item .Please advise..

Page 2 of 2 (20 items) 12