Loading Data and Binding Controls in WPF with CollectionViewSource
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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local ="clr-namespace:WPFCollectionViewSource"
Title="Window1" Height="152" Width="300" Name="Window1">
< Window.Resources ><br> <ObjectDataProvider x:Key ="CustomerData" MethodName
="GetCustomers" ObjectType="{x:Type local:CustomerFactory
}"/><br> </Window.Resources >
<Grid DataContext="{Binding Source={StaticResource CustomerData }}" >
.
.
.
This has the immediate effect of loading the data onto our form in the designer.
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" ><br> <GridViewColumn.CellTemplate ><br> <DataTemplate ><br> <ComboBox IsEditable ="False" Name
="cboEmployee" <br> IsSynchronizedWithCurrentItem="False" SelectedValue="{Binding Path
=EmployeeID}" DisplayMemberPath
="LastName" SelectedValuePath
="EmployeeID" /><br> </DataTemplate ><br> </GridViewColumn.CellTemplate ><br> </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="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="242" Width="367" Name="Window1">
< Window.Resources ><br> <CollectionViewSource x:Key ="MasterView" /><br> <CollectionViewSource x:Key="DetailView" Source="{Binding Source={StaticResource MasterView}, Path='Orders'}"
/><br> <CollectionViewSource x:Key ="EmployeeLookup" /><br></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.
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!
Comments
Anonymous
November 07, 2008
PingBack from http://mstechnews.info/2008/11/loading-data-and-binding-controls-in-wpf-with-collectionviewsource/Anonymous
January 29, 2009
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;Anonymous
March 10, 2009
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 advanceAnonymous
March 19, 2009
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, SimonAnonymous
May 05, 2009
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 gioVhanAnonymous
May 06, 2009
Hi afva, You can use the <Page.Resources> section instead and access it the same way in code as the window example above. Cheers, -BAnonymous
May 06, 2009
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, -BAnonymous
May 08, 2009
The ObservableCollection is a special WPF collection that provides proper notifications to the UI whenAnonymous
May 09, 2009
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. JoshAnonymous
May 11, 2009
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, -BAnonymous
November 08, 2009
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!Anonymous
November 29, 2009
The comment has been removedAnonymous
December 01, 2009
The comment has been removedAnonymous
December 03, 2009
Thanks Beth, this was very helpful.Anonymous
December 14, 2009
The comment has been removedAnonymous
June 07, 2010
Hi Beth, Awesome examples! thanks. What about many to many relations with a link table. Any examples / solutions? Regards JiriAnonymous
November 15, 2010
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, TomAnonymous
November 25, 2010
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!Anonymous
November 25, 2010
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? ThanksAnonymous
January 12, 2011
How can we use collectionview to select multiple items?Currentitem is only for single item .Please advise..