More on ListBoxes: Using databinding to control appearance
In a previous blog posting, I discussed how I used a XAML ListBox to create a control rather like an iOS UITableView. In this installment, I’m going to expand upon that a little and explain how you can change the appearance of individual items in the list.
In iOS development, you usually get data into a UITableView by writing a callback method: for every row of data in the list, your callback decides what needs displayed. You might take the data from an array, for example, and then decide if you need to change the text color or add a fancy background pattern.
With XAML, you commonly databind the control. This simply means you tell the control where to get its data from, and it goes and does it all by itself. You can define this relationship either in the XAML, or programmatically in code.
This approach is convenient, but if you are like me, you are left scratching your head when it comes to changing the appearance of a specific item in the list.
Here’s a scenario. You are writing a Podcast player, and you have a list of podcasts stored in some kind of array. You databind this list to the ListBox, using the Title field, and hey presto: your app now lists all the podcasts. The XAML code do to this might look something like this:
<ListBox x:Name="PodcastList" SelectionChanged="PodcastList_SelectionChanged" SelectionMode="Single" Foreground="White">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
This XAML defines the ListBox, including linking to a method which is called if you tap on one of the items, and then defines the things which are actually included in the list as a single TextBox. The TextBox has its text property bound to an object which has at least a field called Title. I use some code like this:
PodcastList.ItemsSource = myListOfNames;
in the C#, so the ListBox knows exactly where to get the values.
Now, obviously as you listen to the Podcasts you want the app to remember them. So you add a Boolean field to your Podcast item object (the one which contains Title) called ListenedTo. When ListenedTo is true, you’re played the Podcast and so the title in the list should be greyed-out.
How do you make this work?
Well, it turns out you can bind data to control the appearance of something, as well as simply Text fields. There is a slight catch though: you need to convert the data being bound into something which the appearance properties can actually use. For example, if you are changing the color, you need the data binding to return a SolidColorBrush. And so you mustto intercept the databinding process, and convert that boolean ListenedTo into a SolidColorBrush.
Here is how you add that converter. First, add this class to the code-behind page:
public class ListenedToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
if ((bool)value == true) return new SolidColorBrush(Colors.DarkGray);
return new SolidColorBrush(Colors.White);
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
This is the logic which defines the conversion. Here, we examine the input value as a bool, and return the required color. (By the way, you don’t have to use a bool state, you could just as easily use a String object to store all manner of states, and return multiple colors.)
Then, in your XAML, add the following to the start of your XAML:
<Page.Resources>
<local:ListenedToColorConverter x:Key="ListenedToColor"></local:ListenedToColorConverter>
</Page.Resources>
And then adjust the ListBox code to be like this:
<ListBox x:Name="PodcastList" SelectionChanged="PodcastList_SelectionChanged" SelectionMode="Single" Foreground="White">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" Foreground="{Binding ListenedTo, Converter={StaticResource AvailabilityToColor}}" FontSize="22"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
We’ve added a second binding, this time to the Foreground property. As you can see, we use the ListenedTo field, and also the Converter we defined to change it to a color.
Now, assuming that you have populated the ListenedTo field in your data appropriately, when the ListBox is displayed, the titles that you have already played will be displayed in a darker color.
Updating Live
When I was working on this app, I wanted to make the ListBox items change color when you tapped them, not just when the ListBox was first displayed. After two days of trying to work it out, I had to give up. Windows 8 has much in common with Silverlight and WFC, but there are enough differences to make this kind of thing tricky to track down.
I spend most of Boxing Day looking at UpdateSourceTrigger and GetBindingExpression (the latter was added into Windows 8.1, and is something used in WPF a lot). However, I had no luck.
I then spent a day looking at PropertyChanged events, but first I found my event was always null, and then even when it fired (I set the default event equal to Delegte { } )nothing actually changed.
I was about to give up, when I realized I could simply redraw the ListBox by assigning its ItemSource property. This is not nice code, but it seems to work fine:
private void PodcastList_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Tapped on a podcast
ListBox lb = (sender as ListBox);
if (lb != null)
{
var item = (lb.SelectedItem as FeedItem);
if (item != null)
{
// Play back new podcast code.
// Ugh.. Refresh the UI using brute force.
PodcastList.ItemsSource = null;
PodcastList.ItemsSource = feed.Items;
}
}
}
Yes, I literally rip-away the data from the databound control and then give it back, forcing a refresh. I am not proud of this, but the pragmatic side of me - the side that actually wants to ship something every so often - let it slide.
NOTE: Ugh! What was I thinking? See this blog posting on the correct way to refresh the data.
If you have gotten this kind of update working without being so rude, please do let me know in the comments!
Ok, that’s it for now. Here’s wishing you all the best for the New Year!