Better Data Editing Features in WPF with SP1

Better Data Editing Features in WPF with SP1

  • Comments 25

One of the things I was really missing from WPF when I started to dig into data binding was feature consistency between the BindingListCollectionView and the Winforms BindingSource I had grown to love. The BindingListCollectionView provides the navigation, currency, filtering and sorting on the bound collection of data (or DataTable) just like the BindingSource in Winforms.

However transacted adding and removing of items to the collections was not supported. You'll notice in my WPF Forms over Data videos when I go to add or remove a row of my data I have to access the DataTable directly. This isn't really a problem when working with DataTables because they can do their own transacted editing (along with change tracking). However this is usually a necessary feature in order to work well with custom business collections implementing typical binding interfaces.

SP1 Adds New Properties/Methods to WPF's BindingListCollectionView

With the release of Visual Studio/.NET FX SP1 they've enhanced the BindingListCollectionView to include new properties and methods: CanAddNew property, CanCancelEdit property, CanRemove property, CurrentAddItem property, CurrentEditItem property, IsAddingNew property, IsEditingItem property, ItemProperties property, NewItemPlaceholderPosition property, AddNew method, CancelEdit method, CancelNew method, CommitEdit method, CommitlNew method, EditItem method, Remove method, RemoveAt method.

One thing that I want to point out here that's different is in Winforms we used to call EndEdit on the BindingSource to push all transacted changes whether they were edits or adds to the bound data source (i.e. the DataTable or your collection). In WPF there's a separate call for CommitEdit and CommitNew and you have to make sure you don't call CommitEdit if you are in the middle of an add that calls AddNew, instead you have to call CommitNew. I'm not sure why they separated this. When using DataSets I always commit the changes pretty much right away (after filling default values for instance) and rely on the DataSet's change tracking using Accept/RejectChanges.

Adding and Removing Data with the New AddNew and Remove Methods

When we want to add a new row of data to a DataTable we can now call AddNew directly on the BindingListCollectionView. For instance..

Private OrderData As New OrdersDataSet
Private OrdersViewSource As BindingListCollectionView
Sub New()
    ' This call is required by the Windows Form Designer.
    InitializeComponent()

    ' Add any initialization after the InitializeComponent() call.
    Me.LoadData()
    Me.DataContext = Me.OrderData.Orders
    Me.OrdersViewSource = CollectionViewSource.GetDefaultView(Me.DataContext)
End Sub
Private Sub AddNewOrder()
    '--- Old Code ---
    'Add a new row to the collection
    'Dim order = Me.OrderData.Orders.NewOrdersRow
    'Me.OrderData.Orders.AddOrdersRow(order)
    'Up to us to update the position
    'Me.OrdersViewSource.MoveCurrentToLast()

    '--- New Code ---
    'Add a new row to the collection
    Me.OrdersViewSource.AddNew()
    'Push changes into the DataTable
    Me.OrdersViewSource.CommitNew()
End Sub

Above I'm handling all the validation and setting of default values via the DataSet partial classes so the AddNewOrder() method would remain unchanged if we were working against our own business object collections. This is what I came accustomed to in Winforms, you can easily swap out your data sources without messing with the data binding code. Keep in mind though that if you call CommitNew and the DataRow is invalid, you'll get an error when it's pushed into the DataSet -- so make sure to handle the TableNewRow event on the DataTable partial class and fill in valid default values.

Removing the current row is also very straightforward:

Private Sub RemoveOrder()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        '--- Old Code ---
        'Dim order As OrdersDataSet.OrdersRow
        'order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, OrdersDataSet.OrdersRow)
        'order.Delete()

        '--- New Code ---
        Me.OrdersViewSource.Remove(Me.OrdersViewSource.CurrentItem)
    End If
End Sub 

Working with Master-Detail Forms in WPF

