Build WPF Data Controls for Outlook Addins Easily with VS2010

Last post I showed how to migrate our Northwind Outlook client to .NET 4 and Office 2010. This Outlook Add-in displays order history information in an adjoining form region so sales associates can see that data immediately when communicating with customers. When we originally built this with Visual Studio 2008, we used a WPF user control to display the data so that we could better match the look and feel of Outlook 2007. However we had to manually figure out the colors we needed and bind the data to the controls by writing XAML by hand.

Today I want to show how we can use drag & drop data binding and the new WPF designer in Visual Studio 2010 to quickly create this control without writing one line of XAML ourselves.

Displaying WPF Controls in Form Regions with ElementHost

Office clients by default work with Windows Forms controls. If you create a new Form Region you can add any Windows form control from the toolbox onto it. If you want to add WPF controls you simply add what’s called an ElementHost control which you find in the WPF Interoperability tab on your toolbox. This allows you to select from and host WPF User Controls in your project.

image

This is the same technique in VS2008 and VS2010. However, VS2010 makes it much easier to create the WPF User Control in the first place because you can now use the data sources window to design and data bind WPF controls. The WPF designer is also much easier to use. Let’s see how we can build a WPF control that displays Order information and have Visual Studio generate all the XAML we need automatically for us, and have it look good too.

Using the Data Sources Window with WPF

Let’s create a new WPF control called OrderHistoryDD where we’ll use drag & drop data binding from the data sources window. Project –> Add New Item, then select User Control (WPF). To view the data sources window select menu Data –> Show Data Sources. Because we already have added a service reference to our data service (which we built in part 1), you will see the client types that were generated for us appear in the data sources window.

image

We want to display three levels of related information to the user. Orders, the related Order Details and the related Product inventory information. If you are familiar with Windows Forms development using the data sources window here is the same. You can select which controls should be dropped onto the design surface by selecting the desired control in the dropdown.

The controls that are available to you depend on which target framework you are using. Since we are targeting .NET Framework 4 then we have some new controls at our disposal namely the WPF DataGrid and the DatePicker.

However, we are only displaying data to the user here (new orders are handled as POs in Word and the editing is done by our shipping department in the Excel client) so we’ll select TextBox controls instead of the DatePicker control. I also only want to display the ShipName, OrderDate, RequiredDate and ShippedDate in the Order grid so for the rest of the fields you can select [None].

Now drag the Orders onto the WPF design surface and you will see those fields in the grid. By default it is anchored to the top and left of the control. Click on the arrow to the right of the grid to also anchor it to the right side so it will stretch.

Since we are not allowing edits, in the Property window for the DataGrid, set the IsReadOnly property to True. Also set the ColumnWidth property to Auto. To modify the order in which the columns appear, click on the ellipsis (…) next to the Columns property to open up the columns editor. Here we can also set the data binding and formatting on our columns by selecting the Binding property and dropping down the editor. You’ll notice that the columns are already bound to the data based on what we specified in the data sources window. Expand Options and you’ll see some additional settings you can make on the Binding. For this example, set the String Format to a simple date for all three date columns.

image

You can also set additional properties here like styles and widths, etc. Since we set the DataGrid Width to Auto, I’ll select each of the column’s widths here, right-click on the Width property and select “Reset value” so that they pick up the same setting. This means the cells will auto size to display all the data.

Now we need to display the related Order Details. To do this, back in the data sources window expand the Order Details under the Orders. It’s very important you select the related Order Details under the Orders otherwise the user won’t see the Order Details change as they select the Order above. This sets up a master-detail binding when we do it this way. This time, just select drop controls as TextBoxes for Quantity and UnitPrice and drop the related Order Details DataDrid under the Orders DataGrid. We also want to display the ProductName here, but because that field is on the Product entity we will have to add a column and set up the binding to it in the Column editor instead.

So click the Columns ellipsis button for the Order Details DataGrid in the Property window and then click the Add button to add a new column. Set the Header property to “Product”. Open up the Binding editor again and expand the Path section. Select Product then ProductName to set up the binding to that field.

image

Select the Bindings for Quantity and UnitPrice and expand the Options node to set the String Format for those columns to display as number and currency respectively.

Now we want to display the inventory information for each Product selected on the Order Details. Same as before, in the data sources window expand the related Product under the Order – Order Details. Select the dropdown on Product and select Details. Then select to drop Labels for just ProductName, UnitsInStock and UnitsOnOrder. Drag the Product onto the form under the Order Details DataGrid and it will create three labels bound to the corresponding fields on Product. It creates a Grid control (not to be confused with DataGrid) with two columns with the field labels on the left and the controls on the right. Dock the entire grid control to the bottom left by selecting the grid and then clicking on the dock arrow at the top. This will dock it to the bottom left. Next add an image control to the bottom right, select the Source property, and select the Northwind traders logo.

image

Notice that we haven’t typed any XAML at all to set any of this up so far. If you look in the generated XAML you will see all the control and data binding definitions. Visual Studio 2010 takes care of all of this for us. But we also want to set up some nice styles on the data grids. Let’s set the alternating row colors and set up a style in the control resources so that we can share it on both DataGrids. We don’t have to know XAML to do this either.

Styling the DataGrids and Sharing Style Resources

image

To quickly set up a nice gradient style shared on both data grids for alternating rows, select a DataGrid and then AlternatingRowBackground in the property window. This opens up the new Brush Editor. Select the third glyph at the top left for gradient. You can select the start and stop gradient colors with the color selector on the left but it’s much easier if you already have an application in mind that has the colors you want, just click the eyedropper in order to pick up any color on your desktop.

