• Beth Massi - Sharing the goodness

    Microsoft Beth 2009 - Has it really been two years!?

    • 17 Comments

    Last week marked the two year anniversary of my employment at Microsoft. Wow! I completely forgot about it -- I had to go back to my blog archive and look it up ;-) Time flies when you're having fun, I guess. I've really enjoyed my time here so far and I hope that I can continue to do my best for the Microsoft developer community.

    I can't tell you how many friends I've made here and in the community and how much I've learned. Microsoft is truly an awesome company and I work with the best minds in the industry. It's overwhelming sometimes working here but it's so worth it to see the fruits of your labor really pay off.  And it's incredible to have this kind of impact in the world. I hope that my work continues to help developers out there.

    Many thanks to all of you -- the awesome developers in the community helping each other make it through the challenges we face building applications that make people's lives that much better. Let's continue being excellent to each other and here's to another year!

    Thanks!

  • Beth Massi - Sharing the goodness

    "How Do I" Videos Now Linked to Visual Studio Help

    • 3 Comments

    Today Kathleen posted on her blog that the Visual Studio help topics in the library now contain links to related How Do I videos making it much easier for you to find related video content while reading the library articles. For instance, if you're reading the Overview of LINQ to XML in Visual Basic topic you'll see links to related How Do I videos that I did in the center of the page.

    Cool, thanks Kathleen!

    Enjoy!

  • Beth Massi - Sharing the goodness

    Channel 9 Interview: No More Underscores in Visual Basic 10!

    • 4 Comments

    I just posted a new Channel 9 interview on a nifty little feature which isn't so little when you look at it from the compiler. In this interview, Tyler Whitney, a developer on the Visual Basic compiler team demonstrates how line termination has changed in the Visual Basic 10 compiler making underscores unnecessary as line continuation indicators. Woot!

    This makes your code more readable especially when writing multi-line LINQ queries. It's also really handy not to have to worry about them when writing multi-line lambdas, a new feature of Visual Basic 10. Check out Tyler's blog post that explains the details.

    Yes it's true, many underscores are unemployed so the team has created a support group for them at www.unemployedunderscores.com :-)

    Enjoy!

  • Beth Massi - Sharing the goodness

    Visual Basic QuickStarts and How-to Topics for the Composite Application Guidance for WPF and Silverlight

    • 4 Comments

    I just popped a headline onto the Visual Basic Developer Center (which also appears on the VS start page for all of us that have selected the VB development settings :-)) about the release of Visual Basic QuickStarts and How-to Topics for the Composite Application Guidance for WPF and Silverlight (formerly known as "Prism"). Cool, more things to play with!

    Here's an excerpt from the download page:

    The Composite Application Guidance for WPF and Silverlight is designed to help you more easily build enterprise-level Windows Presentation Foundation (WPF) and Silverlight client applications. It will help you design and build enterprise-level composite WPF client applications—composite applications use loosely coupled, independently evolvable pieces that work together in the overall application.

    This download includes QuickStarts, the Composite Application Library, and documentation. This download is provided to help the Visual Basic developer use the Composite Application Library. The documentation includes:

    • Visual Basic Content for Composite Application Guidance for WPF and Silverlight - February 2009.chm: The QuickStarts, Hands-On Labs, and How-to Topics in Visual Basic.
    • Composite Application Guidance for WPF and Silverlight - February 2009.chm: Complete documentation in C#.
    • Composite Application Library Reference February 2009.chm: Library reference API.

    You can post your feedback on www.codeplex.com/prism.

    Enjoy!

  • Beth Massi - Sharing the goodness

    Channel 9 Interview: WPF Improvements in VS2010 for Business Apps

    • 2 Comments

    I just posted an interview on Channel 9 on WPF Improvements in Visual Studio 2010 for Building Business Apps.

    At the MVP Summit a few weeks ago, Italian MVP Alessandro del Sole was so excited about the new WPF features in Visual Studio 2010 that he asked me if he could interview Milind Lele himself to get the low-down -- I said sure!

    Milind is a Program Manager on the Visual Studio Pro Tools team. In this interview he chats with Alessandro about the new WPF improvements in Visual Studio 2010 for building business applications. This time Milind shows off the new Visual Studio UI and the new WPF data grid as he quickly builds a master-details form against an Entity Data Model using the new RAD drag-drop data binding features for WPF.

    Also check out Milind's blog post on this topic for more information.

    Enjoy!

  • Beth Massi - Sharing the goodness

    Channel 9 Interview: Using the Touchless SDK with Visual Basic

    • 0 Comments

    I just posted a new interview on Channel 9 on using the Touchless SDK.

    The Touchless SDK enables developers to create multi-touch based applications using a webcam for input. "Touch without touching." It's actually pretty cool! In this interview I meet up with Touchless SDK creator, Mike Wasserman and Visual Basic Spec Lead, Lucian Wischik. They explain the project and show off a game they developed using Visual Basic 9 in Visual Studio 2008. I almost got my block knocked off playing the game but you'll see how it was worth it ;-)

    For additional information mentioned in the video check out:

    Enjoy!

  • Beth Massi - Sharing the goodness

    The Low Bandwidth Version of the MSDN Library

    • 7 Comments

    This weekend Mary Lee posted on the VB Team blog some tips for navigating the MSDN Library. One thing she mentioned that was interesting to me (that I didn't even know about) is that there is a low bandwidth version of the library online that you can use. Take any library URL and place "(loband)" in the page name, i.e: http://msdn.microsoft.com/en-us/library/cc488280(loband).aspx

    Then you can persist this by clicking on the link at the top of the page:

    msdnloband

    This removes the large table of contents that displays on the left hand side and should help out those developer communities on low bandwidth connections. Give it a try and let us know if it works.

    Enjoy!

  • Beth Massi - Sharing the goodness

    OBA Part 4 - Building an Excel Client against LOB Data

    • 2 Comments

    In my last few app building posts we've been building a simple Office Business Application (OBA) for the new Northwind Traders. If you missed them:

    (In the last post on OBA I mentioned we would build out the SharePoint Workflow next but I realized that it's important to see how the Excel client works before we write the workflow, so that will be the next post.)

    In this post I want to focus on how we can build an Excel client for our shipping department. This will be a customized Excel spreadsheet that lists the orders that need to be shipped and allows users to enter the ship dates and save them back to the database.

    OBAExcel1

    We'll also generate a pivot table and chart so that we can visualize the percentages of cities that we're shipping our products. This is one of the key advantages of building an Excel client. Not only does it lend itself well for editing tabular data, it also makes it very easy to provide additional analysis of that data.

    The Excel Document Customization

    I've been building upon the the Visual Studio Solution that we started in the first post on OBA when we built the data service. So just add a new project to the solution (File -> Add -> New Project) and select Office 2007 Excel Workbook which is available in Visual Studio 2008 Professional and higher: 

    OBAExcelClient1 

    Since we're building a document customization and not an Add-In this means that our code will only run on the specific document as opposed to running in the Excel application over any document that is opened. After we add the project we're asked if we want to import an existing Excel workbook or create a new one. For this example we'll want to create a new one named the same name as our project, NorthwindClientExcel.xlsx. After we specify this, our project is created and the Excel designer opens.

    Next step is to add the data service reference just like we did for the Outlook Add-In. Right-click on the project, select Add Service Reference. Click the Discover button and it should find our ADO.NET data service in the solution called NorthwindDataService. Name the service reference NorthwindService.

    Data Binding in Excel

    Now that we have our service reference we can retrieve the list of Order objects that have not been shipped yet and display them in the first sheet of our workbook. But we'll also want to show the Order Details to the shipping department and display the Quantity and Product that should be shipped. We'll do this using an Excel Action Pane by setting up a master-detail view of the data. So as the user scrolls through the Order rows, the Action Pane will display the Order Details. The user only needs to make changes to the Order rows itself so this design will work well.

    The magic control we use in Excel to bind data to rows is called the ListObject. This object can easily bind to a Winforms BindingSource just like any other Winforms control. So to set up our data source so we can bind to a BindingSource we'll want a BindingList(Of Order) so that list change notifications are communicated to the UI. We'll also need to augment the Order partial class on the client so that property change notifications are also sent.

    So first create a new class named Order and place it in the same Namespace as the NorthwindService service reference we just added. This is where the client proxy objects are generated for us so in order to extend these classes with our own code we just need to get it into the same Namespace. (Here's a Visual Basic tip, you know you've got it right if you see the Order properties and methods in the Declarations drop down above the editor for the Order class.) We need to fire a change notification on all of the properties of our Order so I've placed code to do this in all the xxxChanged() partial methods like so :

    Imports NorthwindExcelClient.NorthwindService
    Imports System.ComponentModel
    
    Namespace NorthwindService
    
        Partial Public Class Order
            Implements INotifyPropertyChanged
    
            Public Event PropertyChanged(ByVal sender As Object, _
                                         ByVal e As PropertyChangedEventArgs) _
                                         Implements INotifyPropertyChanged.PropertyChanged
    
            Private Sub FirePropertyChanged(ByVal propertyName As String)
                If propertyName <> "" Then
                    RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(propertyName))
                End If
            End Sub
    
            Private Sub OnShippedDateChanged()
                FirePropertyChanged("ShippedDate")
            End Sub
    
            Private Sub OnOrderDateChanged()
                FirePropertyChanged("OrderDate")
            End Sub
    
            'FirePropertyChanged is called from all xxxChanged() partial methods.
            ' I excluded the rest of the code here for clarity....
    .
    .
    .
    End Class
    End Namespace

    Next we need to create a BindingList(Of Order). I'm going to create a class called MyOrdersList that inherits from this which will also encapsulate the calls to the ADO.NET Data Service. MyOrdersList will use the data service context on the client (NorthwindService in our case) to retrieve and update the Orders. We don't want to allow adds or deletes in this application because orders are going through a workflow and our shipping department is only updating the information on an order, specifically the ShippedDate. So we'll need to override AddNewCore and RemoveItem to prevent this. We'll also override the OnListChanged so that we can tell the data service context to mark the Order entity as updated.

    We also need a couple housekeeping fields for saving and for exposing a HasChanges property. The interesting code to notice here is that we're specifying a MergeOption on the data service client to OverwriteChanges after we save as opposed to the default AppendOnly. This means that after we save our changes it will refresh the existing Order entity instances with changes from the database.

    Imports NorthwindExcelClient.NorthwindService
    Imports System.ComponentModel
    
    Public Class MyOrderList
        Inherits BindingList(Of Order)
    
        Private DataServiceContext As New NorthwindEntities(New Uri(My.Settings.ServiceURI))
        Private _hasChanges As Boolean
        Private _isSaving As Boolean
    
        Public ReadOnly Property HasChanges() As Boolean
            Get
                Return _hasChanges
            End Get
        End Property
    
        Sub New()
            MyBase.New()
            Me.DataServiceContext.MergeOption = Services.Client.MergeOption.OverwriteChanges
        End Sub
    
        ''' <summary>
        ''' Do not allow adds.
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Protected Overrides Function AddNewCore() As Object
            Throw New NotSupportedException("You cannot add items to the list.")
            Return Nothing
        End Function
        ''' <summary>
        ''' Do not allow deletes.
        ''' </summary>
        ''' <param name="index"></param>
        ''' <remarks></remarks>
        Protected Overrides Sub RemoveItem(ByVal index As Integer)
            Throw New NotSupportedException("You cannot remove items from the list.")
        End Sub
        ''' <summary>
        ''' When a property is updated on an order, then we need to
        ''' tell the data services client to mark it as updated.
        ''' </summary>
        ''' <param name="e"></param>
        ''' <remarks></remarks>
        Protected Overrides Sub OnListChanged(ByVal e As System.ComponentModel.ListChangedEventArgs)
            If Not _isSaving Then
                'The Order partial class is implementing INotifyPropertyChanged so this will work
                If e.ListChangedType = ListChangedType.ItemChanged Then
                    Me.DataServiceContext.UpdateObject(Me(e.NewIndex))
                    _hasChanges = True
                End If
                MyBase.OnListChanged(e)
            End If
        End Sub

    Before we get into the code that will retrieve and save the orders, let's bind this to a ListObject and get it onto our Excel worksheet. Since we're going to have to interact with the list of orders from our Sheet1 as well as the Action Pane displaying the details, I'm going to create a property on the Workbook itself that exposes the MyOrdersList. Double-click on the ThisWorkbook in the project and switch to the Code view:

    Public Class ThisWorkbook
    
        'This is the data source we'll be working with on the Sheet and Action Pane.
        ' This class communicates with the ADO.NET Data Service
        Private _orderList As MyOrderList
        Public ReadOnly Property OrderList() As MyOrderList
            Get
                If _orderList Is Nothing Then
                    _orderList = New MyOrderList()
                End If
                Return _orderList
            End Get
        End Property

    Now build the project and open the Data Sources window.  Add a new Object Data Source and select MyOrderList then Finish. Drag the MyOrderList onto the designer for Sheet1, just drop it into the first cell, and it will generate all the columns for every property on the Order. You can also change the name of it on the Design tab -- I'll change ours to OrderListObject.

    OBAExcelClient2

    Unfortunately it drops all the columns regardless if we select a drop target of none or not. Right click on the column and select Delete from the drop-down menu to delete a column (don't press the delete key). I'm going to remove the first three and the last three columns that it dropped. You'll also want to set any date fields you're displaying in the sheet to Text format. This is to work around a bug in the ListObject where it won't let you change date values if the formatting isn't set to text. You do this by selecting the column then right-click, select Format Cells, and then select Text from the list.

    We can be explicit about setting our data source and binding to columns in the startup event handler:

    Public Class Sheet1
    
        Private Sub Sheet1_Startup() Handles Me.Startup
            Dim myApp = NorthwindExcelClient.Globals.ThisWorkbook
            Me.OrderBindingSource.DataSource = myApp.OrderList
    
            Me.OrderListObject.SetDataBinding(Me.OrderBindingSource, "", _
                                              "ShippedDate", _
                                              "Freight", _
                                              "ShipName", _
                                              "ShipAddress", _
                                              "ShipCity", _
                                              "ShipRegion", _
                                              "ShipPostalCode", _
                                              "ShipCountry")
    
            Me.OrderListObject.AutoSelectRows = True
    
            myApp.LoadOrders()
        End Sub

    Also notice the last line calls a method on ThisWorkbook to load the orders. Let's see how we can do this by writing a query against our ADO.NET data service.

    Querying and Saving Orders via our Data Service

    Now we can return back to our MyOrdersList and write a method for returning the list of orders that have not yet been shipped. Here I call the service to return a list of unshipped orders, add them all to MyOrderList and then raise the list changed event once to notify the Excel ListObject to refresh it's UI:

     ''' <summary>
    ''' Queries the open orders from the data service 
    ''' and returns the number of orders returned in the results
    ''' </summary>
    ''' <returns>number of orders returned from the service</returns>
    ''' <remarks></remarks>
    Public Function LoadOrders() As Integer
        Dim isLoaded = True
        Me.Clear()
    
        Me.RaiseListChangedEvents = False
    
        Dim orders = From o In DataServiceContext.Orders _
                     Where o.ShippedDate Is Nothing _
                     Order By o.OrderDate
    
        For Each o In orders
            Me.Add(o)
        Next
    
        Me.RaiseListChangedEvents = True
        Me.OnListChanged(New ListChangedEventArgs(ListChangedType.Reset, 0))
        Return Me.Count
    End Function

    We're also going to need a way to load the order details, shipper and product entities when requested. This will happen as the user selects rows of data -- we need to display these details in our Action Pane. By default these entity references and collections are not loaded, we need to do this explicitly. This is a good thing in our case because it avoids transferring unnecessary data if the user doesn't select the Order row in the sheet.

    ''' <summary>
    ''' Loads the Order Details and Product information from the data 
    '''  service and returns the OrderDetails for the specified order.
    ''' </summary>
    ''' <param name="o"></param>
    ''' <remarks></remarks>
    Public Sub LoadOrderDetails(ByVal o As Order)
        If (o IsNot Nothing) Then
            If o.Shipper Is Nothing Then
                Me.DataServiceContext.LoadProperty(o, "Shipper")
            End If
            If o.Order_Details Is Nothing OrElse o.Order_Details.Count = 0 Then
                Me.DataServiceContext.LoadProperty(o, "Order_Details")
                For Each detail In o.Order_Details
                    detail.Order = o
                    If detail.Product Is Nothing Then
                        Me.DataServiceContext.LoadProperty(detail, "Product")
                    End If
                Next
            End If
        End If
    End Sub

    Finally we need a method that submits changes back to the data service. This is easily done by calling SaveChanges on the service context which sends all the changes we made in the client through the service. Note here that we are not sending them in a single batch, therefore if one order fails to update the ones before the failure will still be saved. You may need to put more robust error handling here depending on what kind of failures you anticipate in your applications. Simple error handling is done for this example in the Action Pane (we'll get there next).

        ''' <summary>
        ''' Saves the customer changes back to the service.
        ''' </summary>
        ''' <returns>true if save was successful, otherwise an exception is thrown</returns>
        ''' <remarks></remarks>
        Public Function SaveChanges() As Boolean
            Dim saved As Boolean
            Try
                _isSaving = True
                Me.DataServiceContext.SaveChanges()
                _hasChanges = False
                saved = True
            Finally
                _isSaving = False
            End Try
    
            Return saved
        End Function
    
    End Class

    Creating the Excel Action Pane Using WPF

    Now we're ready to design our Action Pane that will display the order details as well as provide a status message area and a Save and Refresh button. Refresh will re-load the orders from the service and Save will send the changes back. (Later we'll also hook up Excel's save button to do the same thing.)

    I've showed how to use WPF in Office applications before when we built the Outlook Add-In in Part 2. It's the same technique here -- use the Winforms ElementHost as a container for your WPF control. So let's create our WPF user control -- add a new item to the project and select WPF category on the left and then choose WPF User Control. I named it WPFActionPane and click OK.

    The reason for using WPF in this case is so that we could really get a nice look-and-feel that matches Excel's colors. Additionally we can easily provide a couple simple animations so that users notice the control on the right-hand side of the spreadsheet -- this is the default location of an Action Pane. So here's the layout of the control and the styles used to make the read-only GridView have a nice Excel-friendly look:

    <UserControl x:Class="WPFActionPane"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
                 MinHeight="200" MinWidth="200" 
                 VerticalContentAlignment="Stretch" 
                 HorizontalContentAlignment="Stretch" Height="400" >
    <UserControl.Resources>
    <Storyboard x:Key="LoadingStoryBoard">
        <DoubleAnimation
                        Storyboard.TargetName="ListGrid"
                        Storyboard.TargetProperty="Opacity"
                        From="0.0" To="1.0" Duration="0:0:0.5" 
                        AutoReverse="False"  />
    </Storyboard>
    <Storyboard x:Key="UpdateStoryBoard">
        <ColorAnimation From="White" To="Yellow" 
                        Storyboard.TargetName="txtStatus" 
                        Storyboard.TargetProperty="Background.Color" 
                        AutoReverse="True" />
        <ColorAnimation From="Black" To="White" 
                        Storyboard.TargetName="txtStatus" 
                        Storyboard.TargetProperty="Foreground.Color" 
                        AutoReverse="True" />
    </Storyboard>
    <Style x:Key="GridViewStyle" TargetType="TextBox">
        <Setter Property="Margin" Value="-3,0,-9,-3" />
        <Setter Property="Cursor" Value="Arrow" />
        <Setter Property="IsReadOnly" Value="True" />
        <Setter Property="BorderBrush" Value="#B8CCE4" />
    </Style>
    <Style x:Key="ListViewStyle" TargetType="ListView">
        <Setter Property="Background">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="#4F81BD" Offset="0"/>
                    <GradientStop Color="White" Offset="1.0"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
        <Setter Property="Background" Value="Transparent"/>
        <Setter Property="BorderBrush" Value="Transparent"/>
        <Setter Property="BorderThickness" Value="1"/>
        <Setter Property="HorizontalContentAlignment" Value="Stretch" />
        <Setter Property="VerticalContentAlignment" Value="Stretch" />
        <Setter Property="Height" Value="22" />
        <!--Removes the row higlight-->
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type ListViewItem}">
                    <GridViewRowPresenter 
                        SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" 
                        VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                        Grid.RowSpan="2"/>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style x:Key="GridViewColumnHeaderStyle" TargetType="GridViewColumnHeader">
        <Setter Property="Background" Value="#4F81BD"></Setter>
        <Setter Property="Foreground" Value="#DBE5F1"></Setter>
        <Setter Property="FontWeight" Value="Bold"></Setter>
        <Setter Property="HorizontalContentAlignment" Value="Left"></Setter>
        <Setter Property="BorderThickness" Value="2"></Setter>
        <Setter Property="Focusable" Value="False"></Setter>
        <Setter Property="BorderBrush">
            <Setter.Value>
                <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                    <GradientStop Color="White" Offset="0"/>
                    <GradientStop Color="#B8CCE4"  Offset="1.0"/>
                </LinearGradientBrush>
            </Setter.Value>
        </Setter>
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type GridViewColumnHeader}">
                    <Grid SnapsToDevicePixels="True">
                        <Border BorderThickness="0,0,0,1" 
                                BorderBrush="{TemplateBinding Border.BorderBrush}" 
                                Background="{TemplateBinding Panel.Background}" Name="HeaderBorder">
                            <Border Padding="{TemplateBinding Control.Padding}" Grid.RowSpan="2">
                            <ContentPresenter 
                                RecognizesAccessKey="True" 
                                Content="{TemplateBinding ContentControl.Content}" 
                                ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}" 
                                ContentStringFormat="{TemplateBinding ContentControl.ContentStringFormat}" 
                                Name="HeaderContent" Margin="0,0,0,1" 
                                HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" 
                                VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}" 
                                SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
                            </Border>
                        </Border>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    </UserControl.Resources>
     <Grid Height="Auto" Width="Auto" Name="MainGrid">
        <Grid.RowDefinitions>
            <RowDefinition Height="50" />
            <RowDefinition Height="50" />
            <RowDefinition Height="140*" />
            <RowDefinition Height="60" />
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top"  
                    Visibility="Hidden" Name="ButtonStack">
           <Button Height="40" Name="cmdSave" Width="40" Margin="3" ToolTip="Save">
                <Image Source="FloppyDisk.png" />
            </Button>
            <Button Height="40" Name="cmdRefresh" Width="40" Margin="3" ToolTip="Refresh">
                <Image Source="Refresh.png" Name="Image1" />
            </Button>
        </StackPanel>
        <StackPanel Grid.Row="1">
            <TextBlock Name="txtShipper" Width="Auto" Height="25"
                     Text="{Binding Path=Shipper.CompanyName, StringFormat=Ship via \{0\}}"  
                     Foreground="DarkBlue" />
            <TextBlock Name="txtOrderDate" Width="Auto" Height="25"
                      Text="{Binding Path=OrderDate, StringFormat=Ordered \{0:D\}}" 
                      Foreground="DarkBlue"/>
           
        </StackPanel>
        <Image Grid.Row="2" Source="northwindlogo.gif" Width="140" Height="140" />
        <Grid Grid.Row="2" Background="White" Name="ListGrid">
            <ListView 
                ItemsSource="{Binding Path=Order_Details}"
                Style="{StaticResource ListViewStyle}"
                ItemContainerStyle="{StaticResource ListViewItemStyle}"
                Name="ListView1" Margin="4" IsEnabled="True" 
                BorderThickness="0" >
                <ListView.View>
                     <GridView 
                         ColumnHeaderContainerStyle="{StaticResource GridViewColumnHeaderStyle}">
                            <GridViewColumn Header="Product" Width="150">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBox 
                                        Text="{Binding Path=Product.ProductName}" 
                                        Style="{StaticResource GridViewStyle}"/>
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        <GridViewColumn Header="Quantity" Width="61">
                            <GridViewColumn.CellTemplate>
                                <DataTemplate>
                                    <TextBox 
                                        Text="{Binding Path=Quantity, StringFormat='n0'}" 
                                        Style="{StaticResource GridViewStyle}" />
                                </DataTemplate>
                            </GridViewColumn.CellTemplate>
                        </GridViewColumn>
                        </GridView>
                </ListView.View> 
                </ListView>
        </Grid>
       <Grid Grid.Row="3">
           <TextBox Name="txtStatus" TextWrapping="Wrap" Background="Transparent"
                  IsReadOnly="True" BorderThickness="0" HorizontalContentAlignment="Stretch" 
                  VerticalScrollBarVisibility="Auto" Cursor="Arrow"></TextBox>
         
        </Grid>
    </Grid>
    </UserControl>

    The code that calls the load and save on the orders will be here since this control will interact directly with the list of Orders. In the code-behind of the WPFActionPane we'll also animate the control by running the StoryBoards defined in the XAML when we display the order details and when we display status messages at the bottom of the pane. (Note that I've also included the images referred by the XAML above into the project and set their Build Action to Resource in the properties for the files in the Solution Explorer).

    We will pass the reference to the list of Orders into the control which we can then set as the DataContext. This will trigger all the data bindings to hook up to the currently selected Order's details as noted by the dot syntax in the bindings above. For instance, if we want to display the shipper's company name then we specify the Shipper.CompanyName path on the Order object contained in the list.

    Imports NorthwindExcelClient.NorthwindService
    Imports System.Windows.Data
    Imports System.Windows.Media
    
    Partial Public Class WPFActionPane
    
        Private _orderList As MyOrderList
        Public Property OrderList() As MyOrderList
            Get
                Return _orderList
            End Get
            Set(ByVal value As MyOrderList)
                _orderList = value
                Me.DataContext = _orderList
            End Set
        End Property
    
        Private Sub cmdRefresh_Click() Handles cmdRefresh.Click
            Me.LoadOrders()
        End Sub
    
        Private Sub cmdSave_Click() Handles cmdSave.Click
            Me.SaveOrders()
        End Sub
        ''' <summary>
        ''' Saves changes made to the orders in the sheet
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function SaveOrders() As Boolean
            Dim msg = "Orders could not be saved." & vbCrLf
            Try
                'Save changes back to the data service
                If Me.OrderList.SaveChanges() Then
                    msg = "Orders saved."
                End If
    
                Me.SetMessage(msg)
    
            Catch ex As Exception
                Me.SetMessage(ex.ToString())
            End Try
        End Function
        ''' <summary>
        ''' Re-loads the orders that are not shipped yet. 
        ''' </summary>
        ''' <remarks></remarks>
        Public Sub LoadOrders()
            Dim msg = ""
            Try
                Dim count = Me.OrderList.LoadOrders()
                msg = String.Format("{0} orders returned.", count)
    
                Me.SetMessage(msg)
                Me.ButtonStack.Visibility = Windows.Visibility.Visible
            Catch ex As Exception
                Me.SetMessage(ex.ToString())
            End Try
        End Sub
        ''' <summary>
        ''' Show the Order Details in the ListView by moving the position
        ''' </summary>
        ''' <param name="o"></param>
        ''' <remarks></remarks>
        Public Sub DisplayOrderDetails(ByVal o As Order)
            If o IsNot Nothing Then
                Me.OrderList.LoadOrderDetails(o)
                Dim view = CollectionViewSource.GetDefaultView(Me.DataContext)
                view.MoveCurrentTo(o)
    
                Me.ListGrid.Visibility = Windows.Visibility.Visible
                Me.ListView1.SelectedIndex = -1
                Dim story = CType(Me.Resources("LoadingStoryBoard"), Animation.Storyboard)
                story.Begin()
            End If
        End Sub
        ''' <summary>
        ''' Sets the status message on the control
        ''' </summary>
        ''' <param name="msg"></param>
        ''' <remarks></remarks>
        Public Sub SetMessage(ByVal msg As String)
            Me.txtStatus.Text = msg
            Dim story = CType(Me.Resources("UpdateStoryBoard"), Animation.Storyboard)
            story.Begin()
        End Sub
    
    End Class

    Now we can create the Action Pane. Add a new item to the project and select Actions Pane Control from the Office group. I named it OrdersActionPane:

    OBAExcelClient3

    Now from the toolbox in the WPF Interoperability category you should see the ElementHost, drag that onto the Actions Pane user control, click the smart tag and select the WPFActionPane control as the hosted content and dock it in the parent container. I'm also going to set the font of the OrdersActionPane to Calibri 11 point.

    In the code behind for the OrdersActionPane we need to set the OrderList for the WPF control as well as listen to position changed event on the OrderListBindingSource that is hooked up as the DataSource of the ListObject on Sheet1. When the position changes we need to tell the WPF control to display that Order's details. There are many ways to integrate WPF and Windows Forms to manage currency but we're trying to keep this example straightforward so we'll do it the easy way:

    Imports System.Data.Services.Client
    Imports NorthwindExcelClient.NorthwindService
    
    Public Class OrdersActionsPane
    
        Private Sub OrdersActionsPane_Load() Handles Me.Load
            'Pass in our data source to the WPF control
            Me.WpfActionPane1.OrderList = NorthwindExcelClient.Globals.ThisWorkbook.OrderList
            'When the position changes in the spreadsheet, display the order details in the WPF control
            AddHandler NorthwindExcelClient.Globals.Sheet1.MyOrderListBindingSource.PositionChanged, _
                      AddressOf Me.DisplayOrderDetails
        End Sub
    
        Private Sub DisplayOrderDetails(ByVal sender As Object, ByVal e As EventArgs)
            Dim source = TryCast(sender, BindingSource)
            If source IsNot Nothing Then
                If source.Position > -1 Then
                    Me.WpfActionPane1.DisplayOrderDetails(TryCast(source.Current, Order))
                End If
            End If
        End Sub
    End Class

    Finally we're almost ready to test this out. The last thing we need to do is load the Actions Pane when the application starts as well as expose a LoadOrders to our Sheet. This is because if we're adding charts, pivot tables or other types of controls for data analysis we can't rely on the Action Pane loading the data, we need the sheet to be able to call upon it explicitly. We also will hook up the Excel BeforeSave event so that we can call save on the orders. So back in the ThisWorkbook class:

       Private _orderActions As New OrdersActionsPane
    
        Private Sub ThisWorkbook_Startup(ByVal sender As Object, _
                                         ByVal e As System.EventArgs) Handles Me.Startup
            'Load the action pane
            Me.ActionsPane.Controls.Add(_orderActions)
        End Sub
    
        Public Sub LoadOrders()
            _orderActions.WpfActionPane1.LoadOrders()
        End Sub
    
        Private Sub ThisWorkbook_BeforeSave(ByVal SaveAsUI As Boolean, _
                                            ByRef Cancel As Boolean) Handles Me.BeforeSave
            _orderActions.WpfActionPane1.SaveOrders()
        End Sub
    End Class

    Hit F5 and try it out. You will see all the Orders that have not shipped. As you select a row, the details are displayed in the Action Pane and an animation that shows the Northwind logo fades into and out of view so that it catches the user's attention without annoying them ;-). Modify any of the fields in the Sheet and click save to save your changes back to the service.

    Adding a Pivot Table and Chart

    The final thing we want to do is add a pivot table and a pie chart to our Sheet that will display a breakdown of cities we're shipping to. In the designer for Sheet1 select the first cell and then go to the Insert tab. On the left select the arrow under the PivotTable and select PivotChart. A dialog will open which allows you to select the location of the PivotChart. Here I'm just adding it under the ListObject, it will automatically move down as orders come back from the data service.

    OBAExcelClient4

    Now you move into specifying the PivotTable fields and values through Excel like you normally would. Here I'm adding the ShipCity as both a label and a value, which automatically assumes a count. I also right-click on the chart and am allowed to change the chart type to a Pie Chart:

    OBAExcelClient5

    We're not quite done though. In order to get our PivotTable to update when the data is retrieved from the service we need to add some code to handle this. First name the PivotTable by right-clicking on it and selecting PivotTable Options -- I named it PivotTableShipCity. Now in the code behind we can handle the ListChanged event on the BindingSource which bubbles up what we're doing in our MyOrdersList class -- when we load the orders in the LoadOrders method, we raise the ListChangedEvent and pass a "Reset" parameter. Now we can check that here:

    Private Sub MyOrderListBindingSource_ListChanged(ByVal sender As Object, _
                ByVal e As System.ComponentModel.ListChangedEventArgs) _
                Handles MyOrderListBindingSource.ListChanged
    
        If e.ListChangedType = System.ComponentModel.ListChangedType.Reset Then
    
            Dim table = TryCast(Me.PivotTables("PivotTableShipCity"), Excel.PivotTable)
            If table IsNot Nothing Then
                table.RefreshTable()
            End If
            Me.MyOrderListBindingSource.MoveFirst()
    
        End If
    End Sub

    Now when we hit F5 we will see the open orders come down into the spreadsheet and the pie chart will update to display our Excel data visualization. Nice!

    OBAExcelClient6

    I've updated our sample on Code Gallery with a new release that includes this Excel client so have a look.

    Now that we understand what our shipping department is doing we can build out our SharePoint workflow. It will need to parse the purchase order using the code from part 3 and it will also need to update the order status based on the change made here to the ShippedDate field. We don't want our Excel client to be tightly coupled to SharePoint so we'll need to think about how we can achieve this all from our SharePoint Workflow. Until next time...

    Enjoy!

Page 1 of 1 (8 items)