These are good improvements but the BindingSource in Winforms is still easier to work with at the moment IMHO because the BindingSource is a visual component that handles the underlying CurrencyManager goo. One thing that's a pain is when you are working with master-detail binding scenarios in WPF. You need to obtain a reference to the details' BindingListCollectionView every time the detail view changes (this would be the ItemSource on a ListBox or ListView). If you've got the binding set up correctly in the XAML then the detail view changes automatically when the parent's position changes. However, if you want to AddNew into the child then you need to obtain a reference in code every time because the view is dynamic. Winforms BindingSources handle this scenario better.

Even though we need to obtain a reference every time, it's fairly straightforward:

Private Sub AddNewDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then
        '--- Old Code ---
        'Dim order As OrdersDataSet.OrdersRow
        'order = CType(CType(Me.OrdersViewSource.CurrentItem, DataRowView).Row, _
        '              OrdersDataSet.OrdersRow)
        'Dim detail = Me.OrderData.OrderDetail.NewOrderDetailRow
        'detail.OrderID = order.OrderID
        'Me.OrderData.OrderDetail.AddOrderDetailRow(detail)

        '--- New Code ---
        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)
        detailView.AddNew()
        'Note that the related OrderID is set for us automatically just like Winforms
        detailView.CommitNew()
    End If
End Sub

Private Sub RemoveDetail()
    If Me.OrdersViewSource.CurrentPosition > -1 Then

        Dim detailView As BindingListCollectionView = _
                          CollectionViewSource.GetDefaultView(Me.lstDetails.ItemsSource)

        If detailView.CurrentPosition > -1 Then
            '--- Old Code ---
            'Dim detail As OrdersDataSet.OrderDetailRow
            'detail = CType(CType(Me.OrderDetailsViewSource.CurrentItem, DataRowView).Row, _
            '               OrdersDataSet.OrderDetailRow)
            'detail.Delete()

            '--- New Code ---
            detailView.Remove(detailView.CurrentItem)
        End If
    End If
End Sub

By using the new AddNew and Remove methods it also seems to solve some issues I was seeing when working with LINQ to SQL generated child collections (EntitySets) as well so it's best to start taking advantage of these new methods. In a future blog post I'll take the LINQ to SQL N-Tier application we did a while back and slap a WPF Front end on it.

I'm probably not going to update the videos I've already done, but going forward I'll be using these new methods and properties where appropriate so go download SP1 ;-)

Enjoy!

