During my presentation at Tech Ed 2010 in New Orleans this past summer, someone asked me about how to display a total and keep that total updated in a DataGrid control in Windows Presentation Foundation (WPF). Figure 1 shows an example of a WPF DataGrid control that has some product data.
Figure 1: A window that has a sum displayed at the bottom
At the bottom of the form, you can see the sum of the Price column. As you modify the data in the Price column, you want this sum to automatically update. This is easy to do in WPF. You can accomplish this task by using just two classes and a few lines in the code-behind for the window.
The data in Figure 1 uses an XML file that contains products. Using an XML file is an easy way to demonstrate these features, and it avoids having to create any database tables. A partial listing of the Product.xml file is shown in Figure 2.
The Product Class
To populate the DataGrid, you first have to create two classes: Product and ProductManager. The Product class (Figure 3) is an entity class that contains one property for each element in the XML file. If you are using a table in a database, the Product class contains one property for each column in your table. Your entity class should typically implement the INotifyPropertyChanged interface so that changes that are made to a property in the class are automatically reflected in any XAML control that is bound to that property.
Because you will most likely use the INotifyPropertyChanged interface repeatedly, you might want to create a base class that implements this interface. Each of your entity classes can then inherit from this base class. This avoids a lot of duplicate code.
Perform that refactoring now. Create a new class called XamlBaseClass, as shown in Figure 4. When you have this class in place, you can go back and modify your Product class (Figure 5) to inherit from this base class and get all the benefits of the INotifyPropertyChanged interface.
The ProductManager Class
In addition to an entity class, you should create a class to populate the entity class by using data from your database. This new class will perform functions such as creating a collection of entity classes, and calculating totals that are required for this sample. The ProductManager class also has to implement the INotifyPropertyChanged event because this class contains one property (PriceSum) that is used to keep the sum of all the prices in the collection updated on the WPF window. Therefore, the ProductManager class also inherits from the XamlBase class that you created earlier.
Figure 6 is a partial listing of the ProductManager class. It shows the definition of the PriceSum property and an ObservableCollection named DataCollection. The ObservableCollection property is a collection of all of the Product entity objects. You load this collection one time. After that, you can use this collection to recalculate the sum of all price values in the collection. When you modify one price in the DataGrid, the Price property of the product class is updated automatically through WPF data binding. After that, you have to write only a little bit of code to recalculate the PriceSum property, as we will see shortly.
The RecalcPriceSum Method
Figure 7 shows the code that you will write to sum the total of all prices in the collection of Product objects. You will use a local variable to calculate the sum instead of using the PriceSum property. The reason for this is that the RaisePropertyChanged event in the setter of the PriceSum property fires each time through the loop if you update the PriceSum property directly. Setting the PriceSum property after the loop is complete by using the value in the total variable forces any WPF control that is bound to this property to be updated with the new value.
Load the Products Collection
At some point, you have to load the DataCollection property by using a collection of Product entity objects. To do this, you create a GetProducts method (Figure 8) to load the Product.xml file from the \Xml folder in your project. There is a special method named GetCurrentDirectory in another class named WPFCommon. This method calculates the current directory in which this project is running, and then appends the string "\Xml\Product.xml" to allow the GetProducts method to open the file.
LINQ to XML code is used to create an IEnumerable collection of Product objects from each node in the XML file. After this collection of Product objects is created, a new ObservableCollection object is created from the IEnumerable collection. This value is what is then assigned to the DataCollection property in the ProductManager class.
Notice that at the end of creating the collection of Product objects, you hook up an event procedure to handle the CollectionChanged event of the ObservableCollection object. You create this event procedure in case you add or delete an item in the collection. Notice also the call to the RecalcPriceSum method that's in the event handler. This event handler makes sure that the PriceSum property is updated even if you modify the collection.
The Product Window
Now it's time to use the Product and ProductManager classes in the WPF window. Begin by adding an XML Namespace to the WPF window. To use a class in XAML, you have to first bring in the namespace in which that class is located, and then assign an alias to it. In the following code snippet, a namespace alias named local is added to the WPF window. This snippet assumes that your project name is WPFCollectionTotal.
Now that you have the namespace, you can create an instance of the ProductManager class by having just a little bit of XAML in the Window.Resources section of your Window. The line of code in the following snippet that uses
is the code that creates an instance of your ProductManager class.
The key that you assign to the newly created ProductManager object is important because this is the key that is used by any other XAML in your window. For this particular window, the ProductManager object is used as the source of the data in the ObjectDataProvider object that you see just below the XAML that created the ProductManager class.
The ObjectDataProvider object uses the ObjectInstance property to point to the static resource that has a key named productsObject. Using the ObjectInstance property tells the ObjectDataProvider object that it does not have to create an instance of an object. It tells the object, instead, to use the object instance that has already been created. The ObjectDataProvider’s MethodName property is set to the name of the method that you want to call on the object, and thereby retrieve the data that can be used for the remainder of the window.
The DataGrid control within the main
Updating the Sum When Data Changes in the Grid
The last piece of code that you have to write is code to calculate what happens when you modify any Price cells in the DataGrid. To accomplish this, you have to respond to the CellEditEnding event on the DataGrid. If a cell has been edited, and if the user moves off the cell, one of the events that fires is CellEditEnding. You first have to figure out which cell the user just edited. As shown in Figure 10, you check the column header property to see whether the cell that was just edited is the Price cell. If it is, you call the RecalcPriceSum method on the ProductManager class. This method updates the PriceSum property of the entity class. The PriceSum property, in turn, updates the TextBlock control to which it is bound.
Putting Your Code to Work
Now that we have learned how to use a collection class to update a total on a WPF window, you should make sure that you write the majority of your code in your business and data objects instead of in your UI layer. Doing this helps keep your logic centralized. It also makes it easier to unit test your logic, and it makes your code more reusable across multiple UIs, if that is what is required.
Take advantage of the data binding features of WPF by using the INotifyPropertyChanged interface to allow your business/data objects to automatically inform your UI about any changes to the data.
Note: You can download the complete sample code (in both VB and C# formats) at my website, http://www.pdsa.com/downloads. In the Category list, select Tips & Tricks. In the Item list that is displayed, select "WPF Calculate and Display Totals."
Paul D. Sheriff is the President of PDSA, a Microsoft Partner in Southern California. Sheriff acts as the Microsoft Regional Director for Southern California, and has written hundreds of books, webcasts, videos and articles about various Microsoft products and services.