Click here to Skip to main content
15,885,537 members
Articles / Desktop Programming / WPF
Article

Using converters to aggregate a list in a ListView

Rate me:
Please Sign up or sign in to vote.
4.64/5 (11 votes)
23 Jul 2008CPOL6 min read 66K   1.8K   27   7
Explanation on how to use converters to calculate the sum, largest, average, etc., of the items in a ListView.

Image 1

The Demo

The demo shows a table with a list of employees, with their salary and the distance they need to travel. The red and green bars show the travel distance in a more graphic way. These bars are scaled to the largest value shown in that column. Below the table is a footer with some extra information about the data. These textboxes are bound in a similar way as the bars in the table, they use the exact same converters. The three buttons show different selections of the same data.

Introduction

In a lot of cases, when we present a table to a user, we want to show some extra information. For example, the average salary of the selected employees, or the total number of a specific product in a selection of orders. In a project I'm working on at the moment, I needed to calculate the maximum value of one of the columns in a WPF ListView to present some kind of chart. One possible approach is the use of converters. A great thing about converters is that they only have to be written once and can be used in many places. But, what if you need to aggregate a specific column in a table? The easiest way to do such a thing is to use the class and its properties in the converter. But that way, the converter becomes unique for almost each list. The solution, a converter which converts the ItemsSource of the ListView into a double through Reflection.

With Reflection, you can parameterize the property of the list you want to aggregate, thus making the converter generic again! To do this, we need to create a converter that takes a generic List<T> and convert this into a single value. An average of a column, for example. We want to pass the name of the column to the converter as a parameter, so we can use Reflection to retrieve the values of that specific column. The converter needs to be used in a binding to the ItemsSource of a ListView. Even the items in a DataTemplate are able to use these converters. This way, we can use a ProgressBar to show some data and bind the maximum value in the table to the maximum value of all ProgressBars to make them scale nicely according to the data.

Value converter

To create a value converter, we need to create a class which inherits and implements IValueConverter. First, let's take a look at the code for the converter that calculates the largest value in a particular column of the ListView, a snippet from AggregateConverters.cs:

C#
public class MaxConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, 
                          System.Globalization.CultureInfo culture)
    {
        double Max = 0.0; // initialize a variable to keep track of the highest value
        Type ValueType = value.GetType(); // get the type of the provided value
        if(ValueType.Name == typeof(List<>).Name) // Check if we're dealing with a list
        {
            foreach (var item in (IList)value)//if so, loop thru all items in the list
            {
                // Get the type of the item
                Type ItemType = item.GetType();                     
                //Get the propetry information
                PropertyInfo ItemPropertyInfo = ItemType.GetProperty((string)parameter);
                // get the actual value of the item
                double ItemValue = (double)ItemPropertyInfo.GetValue(item, null); 
                if (ItemValue > Max) // compare ..
                    Max = ItemValue; // .. and assign value to max if needed
            }
            return Max;
        }
        return 0.0; // It's not a list so return 0.0
    }
    public object ConvertBack(object value, Type targetType, object parameter, 
                              System.Globalization.CultureInfo culture)
    { throw new NotImplementedException();  }
}

A variable is initialized to hold the maximum value, double Max = 0.0;. To prevent errors, we need to check if we are dealing with a list. We take the type of the value and compare its Name property to the Name property of the type of a List<T>. When we are sure we're dealing with a list, we cast it to an IList and iterate through its items. For every item, we call GetType() to get the information about the type. To get the attributes and methods of this item, we call GetProperty() with the parameter provided to the converter. The GetProperty() method expects a string as a parameter, so we make a quick cast. To use the PropertyInfo class, the System.Reflection namespace needs to be added to the using directives. All there is left to do is get the actual value from the item. Luckily for us, the PropertyInfo class provides us with the GetValue method. We pass in our item and null, because we don't use an indexed property. We cast the returned object to a double.

Now that we have got the value, we can do all kinds of complex calculations with it. In our case, we compare it to the largest value we kept in the Max variable. If the value is greater than Max, we assign it and go to the next iteration. At the end of the converter method, we return Max.

XAML

To make the convert visible to our XAML file, we first need to add the namespace to the XAML file (if it isn't there already). In the demo, the converter is in the AggregateConverterExample namespace.

XML
<window
.
.
    xmlns:local="clr-namespace:AggregateConverterExample"
.
. 
>

After that, the namespace is available in the XAML file and can be accessed by using the local prefix. Next, we add the converter to the window's resources for easy access.

XML
<window.resources>
.
.
    <local:MaxConverter x:Key="MaxConverter" />
.
.
</window.resources>

The demo shows a textbox with the largest salary. To make this work, we bind the Text property of the TextBox to the ItemsSource of the ListView and convert that to the largest value through our converter. In the snippet below, I've set the binding's Path to ItemsSource. This is a property of the ListView I've named TheList. We let the binding know we want to use the MaxConverter which is a StaticResource, and we let it know to use Salary as its ConverterParameter.

XML
.
.
  <TextBlock VerticalAlignment="Center" 
             Grid.Column="4" 
             Text="{Binding Path=ItemsSource, 
                            ElementName=TheList, 
                            Converter={StaticResource MaxConverter}, 
                            ConverterParameter=Salary}"/>
