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 3 and 3 and type the answer here:
  • Post
  • PingBack from http://mstechnews.info/2008/11/loading-data-and-binding-controls-in-wpf-with-collectionviewsource/

  • Hi Beth, I've spent a full day baffled by what seems very complicated ways of getting a list of items from the code=behind into the Window.Resources to then bind to a control in a DataTemplate.

    Your solution here, that show you can just 'push' it up to the resources using the CollectionViewSource is by far the simplest solution - Great Work!

    My C# solution is :-

    /// pull string form database

    string tariffs = tsUtils.getCRList((string)dr["lists"], "TARIFFS");

    //// split down to cr+lf list

    splitTariffs = tsUtils.fillCRList(tariffs);

    /// create CollectionViews and load them up to xaml resources

    CollectionViewSource viewTariffs = (CollectionViewSource)this.Resources["resTariffs"];

    viewTariffs.Source = splitTariffs;

  • Hello Beth,

    How can I accomplish the same in VB (Or C#) code:

    XAML:

       <CollectionViewSource x:Key="DetailView"                           Source="{Binding Source={StaticResource MasterView}, Path='Orders'}" />

    I want to use this in pageFunctions and than I cannot use <resources>

    Thanks in advance

  • Beth

    Thanks for the great post on drilling into data binding (master - detail). Just what I was looking for (tearing my hair out trying to find).

    Thanks,

    Simon

  • Beth,

    I hope you find sometime looking at this scenario on ObjectDataProvider.

    Okay lets just say i have a textbox bind to a source DSTran; this textbox updates in TwoWay; however i also want to retrieve data based on the inputted value and retrieve from an ObjectDataProvider.  How we could do this?

    I used to retrieve the info using MethodName as based on the example; however; textbox itself belongs to different source?

    Is there any workaround for this?

    Thanks

    gioVhan

  • Hi afva,

    You can use the <Page.Resources> section instead and access it the same way in code as the window example above.

    Cheers,

    -B

  • Hi gioVhan,

    I don't think you can do this purely in XAML but this post may help: http://bea.stollnitz.com/blog/?p=22

    You could always set the MethodName in code.

    HTH,

    -B

  • The ObservableCollection is a special WPF collection that provides proper notifications to the UI when

  • Hi Beth,

      Your videos have always been a great help in getting a grip of the data side of vb.net. I am stuck on a point in your video and tutorials that I can't seem to translate to my scenario. My data is in a dataset with two tables that are related through a foreign key. I can set up the master information in the CollectionViewSource and navigate the views without a problem.

    The problem comes in when I try and mimic your code for the Detail CollectionViewSource. Your source binding is:

    Source="{Binding Source={StaticResource MasterView}, Path='orders'}"

    Where does the orders come from? You said this is possible because you customer object contains a collection called orders. Is this possible to do with a tradition dataset and relations?

    thanks for all the great info.

    Josh

  • Hi Josh,

    The path will be the name of the relation if you are using datasets. In the Dataset designer click on the relation (or hover over it) between the two DataTables and you will see the relation name, probably something like FK_Orders_OrderDetails.

    HTH,

    -B

  • Hi  Beth,

      Thank you for your great videos. I have a question. How can I use filter in the detailView ? Something like here :    

    Me.DetailView = CType(Me.DetailViewSource.View, BindingListCollectionView)

    When I use a preidicate , there is an error said Not Support Methord.   What I want to do is how to show only part  of the  detail data  acoording to some conditon.

    Thanks alot!

  • Beth:

    I asked this question on another template and you may have answered it but I can't find it.

    I have followed the WPF videos "Combo Look-up" and "Master Detail". There is a problem that I am having with both and I don't know if it is my fault or not. After a selection is made on the combo box the detail is still pointing to the first record in the Orders dataSet.

    Do I need to implement filtering?

    Thanks,

  • Hi Roger,

    The lookup sample is not meant as a filter, the combobox is used as a selection of values on the row itself. If you want to use the combobox as the master to filter the detail then you would hook it up to the MasterView CollectionViewSource. Something like:

    <ComboBox IsEditable="False

    IsSynchronizedWithCurrentItem="True"

    ItemsSource="{Binding Source={StaticResource MasterView}}" />

    HTH,

    -B

  • Thanks Beth, this was very helpful.

  • Hi Again Beth:

    I have been trying to do something very simple using WPF for several weeks now and can't seem to get it to work. I have watched your videos and have learned a lot.

    The bread and butter of my simple work is that I would like to use a combo to select a customer. Upon displaying the basic information about that customer I would like to display the orders. (Master-Detail). I am sure that data context and binding is my problem. I am using table adapters to connect to the database.

    Any references or videos would be helpful. Most of the information that I see uses class objects such at Persons, etc. The others that do use databases are quite complex.

    I would like to see how I can do this using the Master-Detail scheme that you used in the video.

    Thanks again

    Roger

Page 1 of 2 (20 items) 12