
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 ProgressBar
s 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:
public class MaxConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter,
System.Globalization.CultureInfo culture)
{
double Max = 0.0;
Type ValueType = value.GetType();
if(ValueType.Name == typeof(List<>).Name)
{
foreach (var item in (IList)value)
{
Type ItemType = item.GetType();
PropertyInfo ItemPropertyInfo = ItemType.GetProperty((string)parameter);
double ItemValue = (double)ItemPropertyInfo.GetValue(item, null);
if (ItemValue > Max)
Max = ItemValue;
}
return Max;
}
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.
<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.
<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
.
.
.
<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:
<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 ListView
s. 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 NullReferenceException
s. 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 ListView
s to do some basic aggregations.
References
History
- 07/22/08 - Initial article upload.
- 07/23/08 - Downloads and picture updated
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.