Understanding Views and Binding Lists
CollectionViewSource objects expose an interesting property named View.
It basically provides the ability of filtering, sorting, and navigating
through a bound collection of items. To understand how a view works,
the best example in our scenario is handling the Next and Back buttons. The following code snippet shows how easy it is to navigate back and forward through items:
Private Sub NextButton_Click(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs)
If Me.CustomersOrdersViewSource.View.CurrentPosition < _
CType(Me.CustomersOrdersViewSource.View, CollectionView).
Count - 1 Then
Me.CustomersOrdersViewSource.View.MoveCurrentToNext()
End If
End Sub
Private Sub BackButton_Click(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs)
If Me.CustomersOrdersViewSource.View.CurrentPosition > 0 Then
Me.CustomersOrdersViewSource.View.MoveCurrentToPrevious()
End If
End Sub
The
code simply calculates the position and enables moving back or forward
only if there are any other items that can be navigated. Notice how the CustomersOrdersViewSource.View property exposes the Count property, representing the current position being examined in the collection. Also notice methods such as MoveCurrentToNext and MoveCurrentToPrevious
that enable moving back and forward to another item. Other interesting
members from views are self-explanatory and are summarized in Table 2.
Table 2. Views’ Most Common Members
Member | Type | Description |
---|
CanSort | Property | Returns a Boolean value indicating whether the collection can be sorted. |
CanFilter | Property | Returns a Boolean value indicating whether the collection can be filtered. |
CanGroup | Property | Returns a Boolean value indicating whether the collection can be grouped. |
MoveCurrentTo | Method | Sets the specified item as the current item in the collection. |
MoveCurrentToFirst | Method | Sets the first item in the collection as the current item. |
MoveCurrentToLast | Method | Sets the last item in the collection as the current item. |
MoveCurrentToNext | Method | Sets next item in the collection as the current item. |
MoveCurrentToPrevious | Method | Sets the previous item in the collection as the current item. |
CurrentItem | Property | Returns
the instance of the current item in the collection. Because it is of
type Object, it must be converted into the appropriate type. |
CurrentPosition | Property | Returns an index corresponding to the current item in the collection. |
Also notice how, to retrieve the items count, a CType operator converts from CollectionViewSource.View into a CollectionView object. This last one represents a single view, and the conversion is required because Option Strict is On and the View property is of type ICollectionView. Views from CollectionViewSource objects are straightforward, because they also support data-binding but they have several limitations.
As you can recap from Table 2,
no member is exposed for adding, editing, or removing items in the
underlying data collection. To provide the ability of CRUD operations,
the best approach is utilizing a System.Window.Data.Binding ListCollectionView,
which also offers a reference to data collections but provides more
capabilities. With that said, at class level, declare the following
variables:
Private WithEvents CustomerView As BindingListCollectionView
Private CustomersOrdersView As BindingListCollectionView
Now, in the Window_Loaded event handler, add the following lines as the last lines of code in the method:
Me.CustomerView = CType(Me.CustomersViewSource.View,
BindingListCollectionView)
Me.CustomersOrdersView = CType(Me.CustomersOrdersViewSource.View,
BindingListCollectionView)
This converts views references to two BindingListCollectionView
objects. Now with these you can perform insert/update/delete operations
to the underlying collection, which is picked up from the CollectionViewSource associations and that is data-bound to the BindingListCollectionView, too. To understand how this works, write the following handler for the Click event about the Add button so that we can provide the ability of adding a new order:
Private Sub AddButton_Click(ByVal sender As System.Object,
ByVal e As System.Windows.RoutedEventArgs)
'A new order
Dim newOrder As Order
'Adds a new order to the view and assigns the instance
'to the newly declared order
newOrder = CType(Me.CustomersOrdersView.AddNew(), Order)
'If I need to assign properties to newOrder before
'it is sent to the collection, then this is the place
'Sends the new order to the view
Me.CustomersOrdersView.CommitNew()
End Sub
The AddNew method adds
an instance of the specified object type to the view, and the addition
is automatically reflected to the bound user interface controls. The CType conversion is required because the method returns Object;
therefore, converting to the appropriate type returns the effective
instance of the order. This is not actually required, but it is useful
if you want to set some default properties before the object is sent to
the underlying collection.
Notice that this code submits the new item to the underlying
collection, but the new object will not persist to the underlying
database until you invoke the ObjectContext.SaveChanges
method. Removing items works similarly, in that you simply retrieve the
current object instance and invoke one of the allowed methods. The
following event handler for the Delete button demonstrates this:
Private Sub DeleteButton_Click(ByVal sender As System.Object,
ByVal e As System.Windows.
RoutedEventArgs)
If Me.CustomersOrdersView.CurrentPosition > -1 Then
Dim result = MessageBox.Show("Are you sure?",
"", MessageBoxButton.YesNo)
If result = MessageBoxResult.Yes Then
Me.CustomersOrdersView.
RemoveAt(Me.CustomersOrdersView.CurrentPosition)
Else
Exit Sub
End If
End If
End Sub
In this case I’m using RemoveAt to remove the item at the current position, but you can also invoke Remove that requires the instance of the current object. Basically RemoveAt
requires fewer lines of code. Before running the application, there is
one thing that you need to take care of and that is the fact that the a BindingListCollectionView
content needs to be refreshed each time you move to another item in the
master part of the master-details relationships. Considering our code
example, you need to remember the BindingListCollectionView referred to orders each time you select a different customer. To accomplish this you handle the CurrentChanged event in the master part of the relationship, as demonstrated by the following code:
Private Sub CustomerView_CurrentChanged(ByVal sender As Object,
ByVal e As System.EventArgs) _
Handles CustomerView.CurrentChanged
Me.CustomersOrdersView = CType(Me.CustomersOrdersViewSource.View,
BindingListCollectionView)
End Sub
The preceding event handler is invoked when you click a different customer in the user interface and refreshes the CustomersOrdersView (of type BindingListCollectionView) object pointing to the actual orders collection referred by the underlying CollectionViewSource, which effectively keeps the data-binding alive. At this point you can run the application and get the result summarized in Figure 6.
The drag’n’drop data-binding works the same with DataSets and you can still take advantage of CollectionViewSource and BindingListCollectionView
objects. The code remains the same as the previously shown examples,
whereas the difference is where you need to persist data to the database
or fetch data, where you can respectively use DataSet methods and LINQ
to DataSets.
|
You can now play with additional controls such as Add, Delete, Next, and Back.
When you are done, try and save changes to ensure that new or edited
data is correctly persisted to the database. This sample application can
be enhanced in several other ways. For example you can implement
entities validation or showing details for a single order using LINQ.