Problem: You have a ListView containing data. You want to vary the appearance of items in the ListView according to the value of the data.
I spent some time on this in relation to a panel for a game I am writing. For example, you have a ListView containing numbers. How can you have negative numbers appear in red?
In desktop WPF (Windows Presentation Foundation) you could do this with Property Triggers but these are not supported in Store apps.
One way to do this is with a value converter. Add a class to your project called MyValueConverter. Make the class public, and inherit from Windows.UI.Xaml.Data.IValueConverter.
Right-click IValueConverter and choose Implement Interface to have Visual Studio create two stub methods, Convert and ConvertBack.
This class is going to return an object which will be applied to the Foreground property of a ListViewItem. The Convert method looks like this:
public object Convert(object value, Type targetType, object parameter, string language)
At runtime, the value argument will contain the item displayed in this row of the ListView. The targetType will match the type of the property we are setting, which in this case is a Brush object.
Now add an instance of MyValueConverter to MainPage.xaml (or App.xaml) as a resource. If there is no Page.Resources element, create it, and add an instance of MyValueConverter with the Key “NumberForegroundConverter”:
<Page.Resources>
<local:MyValueConverter x:Key="NumberForegroundConverter" />
</Page.Resources>
Next, select the ListView element in the XAML editor or designer. Right-click the selected element in the designer, and choose Edit Additional Templates – Edit Generated Item Container (ItemContainerStyle) – Edit a Copy …
Accept the default name of ListViewItemStyle1 and click OK.
This generates an element that defines the layout and appearance of items in the ListView. Currently it does not appear to do anything, since it is a copy of the default settings.
Find the element called <ListViewItemPresenter> which is nested within <ControlTemplate TargetType=”ListViewItem”>. No Foreground attribute for ListViewItemPresenter is generated, but we can add one:
Foreground="{Binding Converter={StaticResource NumberForegroundConverter}}"
If you now run the project, you will get an exception, because the methods in MyValueConverter do not yet have any code. Now we have to think about the type of the items in the ListView. In this example, I just typed some strings into the XML editor:
<ListView ItemContainerStyle="{StaticResource ListViewItemStyle1}">
<x:String>145</x:String>
<x:String>-30</x:String>
<x:String>442</x:String>
</ListView>
All the items are strings, so the Convert method can look like this:
String s = (String)value; //note this ONLY works if the item is always a string
if (float.Parse(s) < 0)
{
return new SolidColorBrush(Windows.UI.Colors.Red);
}
else
{
return new SolidColorBrush(Windows.UI.Colors.White);
}
We don’t care about the ConvertBack method so can use this code:
return Windows.UI.Xaml.DependencyProperty.UnsetValue;
It works but there are some issues. One oddity is that when you roll the mouse over a negative number, it looks the same as a positive number.
This is because we did a converter for the Foreground property but not for the SelectedForeground property. XAML in Store apps makes extensive use of themes, and themes include a lot of brushes.
Another issue, which may or may not impact your application, is that the converter code does not run again if you change the displayed item dynamically. That is, if you replace the item it updates OK, but if you update the existing item it does not.
A slightly more complex example will demonstrate this. Let’s say that rather than displaying strings, the ListView is displaying Widget quantities, where a negative number indicates backorders. The ListView is bound to an ObservableCollection<Widget>, and the Widget class implements INotifyPropertyChanged so that the ListView will update automatically when a Widget property changes. Note that the NumberForegroundConverter must be updated to accept a Widget value rather than a string.
Here is what happens if a Widget had a negative quantity when the ListView was first populated, but got dynamically updated to a positive value (some stock arrived):
Oops! Now the positive quantity is in red.
We can fix this by abandoning the converter, and instead giving the Widget class a Foreground property of its own, calculated to return Red for negative quantities and White for positive. Make sure it fires a NotifyPropertyChanged event when updated. Now the Foreground property in <ListViewItemPresenter> looks like this:
Foreground="{Binding Foreground}"
It works:
I used this approach in my game in order to implement an Enabled property that indicates items which are unselectable. This changes dynamically according to the state of the play.
Note that you are unlikely to want a Foreground property in your business objects, but could create a DisplayWidget class for the purpose.
I realise that these are not the only ways to create a ListView which styles items differently according to their values, but they may be the simplest. Other suggestions and comments are welcome.
Update: Mike Taulty has some comments and suggestions here.