5.5. Data Templates and Styles
Let's imagine that we wanted to implement a version of
tic-tac-toe that's more fun to play (that's an important feature in most
games). For example, one variant of tic-tac-toe allows players to have only
three of their pieces on at any one time, dropping the first move off when the
fourth move is played, dropping the second move when the fifth is played, and
so on. To implement this variant, we need to keep track of the sequence of
moves, which we can do with a PlayerMove class, as in
Example 5-20.
Example 5-20. A custom type suitable for tracking
tic-tac-toe moves
namespace TicTacToe {
public class PlayerMove {
private string playerName;
public string PlayerName {
get { return playerName; }
set { playerName = value; }
}
private int moveNumber;
public int MoveNumber {
get { return moveNumber; }
set { moveNumber = value; }
}
public PlayerMove(string playerName, int moveNumber) {
this.playerName = playerName;
this.moveNumber = moveNumber;
}
}
}
Now, instead of using a simple string for each of the button
object's content, we'll use an instance of PlayerMove in
Example 5-21. Figure 5-6
shows the brilliance of such a change.
Example 5-21. Adding the PlayerMove as Button content
namespace TicTacToe {
public partial class Window1 : Window {
...
int moveNumber;
void NewGame( ) {
...
this.moveNumber = 0;
}
void cell_Click(object sender, RoutedEventArgs e) {
...
// Set button content
//button.Content = this.CurrentPlayer;
button.Content =
new PlayerMove(this.CurrentPlayer, ++this.moveNumber);
...
}
...
}
}
As you'll recall from
Chapter 4, what's happening in
Figure 5-6 is that the button doesn't have enough information to render
a PlayerMove object, but we can fix that with a data template.
5.5.1. Data Templates
Recall from
Chapter 4 that WPF allows you to define a data template, which is a
tree of elements to expand in a particular context. Data templates are used to
provide an application with the ability to render non-visual objects, as shown
in Example 5-22.
Example 5-22. Setting a PlayerMove data template
without styles
<?Mapping XmlNamespace="l" ClrNamespace="TicTacToe" ?>
<Window ... xmlns:local="local">
<Window.Resources>
<DataTemplate DataType="{x:Type local:PlayerMove}">
<Grid>
<TextBlock
TextContent="{Binding Path=PlayerName}"
FontSize ="32"
FontWeight="Bold"
VerticalAlignment="Center"
HorizontalAlignment="Center" />
<TextBlock
TextContent="{Binding Path=MoveNumber}"
FontSize="16"
FontStyle="Italic"
VerticalAlignment="Bottom"
HorizontalAlignment="Right" />
</Grid>
</DataTemplate>
...
</Window.Resources>
...
</Window>
Using the XAML mapping syntax introduced in
Chapter 1, we've mapped the PlayerMover type into the XAML
with the mapping directive and the xmlns attribute, which we've used
as the data type of the data template. Now, whenever WPF sees a PlayerMove
object, such as the content of all of our buttons, the data template will be
expanded. In our case, the template consists of a grid to arrange two text
blocks, one showing the player name in the middle of the button and one showing
the move number in the bottom right, along with some other settings to make
things pretty.
5.5.2. Data Templates with Style
However, these property settings are buried inside a data
template several layers deep. Just as it's a good idea to take "magic numbers"
out of your code, pulling them out and giving them names for easy maintenance,
it's a good idea to move groups of settings into styles,
as in Example 5-23.
Example 5-23. Setting a PlayerMove data template with
styles
<Window.Resources>
<Style x:Key="CellTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="32" />
<Setter Property="FontWeight" Value="Bold" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style x:Key="MoveNumberStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontStyle" Value="Italic" />
<Setter Property="VerticalAlignment" Value="Bottom" />
<Setter Property="HorizontalAlignment" Value="Right" />
</Style>
<DataTemplate DataType="{x:Type l:PlayerMove}">
<Grid>
<TextBlock
TextContent="{Binding Path=PlayerName}"
Style="{StaticResource CellTextStyle}" />
<TextBlock
TextContent="{Binding Path=MoveNumber}"
Style="{StaticResource MoveNumberStyle}" />
</Grid>
</DataTemplate>
</Window.Resources>
It's common to use styles, which set groups of properties, with
data templates, which create groups of elements that have groups of properties.
Figure 5-7 shows the result.
Still, as nice as Figure
5-7 is, the interaction is kind of boring given the capabilities of
WPF. Let's see what we can do with style properties as the application is used.
 |