In a previous Blog, I showed how to databind an XML file to a WPF ListView. As some people are more comfortable with collections of objects than they are with XML, in this blog item I thought we could look at binding to a collection.
One difference to note is that if you use XML data and XPath, you have the benefit of seeing all the data in the Design Pane at design time. With this collection of objects approach, the data is being created on the fly, so of course it can't be seen at design time because it doesn't exist. It isn't usually a problem, but if you have become used to seeing the data at design time, this can be disconcerting at first.
Creating a Collection of Objects
Constructing a class that you can use to create objects is exactly the same as with Windows Forms. Here is a simple class named DrinkProduct:
Public Class DrinkProduct
Sub New(ByVal ProductID As String, ByVal ProductName As String, ByVal PackageType As String)
Me.ProductID = ProductID
Me.ProductName = ProductName
Me.PackageType = PackageType
End Sub
Private _ProductID As String
Public Property ProductID() As String
Get
Return _ProductID
End Get
Set(ByVal value As String)
_ProductID = value
End Set
End Property
Private _ProductName As String
Public Property ProductName() As String
Get
Return _ProductName
End Get
Set(ByVal value As String)
_ProductName = value
End Set
End Property
Private _PackageType As String
Public Property PackageType() As String
Get
Return _PackageType
End Get
Set(ByVal value As String)
_PackageType = value
End Set
End Property
End Class
The next step is to create a collection of DrinkProduct objects. Again, there is nothing new here:
Public Function StockCheck() As List(Of DrinkProduct)
Dim CurrentProducts As New List(Of DrinkProduct)
With CurrentProducts
.Add(New DrinkProduct("CF1kg", "Instant Coffee", "1 Kg"))
.Add(New DrinkProduct("CFG500g", "Ground Coffee", "500 g"))
.Add(New DrinkProduct("Te500g", "Tea", "500 g"))
.Add(New DrinkProduct("SMlk1lt", "Skimmed Milk", "1 Litre"))
.Add(New DrinkProduct("HiJ300g", "HiJuice Drink Mix", "300 g"))
End With
Return CurrentProducts
End Function
Where you place the above function is a matter of choice, depending on what kind of scope you want it to have within the application. I've chosen to place it in a helper module named modStockControl so that it is available application wide.
The next step includes a small change. The link between that collection of objects and the ListView that will display them in will be a DataContext. This is very much like the DataSource object that you will be familiar with from Windows Forms. In the code-behind for the Window, insert the following code:
Dim CurrentProducts As New List(Of DrinkProduct)
Private Sub Part2_Loaded(ByVal sender As Object, ByVal e As System.Windows.RoutedEventArgs) Handles Me.Loaded
CurrentProducts = StockCheck()
ProductsListView.DataContext = CurrentProducts
End Sub
Clearly, all this does is create an empty List, then generates some data to populate the List. The final line sets the DataContext of the ListView specifically to be the that List. At the risk of confusing the issue at this early stage, it isn't necessary to bind directly to the ListView. I could have set the DataContext on the Window or the Grid, for example and it may still be accessible by the ListView. This is something I will cover in a future blog.
Let's turn our attention to the ListView markup. A skeleton version is:
<ListView Name="ProductsListView"
ItemsSource="{Binding}">
<ListView.View>
<GridView>
<!-- Product ID -->
<GridViewColumn
Header="ProductID" >
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=ProductID}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<!-- Remaining two columns removed for brevity -->
</GridView>
</ListView.View>
</ListView>
As you can see in the markup, the ListView is named and this name is the one we used in the code-behind to link to the DataContext. Even more crucially, the ItemsSource property is set to point to that same DataContext. Even though it only contains the word "Binding", WPF is smart enough to work out which particular binding is the appropriate one. So those two properties are the glue that enables the ListView to be populated with DrinkProduct objects.
The remaining markup creates a View and in that View there are three GridViewColumns (although I have only shown the markup for the first one). Inside the GridViewColumn markup you see a CellTemplate and a DataTemplate, both of which are simply devices for packaging the look of the data. In this case, a basic TextBlock is used to house the text data.
The line of markup:
<TextBlock Text="{Binding Path=ProductID}" />
is the final piece of the Binding puzzle. The pointer to the "Binding" ensures that WPF looks back up the tree to find the source of the data that has been set. (In this case, it looks no further than the ItemsSource which in turn knows that the data will come from that collection of DrinkProduct items.)
The Path identifies the exact field in the data source that is to be bound to this column. For this column it is the ProductID field.
So, as you can see from the few steps we have taken above, data binding the ListView to a data source is a trivial task.
At the moment, the GUI isn't too impressive, because I've stripped out everything but the basics:

I originally intended to look at several other tweaks that you can make to the look and layout of WPF ListViews. But to keep this blog to a reasonable length (and so as not to confuse the data binding technique with other peripheral stuff) I'll cover that in my next blog.