Introduction
This article shows how to change the color of items in a ListView
, based on values in or associated with the item. The technique used here binds a ListView
to an ADO.NET DataTable
, and makes use of a custom value converter to determine what color each ListViewItem
should be.
Background
Here's a common scenario: you have a DataTable
which needs to be displayed in a ListView
, and any row which contains a value in some certain range (say, less than zero) should be "highlighted" with a special color. Perhaps the value which determines the ListViewItem
's color is not even displayed in the ListView
, but only exists in a DataRow
. How might one implement that functionality in WPF?
There are four steps involved with this task:
- Populate a
DataTable
and bind it to a ListView
.
- Specify how the
ListView
should display the DataTable
(i.e. specify where the items come from, configure the columns, etc.).
- Write a
Style
which highlights ListViewItem
s.
- Create a class which helps determine a
ListViewItem
's color.
This article's demo application creates a simple DataTable
, which contains a customer ID, name, and balance. If the customer is owed money (i.e. her balance is negative) then that customer's item is highlighted red. If the customer owes money, then the item is green.
Step one - Populate a DataTable and bind it to a ListView
Let's assume that our Window
subclass contains a ListView
in it, named 'listView
'. First we must create the DataTable
and set it as the ListView
's DataContext
.
public Window1()
{
InitializeComponent();
this.listView.DataContext = CreateDataTable();
}
DataTable CreateDataTable()
{
DataTable tbl = new DataTable( "Customers" );
tbl.Columns.Add( "ID", typeof( int ) );
tbl.Columns.Add( "Name", typeof( string ) );
tbl.Columns.Add( "Balance", typeof( decimal ) );
tbl.Rows.Add( 1, "John Doe", 100m );
tbl.Rows.Add( 2, "Jane Dorkenheimer", -209m );
tbl.Rows.Add( 3, "Fred Porkroomio", 0m );
tbl.Rows.Add( 4, "Mike Spike", 550m );
tbl.Rows.Add( 5, "Doris Yakovakovich", 0m );
tbl.Rows.Add( 6, "Boris Zinkwolf", -25m );
return tbl;
}
Step two - Specify how the ListView should display the DataTable
Now that the DataTable
is ready and available to be displayed, let's see how to show it in a ListView
.
<ListView
Name="listView"
ItemContainerStyle="{StaticResource ItemContStyle}"
ItemsSource="{Binding}"
>
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" />
<GridViewColumn Header="Name" DisplayMemberBinding="{Binding Name}" />
<GridViewColumn Header="Balance" Width="140">
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding Balance}" TextAlignment="Right" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
In the Window
's constructor we assigned a DataTable
to the ListView
's DataContext
, so setting its ItemsSource
property to '{Binding}
' means to simply bind against that DataTable
. Each column displayed in the ListView
is represented by a GridViewColumn
. The 'Balance
' column's CellTemplate
is set (as opposed to using the DisplayMemberBinding
) so that the monetary value can be right-aligned, which is typical for displaying numeric values.
The ListView
's ItemContainerStyle
property is set to a Style
, which is yet to be shown. ItemContainerStyle
is used because that property affects the Style
property of each ListViewItem
generated by the ListView
. Since we want to highlight an entire ListViewItem
that property is the logical place to apply our "highlight style".
Step three - Write a Style which highlights ListViewItems
In the previous section, the ListView
's ItemContainerStyle
was set to a Style
whose key is 'ItemContStyle
'. That Style
is seen below:
<Style x:Key="ItemContStyle" TargetType="{x:Type ListViewItem}">
<Style.Resources>
-->
-->
<local:NumberToPolarValueConverter x:Key="PolarValueConv" />
</Style.Resources>
-->
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Style.Triggers>
-->
<DataTrigger
Binding="{Binding Balance, Converter={StaticResource PolarValueConv}}"
Value="+1"
>
<Setter Property="Background" Value="{StaticResource ProfitBrush}" />
</DataTrigger>
-->
<DataTrigger
Binding="{Binding Balance, Converter={StaticResource PolarValueConv}}"
Value="-1"
>
<Setter Property="Background" Value="{StaticResource LossBrush}" />
</DataTrigger>
</Style.Triggers>
</Style>
The Style
sets two properties on each ListViewItem
: HorizontalContentAlignment
and Background
. The former is set to 'Stretch
' so that the elements in the ListView
's "cells" will occupy the entire surface area of those cells. That allows us to right-align the text in the 'Balance
' column.
The Background
property of each ListViewItem
is conditionally set to a "highlight brush" based on the customer's 'Balance
' value
. A DataTrigger
is used to evaluate a customer's 'Balance
' value
and then, if the customer either owes money or is owed money, that customer's ListViewItem
will have its Background
set to the appropriate brush.
Step four - Create a class which helps determine a ListViewItem's color
The DataTrigger
s seen in the previous section use a custom value converter in their Binding
s, called NumberToPolarValueConverter
. The purpose of that converter is to take in a customer's balance and return a simple value which indicates if that customer either is owed money, owes money, or has no balance. If the customer owes money (i.e. the customer's balance is more than zero dollars) then it returns +1
. If the customer is owed money, it returns -1
. If the customer has no balance, zero is returned.
This value converter is necessary because a DataTrigger
's Value
property cannot express a range, it can only express a distinct value. In other words, there is no way to have the DataTrigger
's Value
indicate that the trigger should execute when a customer's balance is, say, any number less than zero.
Since Value
cannot express a range, we can take the opposite approach and have the DataTrigger
's Binding
eliminate the range of values which the 'Balance
' field can have. If the Binding
evaluates to a small, discrete set of values (-1, 0, or +1) then the Value
property can easily be used to check for those specific values.
Here is how that value converter is implemented:
[ValueConversion( typeof(object), typeof(int) )]
public class NumberToPolarValueConverter : IValueConverter
{
public object Convert(
object value, Type targetType,
object parameter, CultureInfo culture )
{
double number = (double)System.Convert.ChangeType( value, typeof(double) );
if( number < 0.0 )
return -1;
if( number == 0.0 )
return 0;
return +1;
}
public object ConvertBack(
object value, Type targetType,
object parameter, CultureInfo culture )
{
throw new NotSupportedException( "ConvertBack not supported" );
}
}
External links
ADO.NET data binding in WPF
Related topics
History
- April 28, 2007 � Created the article.