.
.

What about a DataTemplate?

In the demo project, I used a DataTemplate for the items in the ListView. Inside these items, I wanted a graphical representation of the travel distances to quickly view the spread. For demo purposes, I used a ProgressBar to do this. The ProgressBar has two properties which are very useful in this case, a value and a maximum. The length of the colored bar is a nice representation of the value, and is scaled according to the maximum value. This is how it's done:

XML
<DataTemplate x:Key="ExampleDataTemplate">
.
.
  <ProgressBar Foreground="Red" 
               Grid.Column="3" 
               Maximum="{Binding RelativeSource={RelativeSource FindAncestor, 
                                                 AncestorType={x:Type ListView}},
                                 Path=ItemsSource, 
                                 Converter={StaticResource MaxConverter}, 
                                 ConverterParameter=TravelDistance}" 
               Value="{Binding Path=TravelDistance}" />
.
.
</DataTemplate>

The binding to the Value property of the ProgressBar is a basic binding. Nothing special here. The fun begins with the Maximum property of the ProgressBar. The property needs to hold the largest value visible in a column; in this case, the travel distance. We have to create a binding to the ListView, and we would keep the possibility open to use this data template on other ListViews. So, we use a binding to the RelativeSource class. With this class, we instantiate two properties: one with which we tell the binding to find its ancestor, and one with which we tell what type of element we are looking for: RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListView}. Next, we tell the binding we want a path to the ItemsSource. And just like the earlier snippet, we pass this ItemsSource to the MaxConverter with a ConverterParameter TravelDistance. The result will be a double which holds the calculated value, the maximum.

Where to Go from Here

One thing these converters don't implement at this moment is exception handling. When using these in a "real" project, you may want to catch NullReferenceExceptions. Another thing that could cause problems is a column with strings or chars. These are easy to handle with by checking the type through Reflection. Besides lists, there are many other enumerables like IEnumerable<T> or Dictionary<Key,Value>. You may want to add functionality for these and make them work properly. But, I hope you get the main idea of how to use converters in ListViews to do some basic aggregations.

References

History

  • 07/22/08 - Initial article upload.
  • 07/23/08 - Downloads and picture updated

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Velicus B.V.
Netherlands Netherlands
Microsoft MVP Client Dev . Founder of http://StoreAppsUG.nl, the Dutch Windows Store apps and Windows Phone apps usergroup. XAML / HTML5 developer. Writer. Composer. Musician.

Twitter
@Sorskoot

Awards / Honers
• October 2010,2011,2012,2013: Awarded Microsoft Expression Blend MVP
• June 2009: Second Place in the WinPHP challenge
• February 2009: Runner-up in de Mix09 10k Challenge
• June 2008: Winner of the Microsoft expression development contest at www.dekickoff.nl

Bio
I started programming around 1992, when my father had bought our first home computer. I used GWBasic at that time. After using QBasic and Pascal for a few years I started to learn C/C++ in 1996. I went to the ICT Academy in 1997 and finnished it in 2002. Until December 2007 I worked as a 3D specialist. Besides modelling I worked on different development projects like a 3D based Scheduler and different simultion tools in C# and Java. Though out the years I've gained much experience with ASP.NET, Silverlight, Windows Phone and WinRT.

Comments and Discussions

 
GeneralThanks Man Pin
mynameatif18-Nov-09 23:36
mynameatif18-Nov-09 23:36 
GeneralExactly what I needed Pin
rhagerma25-Jul-08 10:44
rhagerma25-Jul-08 10:44 
GeneralRe: Exactly what I needed Pin
Timmy Kokke25-Jul-08 10:53
Timmy Kokke25-Jul-08 10:53 
You're welcome!

Dawn is nature's way of telling you to go to bed.

GeneralProgress bar colors Pin
mofle24-Jul-08 9:17
mofle24-Jul-08 9:17 
GeneralRe: Progress bar colors Pin
Timmy Kokke24-Jul-08 9:31
Timmy Kokke24-Jul-08 9:31 
JokePlease get the names right Pin
mav.northwind22-Jul-08 9:08
mav.northwind22-Jul-08 9:08 
GeneralRe: Please get the names right Pin
Timmy Kokke22-Jul-08 9:54
Timmy Kokke22-Jul-08 9:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.