After you set up the style how you like it, you can extract it from the DataGrid control into the user control resources so that you can share it on both grids. Click on the diamond next to the AlternatingRowBackground property and select Extract Value to Resource. Name the resource and then click OK to add it to the resources section of the user control.

Now you can go to the other DataGrid, click that AlternatingRowBackground and select “Apply Resource”. Then you can select from SystemColors and Local resources that match. Expand Local and you will see the resource you just created. We could spend all day styling our DataGrids but the nice thing is we can do a lot without having to know XAML.

Hooking Up the Data to CollectionViewSource

Now we need to hook up our WPF user control onto the form region and load it with data from our data service. If you haven’t already done so, build the project and then on the EmailForm select the ElementHost and chose the OrderHistoryDD user control we just built.

We already have written the the code to load the data into a List(Of Orders). The same code we wrote for our VS2008 version here applies in VS2010. The only difference now is that we need to set the List(Of Orders) to the CollectionViewSource.Source property for the master view source, in this case it’s called OrdersViewSource. I’ve talked about CollectionViewSources for master-details before. You can see this in the XAML that was generated for us, as well as the related OrdersOrder_DetailsViewSource:

 <UserControl.Resources>
    <CollectionViewSource x:Key="OrdersViewSource" 
                          d:DesignSource="{d:DesignInstance my:Order, CreateList=True}" />
    <CollectionViewSource x:Key="OrdersOrder_DetailsViewSource" 
                          Source="{Binding Path=Order_Details, 
                                           Source={StaticResource OrdersViewSource}}" />

Because the CollectionViewSources are chained together (similar to how Winforms BindingSources are set up), all we need to do is set the master’s Source property and all the controls will pick up their bindings properly. To make this easier, create a property on the OrderHistoryDD user control that exposes the OrdersViewSource. In the code-behind for the WPF user control:

 Imports System.Windows.Data
Public Class OrderHistoryDD

    Public ReadOnly Property OrdersViewSource As CollectionViewSource
        Get
            Return CType(Me.Resources("OrdersViewSource"), CollectionViewSource)
        End Get
    End Property
End Class

Now in the code-behind for the form region (the FormRegionShowing event handler for EmailForm) change the line of code that sets the DataContext of the old user control and set the OrdersViewSource.Source property instead:

 'Load the data from the service
Dim ordersList As New List(Of Order)(customerOrders)
'Set this ordersList as the DataContext of our WPF user control:
'Me.OrderHistory1.DataContext = ordersList

'In VS2010 CollectionViewSources are used instead:
Me.OrderHistoryDD1.OrdersViewSource.Source = ordersList

Now rebuild and run it to see your lovely new user control appear in Outlook:

image

But something is missing. We want to display the line item and order totals in the grids. We could add a property onto the Order partial class, but we also want to sub-total each of the line items. Instead of adding read-only properties to the partial classes we can write one WPF value converter that handles both of these cases and then use that in our DataGrids.

Writing a Converter to Aggregate Totals

I’ve talked about how to write a converter to format data in controls exactly how you like. In this case we want to write one that will take either a single Order_Detail to calculate the line item total or a collection of Order_Details to calculate the Order total. Here’s one way to do that.

 Imports System.Windows.Data
Imports NorthwindOutlookClient.NorthwindService.NorthwindModel

'Used by the OrderHistory WPF control to display line totals and Order total in the UI
Public Class OrderTotalConverter
    Implements IValueConverter

    Public Function Convert(ByVal value As Object, ByVal targetType As System.Type,
                            ByVal parameter As Object,
                            ByVal culture As System.Globalization.CultureInfo) As Object _
                        Implements System.Windows.Data.IValueConverter.Convert

        Dim total As Double = 0.0
        If TypeOf value Is Order_Detail Then
            'return the line item total
            Dim o = CType(value, Order_Detail)
            total += o.Quantity * o.UnitPrice

        ElseIf TypeOf value Is IEnumerable(Of Order_Detail) Then
            'return the order total
            For Each o As Order_Detail In value
                total += o.Quantity * o.UnitPrice
            Next
        End If
        Return total
    End Function

    Public Function ConvertBack(ByVal value As Object,
                                ByVal targetType As System.Type,
                                ByVal parameter As Object,
                                ByVal culture As System.Globalization.CultureInfo) As Object _
                            Implements System.Windows.Data.IValueConverter.ConvertBack
        Return Nothing
    End Function
End Class

Once you add this class rebuild the project. Now go back to the Order DataGrid and select Columns in the Property window to open the Column editor. Add a new TextBoxColumn, set the Header property to “Total” and then open up the Binding property. Set the Path to Order_Details and then expand Converter and select the NorthwindOutlookClient then OrderTotalConverter. Under Resources select “Create New…” and name and save the OrderTotalConverter in the resources section so you can select it on the Order_Details DataGrid. Expand Options and select the currency String Format.

image

Next Select the Order_Details DataGrid, add a new column, set the Header property to say “Total”. Select the Bindings property but this time don’t select anything for the Path. Expand Converter and select select the NorthwindOutlookClient then OrderTotalConverter and now in the Resources you will see OrderTotalConverter1, select that. Expand Options and select the currency String Format for this column as well.

Now rebuild and run the project again. You can set a breakpoint on the Convert method and you will see that different value types are getting passed in depending on what DataGrid column is requesting the conversion. And best of all, now the user can see line item and order totals in the form region.

image

As you can see Visual Studio 2010 has much improved RAD designers for data binding and styling business applications like this one. I’ve uploaded the source code which works with Visual Studio 2010 Beta 2 and Outlook 2010 Beta onto Code Gallery so have a look.

Enjoy!