Introduction
One thing that has changed from Windows Forms to WPF is the way we show our data; not only the visual aspect, what really has changed is how we bind our controls to our sources of data and show them to the user.
To illustrate a bit the new paradigm of WPF data binding, we will make a small application (small because between XAML and code, it is no more than 85 lines) that will allow us to display in a datagrid orders from our customers, and by selecting any order, we can see it in a listbox next to the datagrid products included in the order and below the contact information for the customer.
The Code
Preparing the Data Source
In this case, I used the Northwind database in Access to make it easier to work with it. I have included it in the sample project that is at the top of this page, but you can also download it here.
Once you have the database Nwind.mdb, copy it to your project directory, and from the menu Project -> Add Existing Item, select the NWind.mdb file, and Visual Studio 2010 will open the Data Source Configuration Wizard:
Select DataSet and press Next, where the wizard prompts you to select the objects of the database that we want to include in our dataset:
I selected all tables, but our project only needs Customers, Order Details, Orders, Products, and Shippers. Select the tables, give a name to the dataset in the dataset Name textbox and press Finish. The database will be included in our project and Visual Studio will create a typed dataset file (.xsd). If you open it, you'll see something like this (depending on the tables you've selected):
This is a visual representation of your dataset. Each box has two sections, the top is the table with its fields, and the bottom (called like the table + TableAdapter) is the object that we use to retrieve records from that table. You also see lines connecting the tables. These are the relationships between them. If you click on them, they are placed in blue; and remove them all since we will create them in the next step.
Let's create a new relationship between the tables Orders (parent table) and Order Details (child table). For this, press the right mouse button on the header of Order Details, and on the context menu, select Add and click on Relation. You will see a window like this:
Orders and Order Details Relation
|
Property
|
Value
|
Name
|
OrderDetails
|
Parent Table
|
Orders
|
Child Table
|
Order Details
|
Key Columns
|
OrderID
|
Foreign Key Columns
|
OrderID
|
Now create the relationship between the Order Details and Products tables in the same manner and with the following parameters:
Order Details and Products Relation
|
Property
|
Value
|
Name
|
ProductsOrderDetails
|
Parent Table
|
OrderDetails
|
Child Table
|
Products
|
Key Columns
|
ProductID
|
Foreign Key Columns
|
ProductID
|
Also, the relationship between the Customers and Orders tables:
Orders and Customers Relation
|
Property
|
Value
|
Name
|
CustomersOrders
|
Parent Table
|
Orders
|
Child Table
|
Customers
|
Key Columns
|
CustomerID
|
Foreign Key Columns
|
CustomerID
|
And finally, the relationship between the Orders table and Shippers:
Orders and Shippers Relation
|
Property
|
Value
|
Name
|
ShippersOrders
|
Parent Table
|
Orders
|
Child Table
|
Shippers
|
Key Columns
|
ShipVia
|
Foreign Key Columns
|
ShipperID
|
With this, we have finalized the preparation of our data source. Now we will design our window.
Designing our Window
We will design our window. It consists of a grid divided into two rows and two columns. We will use the first row as the title and the second row will have the content. In the first column of the second row, we will insert a datagrid that displays the customer's orders, and in the second column, we will insert controls that display the currently selected order details:
The XAML code for the grid is this:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="75*"></ColumnDefinition>
<ColumnDefinition Width="25*"></ColumnDefinition>
</Grid.ColumnDefinitions>
</Grid>
It's simple, simply stating the definition of rows and columns, with variable or fixed size.
Now we will insert the title of the DataGrid
and the DataGrid
itself, and specify the column and row it belongs to:
<ContentPresenter Grid.Row="0" Grid.Column="0"
VerticalAlignment="Center" Content="Data Grid."/>
<DataGrid Name="grdData" Grid.Row="1" Grid.Column="0"
Margin="5" VerticalAlignment="Stretch"
HorizontalAlignment="Stretch" AutoGenerateColumns="False"
IsSynchronizedWithCurrentItem="True" ItemsSource="{Binding}">
</DataGrid>
As you can see, in my case, I opted not to define the columns automatically (AutoGenerateColumns = "false"
). To define the binding of the columns I want to show, in each of the two controls, I specify the attached properties Grid.Column
and Grid.Row
to place the control where it belongs. In the DataGrid
, I also set the property IsSyncronizedWithCurrentItem
to true
. This property is very important as this is what will enable the other controls to use the currently selected data.
Within the DataGrid
, we must specify the columns we want to show. We include the following XAML code into the DataGrid
:
<DataGrid.Columns>
<DataGridTextColumn Header="Order Code"
Binding="{Binding Path=OrderID}" MinWidth="80"/>
<DataGridTextColumn Header="Customer"
Binding="{Binding Path=CustomersOrders/CompanyName}"
MinWidth="200"/>
<DataGridTextColumn Header="Order Date"
Binding="{Binding Path=OrderDate}" MinWidth="100"/>
<DataGridTextColumn Header="Shipper"
Binding="{Binding Path=ShippersOrders/CompanyName}"
MinWidth="200"/>
</DataGrid.Columns>
And here is where we begin to benefit from the WPF data binding. As you can see, each column has a Binding
property. This property points to the field you want to show. The first column (Order Code) aims directly at the OrderID field in the Orders table, which will be our main DataContext
. It is the same in Order Date, but you will see that Customers and Shippers are different. This is because, using the previously created relations, we are heading our column to the child table and choosing a field of it to show. We don't have to worry about specific queries with joins. We simply use the relationships between the tables to display the fields we need.
Now we create the window part to show the details of the selected order:
<ContentPresenter Grid.Row="0" Grid.Column="1"
VerticalAlignment="Center" Content="Extra Information"/>
<StackPanel Grid.Row="1" Grid.Column="1" Margin="5">
<ContentPresenter Content="Order Details:"></ContentPresenter>
<ListBox Name="lstDetallePedidos" Height="125"
ItemsSource="{Binding Path=OrderDetails}"
DisplayMemberPath="ProductsOrderDetails/ProductName"
IsSynchronizedWithCurrentItem="True" >
</ListBox>
<ContentPresenter Content="Customer Code:"></ContentPresenter>
<TextBox Name="txtCustomerCode"
Text="{Binding Path=CustomerID}"></TextBox>
<ContentPresenter Content="Company:"></ContentPresenter>
<TextBox Name="txtCompany"
Text="{Binding Path=CustomersOrders/CompanyName}"></TextBox>
<ContentPresenter Content="Contact person:"></ContentPresenter>
<TextBox Name="txtContact"
Text="{Binding Path=CustomersOrders/ContactName}"></TextBox>
<ContentPresenter Content="Contact phone:"></ContentPresenter>
<TextBox Name="txtPhone"
Text="{Binding Path=CustomersOrders/Phone}"></TextBox>
</StackPanel>
You see that this in itself is very simple. We used a ContentPresenter
in column 1 row 0 to define the title, and then in row 1 column 1, we introduce a StackPanel
, since we want a simple layout of elements. As in the columns of the DataGrid
, here we bind the Text property of each TextBox
to the field in the Orders table we want to show and use the relationships to display the fields from other tables.
The control that is slightly different is the ListBox
, since in this, we establish two properties for data binding. In ItemsSource
, we establish the relationship OrderDetails. This way, we obtain many items as the relationship between the Orders table and OrderDetails (i.e., if an order contains two OrderDetails, we get those two items). But, to set the property DisplayMemberPath
, we use a second relationship ProductsOrderDetails to bind to the product table and the ProductName field. As we are not interested in showing the product ID selected, which is on the table OrderDetails, we want the name found in the Products table. So, we use the relationship ProductsOrderDetails to move to the Products table for each item of the listbox.
And with this, we have finished designing and preparing our window. Now we only need to write code that makes all this work, and I say only, because it is really simple and easy.
Code Writing
Well, first we have to open the file associated with our XAML code and declare the private members that contain our data and the instances of the TableAdapter
that you use to fill it:
Private dsDatos As New NwindDataSet
Private custAdap As New NwindDataSetTableAdapters.CustomersTableAdapter
Private ordeAdap As New NwindDataSetTableAdapters.OrdersTableAdapter
Private detailAdap As New NwindDataSetTableAdapters.Order_DetailsTableAdapter
Private shipAdap As New NwindDataSetTableAdapters.ShippersTableAdapter
Private prodAdap As New NwindDataSetTableAdapters.ProductsTableAdapter
We define our Dataset dsDatos
and the TableAdapters that we need to populate the tables Customers, Orders, Order_Details, Shippers, and Products.
Now in the constructor of our window, we will load the data and set the data source of our controls:
Public Sub New()
InitializeComponent()
custAdap.Fill(dsDatos.Customers)
detailAdap.Fill(dsDatos.Order_Details)
shipAdap.Fill(dsDatos.Shippers)
prodAdap.Fill(dsDatos.Products)
ordeAdap.Fill(dsDatos.Orders)
Me.DataContext = dsDatos.Orders
End Sub
This code is not complicated. After the call to InitializeComponent
(created automatically by Visual Studio when create the Sub New
of the window), we simply use our TableAdapters to fill the tables in our DataSet dsDatos
.
The most important line of code of our application is the last:
Me.DataContext = dsDatos.Orders
This line sets the data context of our window to the table Orders, which will get all the relationships and the required fields. Here is where the magic of data binding in WPF occurs. In XAML code, each control searches it data source in its parent controls and gets it at the window. This way, all controls that do not specify a datasource share the same source, and with the use of the IsSynchronizedWithCurrentItem
property, if you change the selected item in the Order table, all controls reflect the change automatically.
And ... there is no more code. This is all the code necessary for the window to work perfectly. All you need is to run the application to see the result.
History
- 7 May 2010 - Initial release.