Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / database

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

5.00/5 (2 votes)
12 Jan 2015CPOL4 min read 7.9K  
EF Code First and WPF with the Chinook Database - Styles and DataTemplates

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 advanced concepts such as using adorners to display additional details.

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

Image 1

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 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).


Image 4 Image 5

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)