EF Code First and WPF with the Chinook database. Part 3 – Styles and DataTemplates 101

I have decided to split out the styling work into 2 posts as it would be a huge post to restyle even something so simple.  so this post is going to change the controls to be more style-conscious and introduce basic style and the next post will introduce some more advance concepts such as using adorners to display additional details.

Going back to the previous post we had a basic app as follows:

This is slightly ugly and needs a little work so the first thing I want to add is  a style that will tidy up the artist list.

I want the artist list to have a nice look and feel with rounded button type items and also contain a count of the number of albums – I want each item in the list to look something like this:

image

The first step to getting this design in place is adding the DataTemplate so that it is picked up by the item.  A DataTemplate is a template that gets applied to an object that has no default way of showing itself and manifests itself as its type name.

NOTE – this can also be be done by overriding Object.ToString() but this is a client side post so I don’t want to have to change model code to do something.

I have added a simple DataTemplate to the Application.Resources section in App.xaml as follows:

<DataTemplate DataType="{x:Type business:Artist}">
    <TextBlock x:Name="contentHolder">
        <TextBlock.Text>
            <MultiBinding StringFormat="{}{0} - {1} albums">
                <Binding Path="Name"/>
                <Binding Path="Albums.Count" />
            </MultiBinding>
        </TextBlock.Text>                
    </TextBlock>
    <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding Albums.Count}" Value="1">
            <DataTrigger.Setters>
                <Setter Property="TextBlock.Text" TargetName="contentHolder">
                    <Setter.Value>
                        <MultiBinding StringFormat="{}{0} - {1} album">
                            <Binding Path="Name"/>
                            <Binding Path="Albums.Count" />
                        </MultiBinding>
                    </Setter.Value>
                </Setter>
            </DataTrigger.Setters>
        </DataTrigger>
    </DataTemplate.Triggers>
</DataTemplate>

There are several things going on here:

  1. the first line declares the type that this template is associated with – you have to add the namespace to this file first in order to make this work:
    xmlns:business="clr-namespace:MusicApp.Model;assembly=MusicApp.Model"
    
  2. The MultiBinding has an associated StringFormat – this is the same as a normal format string except for the “{}” after the equals sign – this simply escapes the format string.
  3. The binding to Albums.Count – yes you can bind to “built-in” properties as well.
  4. The DataTrigger is a simple way of saying that if we have only one album then the trailing text shouldn’t have an ‘s’ on the end.

It is possible to do the whole string in a converter but I like the declarative way as you can see everything going on in one place.  For those that like using converters you would bind the TextBlock’s Text property to the whole Artist ({Binding}) and then use a converter like the following:

public class ArtistStringConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        string retVal = string.Empty;
        var artist = value as Artist;
        if (artist != null)
        {
            retVal += string.Format("{0} - {1} {2}", artist.Name, artist.Albums.Count, artist.Albums.Count == 1 ? "artist" : "artists");
        }

        return retVal;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

this is then used from the xaml as follows:

<local:ArtistStringConverter x:Key="artistConverter"/>
<DataTemplate DataType="{x:Type business:Artist}">
    <TextBlock x:Name="contentHolder" Text="{Binding Converter={StaticResource artistConverter}}" />
</DataTemplate>

Once this is added (either option) you need to remove the DisplayMemberPath property from the ArtistList.xaml view so that the template is used instead.

I now have a list of Artists that show the name and the total albums they have so I can work on the actual style to make the list items look like the above image.

I want all three lists to look the same so I am adding a style with no key to the app.xaml file.  If you want specific styles for your controls then you can add ‘x:Key=”YourKeyName”’ and reference it using ‘{StaticResource YourKeyName}’ (in this instance the reference would be in the ItemContainerStyle property of the ListBox).

<Style TargetType="ListBoxItem">
    <Style.Setters>
        <Setter Property="Margin" Value="5,2" />
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="ListBoxItem">
                    <Grid>
                        <Rectangle Opacity="0.5" Height="30" StrokeThickness="1" x:Name="backBox"
                                        Stroke="Silver" RadiusX="5" RadiusY="5" Fill="Azure"/>
                        <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
                    </Grid>
                    <ControlTemplate.Triggers>
                        <Trigger Property="Selector.IsSelected" Value="True">
                            <Setter TargetName="backBox" Property="Fill" Value="Silver"/>
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style.Setters>
</Style>

Again I have a couple of things going on inside this style:

  1. It targets ListBoxItem as this is what we want to style (not the ListBox itself).
  2. The margin separates the items nicely.
  3. The actual style part is contained inside a ControlTemplate which is then assigned  the Template property of the ListBoxItem.
  4. The ContentPresenter will present whatever content it is given, in this case it is presenting the passed in DataTemplate.
  5. The trigger here indicates that when the ListBoxItem is selected we want to change the fill colour of the rectangle to indicate we have selected an item.

To finish I have added a new DataTemplate for the albums – this looks the same with the type and property names changed to display the correct things and I have also rearranged the view as follows:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="0.5*" />
        <ColumnDefinition Width="0.5*" />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="0.5*"/>
        <RowDefinition Height="0.5*"/>
    </Grid.RowDefinitions>

    <ListBox ItemsSource="{Binding Path=Artists}" Margin="5" 
                Grid.Column="0" Grid.Row="0"
                Grid.RowSpan="2"
                SelectedItem="{Binding SelectedArtist}" />
        
    <ListBox ItemsSource="{Binding Path=SelectedArtist.Albums}" Margin="5" 
                Grid.Column="1" Grid.Row="0" 
                SelectedItem="{Binding SelectedAlbum}" />
        
    <ListBox ItemsSource="{Binding Path=SelectedAlbum.Tracks}" Margin="5" 
                DisplayMemberPath="Name" 
                Grid.Column="1" Grid.Row="1" />
</Grid>

This now gives me the following view which I am sure you will agree looks a lot better and is easily achievable by simply using XAML – we could have done this in blend as well but for the simple changes we have made I prefer to edit the XAML directly.

image

In the next post I will be adding some adorner goodness to the screen for displaying album and track details (which also covers changes to the model).

About these ads

About Leom Burke
Working for a healthcare company in the UK with a focus on C#/WPF/WCF/EF. Personally I also enjoy working with ASP.NET (MVC), ruby and investigating best practice using methods like TDD and bettering the quality of code. This is personal blog and as such has no relationship to the thoughts, views and feelings of the company I happen to be working for.

2 Responses to EF Code First and WPF with the Chinook database. Part 3 – Styles and DataTemplates 101

  1. Namgeun Jeong says:

    You missed something in the ListBox for Album.
    To make this work, I think you need to override ToString() method in the classes Album, or put the DisplayMemberPath=”Title” back in the xaml file
    .
    class Album
    {

    public override ToString()
    {
    return Title;
    }
    }

    • Leom Burke says:

      This is not missing. If you read the line above where the ListBox is added I have said that I add a DataTemplate for Album that matches the one for Artist. This is just a simple template that converts an Album into the string representation.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: