Building an Open Data App – Part 7: Styles and Converters

So far we’ve grabbed a data set, supported it, stored it, parsed it, displayed it, and linked events to it. Not too shabby. Now, let’s give the user some meaningful data to make decisions with. Some of this will involve binding more data to the view, while some it will involve applying a bit of design to emphasis important details.

Before we get into that, it may be good idea to give the user the chance to force a data update. So we’ll add a button and event to call our update code.

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ListBox Grid.Row="0" ItemsSource="{Binding BikeStations}">
        <ListBox.ItemTemplate>
            <DataTemplate>
                <ListBoxItem Tag="{Binding}" Tapped="BikeStation_Tapped">
                    <TextBlock Text="{Binding StationName}"/>
                </ListBoxItem>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <Button Grid.Row="1" Content="Update list" Click="UpdateListButton_Click"/>
</Grid>

Notice the addition of the RowDefinitions and Grid.Row properties. This keeps everything organized and looking good. Also, the Height="Auto" tells the control to use what space it needs, and the Height="*" tells the control to give it whatever space is left over. We could handle the updating with a background task, but we’ll deal with that another day. Instead, it’s a simple button click event calling the appropriate method. Remember how I mentioned to extract the property updating in your MainDataModel.LoadingData()? This is why. Here’s the methods.

//In ListPage.xaml code behind
private async void UpdateListButton_Click(object sender, RoutedEventArgs e)
{
    await MainDataModel.Instance.UpdateData();
}

//In MainDataModel
public async Task UpdateData()
{
    var jsonString = await GetBikeStationsFromServer(new Uri(BikeStationJsonUrlString, UriKind.Absolute));

    await SetStationData(jsonString);
}

private async Task SetStationData(string jsonString)
{
    if (jsonString.Length > 0)
    {
        var localFolder = ApplicationData.Current.LocalFolder;
        var file = await localFolder.CreateFileAsync(JsonFileName, CreationCollisionOption.ReplaceExisting);

        using (var writer = new StreamWriter(await file.OpenStreamForWriteAsync()))
        {
            writer.Write(jsonString);
        }

        try
        {
            var bikeStationsResponse = JsonConvert.DeserializeObject<BikeStationResponse>(jsonString);
            BikeStations = new ObservableCollection<BikeStation>(bikeStationsResponse.Stations);
            this.OnPropertyChanged("BikeStations");
        }
        catch (Exception e)
        {
            //Handle the exception
        }
    }
}

To decide what to display, we’ll look at the BikeStation class and determine the information people really need to know. It’s a good idea to also look at your dataset because, as you may remember, some of the fields are empty. Right away AvailableBikes and AvailableDocks should catch your attention. Also, we’ll let users know when the last update came.

Our ListBoxItem will now look like this:

<ListBox.ItemTemplate>
    <DataTemplate>
        <ListBoxItem Tag="{Binding}" Tapped="BikeStation_Tapped">
            <Grid>
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
                <ColumnDefinition Width="Auto"/>
                <ColumnDefinition Width="*"/>
            </Grid.ColumnDefinitions>
                <TextBlock Grid.Row="0" Grid.ColumnSpan="4" Text="{Binding StationName}"/>
                <TextBlock Grid.Row="1" Grid.Column="0" Text="Available bikes: "/>
                <TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding AvailableBikes}"/>
                <TextBlock Grid.Row="1" Grid.Column="2" Text="Available docks: "/>
                <TextBlock Grid.Row="1" Grid.Column="3" Text="{Binding AvailableDocks}"/>
                <TextBlock Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Text="Last update time: "/>
                <TextBlock Grid.Row="2" Grid.Column="2" Grid.ColumnSpan="2" Text="{Binding LastCommunicationTime}"/>
            </Grid>
        </ListBoxItem>
    </DataTemplate>
</ListBox.ItemTemplate>

The only thing left is to do some light styling of the bound TextBlocks and then we’ll add a simple converter. The styling will add a margin around the text and make it bigger. You’ll need to add a style via StaticResources, all of which you can see below.

//This comes before your xaml's main grid
<Page.Resources>
    <Style x:Name="DataTextBlockStyle" TargetType="TextBlock">
        <Setter Property="FontSize" Value="20"/>
        <Setter Property="Margin" Value="10,0,10,0"/>
    </Style>
</Page.Resources>

//This is an example to show how to add the style to the textblock
<TextBlock Grid.Row="0" Grid.ColumnSpan="4" Text="{Binding StationName}" Style="{StaticResource DataTextBlockStyle}"/>

For our super simple converter we’re going to highlight stations with a lot or a few bikes and docks remaining. How we’ll do this is by, first, putting the appropriate TextBlocks in a Border and then setting the Border‘s background based on the value of the bound property. First, the setup:

//This goes in the xaml Page opening tag. It allows you to reference the folder later.
xmlns:data="using:BuildAnAppSeries.DataModel"

//This goes in the xaml Page.Resources. This will expose your converter as a StaticResource.
<data:BackgroundColorConverter x:Key="BackgroundColorConverter"/>

/*This replaces any TextBlock to which you want to apply the converter. Notice the Row and Column values belong to the Border, not the TextBlock
and that the AvailableBikes property (what we want to base the conversion on) is set as a Binding int he Background property.*/
<Border Grid.Row="1" Grid.Column="1" Background="{Binding AvailableBikes, Converter={StaticResource BackgroundColorConverter}}">
    <TextBlock Text="{Binding AvailableBikes}" Style="{StaticResource DataTextBlockStyle}"/>
</Border>

//Finally, the converter.
public class BackgroundColorConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, string language)
    {
        if ((int)value > 9)
        {
            return new SolidColorBrush(Color.FromArgb(255, 0, 255, 0));
        }
        else if((int)value < 5)
        {
            return new SolidColorBrush(Color.FromArgb(255, 255, 0, 0));
        }
        else
        {
            return new SolidColorBrush(Color.FromArgb(0, 0, 0, 0));
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, string language)
    {
        throw new NotImplementedException();
    }
}

So you can see that the converter is merely returning the normal, expected value of the property (in this case, a SolidColorBrush). As we’re not using the ConvertBack method, don’t worry about it throwing an exception. Converters can be used for all sorts of things. So long as they pass back the proper value to affect the property they’re used in, most anything is fair game.

This entry was posted in .NET, Building an Open Data App, Windows 8, Windows 8.x, Windows Phone, XAML and tagged , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s