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: <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="95*" /> <ColumnDefinition Width="183*" /> </Grid.ColumnDefinitions> <StackPanel Name="StackPanel1" Grid. <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. <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: <Window.Resources> <ObjectDataProvider x: </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. <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.
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: <Window.Resources> <CollectionViewSource x: <CollectionViewSource x: <CollectionViewSource x: </Window.Resources> <Grid DataContext="{Binding Source={StaticResource MasterView}}" > .
.
.
<ListView Name="lstDetails" Grid. .
.
.
<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!
Join the conversationAdd Comment
PingBack from
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:
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:
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
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
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:
<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..