Leave a Comment
  • Please add 1 and 4 and type the answer here:
  • Post
  • PingBack from http://hubsfunnywallpaper.cn/?p=1413

  • Hi,

    If you are using Linq to EDM, in my trial of idea, it seems to me that CommitNew does not push data to backend! You still need to call SaveChanges on ObjectContext.

    Here is pseudo code:

    var d = cvDetails.AddNew() as Detail;

    d.Name = "...";

    d.Comment = "Details comments";

    cvDetails.CommitNew();

    EntitiesObjectContext.SaveChanges();

    Am I right, or have I done something wrong?

    Rgds

  • Link Listing - August 20, 2008

  • As usual, Beth made it easy for all devs to grasp new features in few lines :)

  • Hi Beth,

    This is really good stuff!  I got to study it some more, but this way of handling a detail is exactly what I've been looking for.

    Going to try and get sp1 tomorrow night, for my Friday session.  I got to make sure I do it right, because I know there are conflicts with my Silverlight stuff.

    Many Thanks!  This stuff is really something!

    John.

  • A pretty common requirement of any business application is to be able to edit data in a "spreadsheet"

  • Beth,

      I'm using Visual Basic Express Edition and am trying to set up the DataGridView like your video demonstrates except the query builder will not take the "@" symbol.  Also, instead of using a textbox to type in the search parameter, I am trying to use the combox  populated with a a collection list available on the FillBy Toolstrip, but I'm not having any luck getting the parameter passed in so I guess I have 2 questions.  First, is the parser different on the Express edition (won't take the '@") and how can the value of the combobox on the toolstrip be passed into the query?

  • In my last post on WPF I showed how you could use a Winforms DataGridView on a WPF form in order to edit

  • In my last post on WPF I showed how you could use a Winforms DataGridView on a WPF form in order to edit

  • A pretty common requirement of any business application is to be able to edit data in a "spreadsheet"

  • AddNewDetail method (new code) does not work when I tried to add detail immediately after I have added master. ItemsSource of detail is null and it threw NullReferenceException (Object reference not set to an instance of an object).

    Could you advice how I can add new details immediately after I've added master with your new code?

    Your old code for AddNewDetail works but the detail view is blank after detail is added successfully. The detail view only appears on my UI after I restart my application.

  • Hi Chio,

    The code works fine. You may be getting a validation error when you try to commit the row to the collection\table. Make sure the parent row is in the data source before adding the child. Here's a full example: http://msdn.microsoft.com/en-us/vbasic/dd239277.aspx

    HTH,

    -B

  • Hi Beth,

    Thanks for the link! :)

    I was using DataContext Direct previously and the binding for ListView as below:

    Me.DataContext = OMSDataSet.Orders

    <ListView

    IsSynchronizedWithCurrentItem="True"

    ItemsSource="{Binding Path=FK_OrderDetail_Orders}"> ...

    I get NullReferenceException (Object reference not set to an instance of an object) on Me.DetailView.CommitNew(). -> this was the error which I enquired on December 3.

    *****

    After studying your master-detail vb code example, which uses Master-Detail Binding (refer to your post http://blogs.msdn.com/bethmassi/archive/2008/11/07/loading-data-and-binding-controls-in-wpf-with-collectionviewsource.aspx), I was hoping Master-Detail Binding would make a difference as compared to DataContext Direct. So I modified your example to using dataset as I use dataset and table adapters instead of LINQ to SQL in the development of my own application. Here's the link to the modified master-detail example: http://www.mediafire.com/download.php?yhnuijb5wmf

    The problem that I face is:

    1. Select "Add Order", key values in master view and select "Save". (Add parent/master first)

    2. Then select "Add Detail, key values in detail view and select "Save". (Add child/detail)

    3. Save is done but the values for detail view is reflected only after I restart application.

    PROBLEM:

    Parent (master) row is in the data source when I add the child (detail) but somehow the values of detail view "disappear" from the UI when save is performed. The values will only be shown after application gets restarted.

    Do you have any recommendations? This problem seems to prevalent for dataset. LINQ to SQL does not have this problem.

    Also, I got Foreign Key Constraint error.

    DetailView.AddNew()

    'OrderID (Foreign Key) is set incorrectly. Updates of master OrderID (Primary Key) does not get cascade to the detail OrderID (FK)...

    DetailView.CommitNew()

    I did this:

    Me.DetailView.CurrentItem("OrderID") = Me.MasterView.CurrentItem("OrderID")

    'which solves Foreign Key Constraint but I wonder why FK does not get set or updated automatically

    I did set the DataRelation in the DataSet designer to "Both Relation and Foreign Key Constraint" and the Update and Delete rules to Cascade.

    (refer to your post http://blogs.msdn.com/bethmassi/archive/2007/07/10/working-with-tableadapters-related-datatables-and-transactions.aspx)

    Thanks very much.

  • Hi Chio,

    Did you trace the NullReferenceException? I have a feeling it's because your DetailView is null. Check your ItemSource binding path and make sure the relation name is correct. (i.e. are you sure it isn't FK_Orders_OrderDetail?)

    HTH,

    -B

  • Hi Beth,

    I am certain that my DetailView is not null as I can see the details as I scroll through the master records.

    DetailView becomes null for a newly added master record. Problem only occurs when I add new master record then add details. With existing master records, adding detail is ok.

Page 1 of 2 (25 items) 12