Better Data Editing Features in WPF with SP1

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.

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.

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:

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 ;-)

A pretty common requirement of any business application is to be able to edit data in a &quot;spreadsheet&quot;

Carolyn

8 Sep 2008 1:48 PM

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?

A pretty common requirement of any business application is to be able to edit data in a &quot;spreadsheet&quot;

chio

3 Dec 2008 3:32 PM

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.

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

chio

5 Dec 2008 10:23 AM

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.

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)...

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

Chio

11 Dec 2008 1:00 PM

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.