I’ve decided to take a break from some of the more back-end concepts in my game refactoring for a bit, and concentrate for a bit on the front-end. As someone whose primary job function is to release an ASP.NET web application, writing code for Windows is both a significant challenge, as well as a breath of fresh air.
As Andrew Hunt puts it (paraphrased) in Pragmatic Programmer, you should learn outside your common comfort zone – he had stated it in the context of learning languages, but the same holds true for frameworks, and GUIs as well. Or even areas of application development … or even to systems deployment or administration. Each of these realms gives a unique perspective on how applications work in the wild.
But I digress.
To restate the application, it’s a game, whose primary surface is a game Grid ( a two dimensional representation of tiles). The tiles on the Grid can be one of N options, each represented by a different graphical tile. By writing a new WPF UI for the application, I needed to find out how to render the grid.
First things first … drop the Grid onto the XAML form and run, check. Row and Column definitions matching the dimensions of the game Grid object, check. Render a graphic for each cell, check (they’re all the same at this point). Conditionally bind a different color based on the content of the bound data…. uh? Here’s the problem. Upon research, I came across this great interface, called IValueConverter. This interface allows you to map one of your values to another value.
public class CellValueToColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var cell = value as Cell;
return CellValueToColorString(cell);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
So that allows us to do a one-way conversion from a cell value to a color (take it for granted there’s another method in there that takes cell, and returns a string color based on cell.CellValue.
So how do we actually do the conditional work in XAML then, you may ask? Well, let’s jump right to some XAML and deconstruct a working example. Keep in mind the Ellipse objects representing each cell are created when the game’s Grid is redrawn.
<Window x:Class="SameBloxUIWinWpf.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SameBloxUIWinWpf.Assets"
Title="SameBlox" Height="400" Width="400">
<Window.Resources>
<local:CellValueToColorConverter x:Key="CellValueConverter"/>
<Style x:Key="lifeStyle" TargetType="{x:Type Ellipse}">
<Setter Property="Opacity" Value="0.75" />
<Setter Property="Fill" Value="{Binding Path=Cell, Converter={StaticResource CellValueConverter}}" />
</Style>
</Window.Resources>
<DockPanel>
<StackPanel DockPanel.Dock="Bottom"
Orientation="Horizontal"
HorizontalAlignment="Center" >
<Label Height="33" Name="PotentialScore" Width="120" HorizontalAlignment="Left">0</Label>
<Button x:Name="newGameButton" Margin="5" Click="newGameButton_Click">New Game</Button>
<Label Height="33" Name="Score" Width="120" HorizontalAlignment="Right" HorizontalContentAlignment="Right">0</Label>
</StackPanel>
<Grid x:Name="GameGridControl" Background="Black" />
</DockPanel>
</Window>
So above you can see the first interesting part in here is the declaration of a local resource object. CellValueToColorConverter (the class we created before) is given a name: CellValueConverter. Second, a style called “lifeStyle” is created, targetting our ellipses. From here, we’re setting the Fill property (color) to bind the Cell property of the Ellipse’s DataContext to the CellValueConverter! Now because we’re adding the Ellipses dynamically, there’s one more important code snippet, below.
ellipse.Style = Resources["lifeStyle"] as Style;
And that’s really all there is to it.I should mention there’s one other ‘trick’ I added for ease of calculation (& to help perfomance a bit) was to store both the Cell object and the matching coordinate in the DataContext. Below you can see that Cell is one of the properties of that DataContext.
public class WinGridCellDataContext
{
public GridCoordinate Location { get; set; }
public Cell Cell { get; set; }
}