Skip to main content

A board game in MVVM Part 3 - The View

This is a continuation of my experience creating a quick board game in silverlight 5
Original Post
Part 1 - The Model
Part 2 - The AI
Play the Game

An important thing to keep in mind when looking at the UI here is that I'm following the MVVM design pattern. I didn't want to be manipulating anything in code-behind. The code-behind for the entire game board here is simply:

  1. namespace Viking
  2. {
  3.     using System.Windows.Controls;
  4.  
  5.     public partial class MainPage : UserControl
  6.     {
  7.         public MainPage()
  8.         {
  9.             InitializeComponent();
  10.         }
  11.     }
  12. }

That's IT! Nothing else. Seriously. If you haven't worked with Silverlight and MVVM much this might seem surprising. The View is entirely XAML and binding.

To start, I added my xaml namespaces. I'm using the Silverlight Toolkit and I've got my model and a couple other things separated into a separate Silverlight Library. The purpose of the separate DLL is in case I ever want to redo this project in 3D, that way I can reuse my code.

  1. <UserControl 
  2.     ...
  3.     xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
  4.     xmlns:local="clr-namespace:Viking"
  5.     xmlns:library="clr-namespace:Viking;assembly=Viking.Library"
  6.     ... >

I'm not going to show all the XAML in this blog post but merely a few points that people might find interesting. I'll cover the ViewModel in a later post, but for now here's how it's instantiated:

  1.     <UserControl.DataContext>
  2.         <local:MainViewModel />
  3.     </UserControl.DataContext>

First of all is the board. Its simplicity may be surprising to somebody who hasn't seen the power of Silverlight and MVVM before. There's no images here, no long complicated markup. It's short and concise as far as all that functionality goes.

  1. <Border BorderBrush="Black" BorderThickness="1">
  2.     <ItemsControl ItemsSource="{Binding Game.Board.Squares}">
  3.         <ItemsControl.ItemsPanel>
  4.             <ItemsPanelTemplate>
  5.                 <toolkit:WrapPanel Height="657" Width="657" />
  6.             </ItemsPanelTemplate>
  7.         </ItemsControl.ItemsPanel>
  8.         <ItemsControl.ItemTemplate>
  9.             <DataTemplate>
  10.                 <Border Width="73" Height="73" BorderThickness="1" BorderBrush="Black" Background="White">
  11.                     <Grid>
  12.                         <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="BlanchedAlmond" Visibility="{Binding IsStartPosition, Converter={StaticResource BoolToVisibilityConverter}}" />
  13.                         <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="Brown" Visibility="{Binding IsRestricted, Converter={StaticResource BoolToVisibilityConverter}}" />
  14.                         <Border Visibility="{Binding Occupant, Converter={StaticResource HideOnNullConverter}}">
  15.                             <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="#6600FF00" Visibility="{Binding Occupant.IsActive, Converter={StaticResource BoolToVisibilityConverter}}" />
  16.                         </Border>
  17.                         <Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding DataContext.MakeMoveCommand, RelativeSource={RelativeSource AncestorType=local:MainPage}}" CommandParameter="{Binding}">
  18.                             <Button.Template>
  19.                                 <ControlTemplate>
  20.                                     <Rectangle HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Fill="#2200FF00" Visibility="{Binding IsValidMove, Converter={StaticResource BoolToVisibilityConverter}}" />
  21.                                 </ControlTemplate>
  22.                             </Button.Template>
  23.                         </Button>
  24.                         <Border Visibility="{Binding Occupant, Converter={StaticResource HideOnNullConverter}}">
  25.                             <Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Command="{Binding DataContext.ActivatePieceCommand, RelativeSource={RelativeSource AncestorType=local:MainPage}}" CommandParameter="{Binding Occupant}" IsEnabled="{Binding Occupant.IsMoveable}">
  26.                                 <Button.Template>
  27.                                     <ControlTemplate>
  28.                                         <Ellipse HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stroke="{Binding Occupant, Converter={StaticResource KingStrokeConverter}}" StrokeThickness="{Binding Occupant, Converter={StaticResource KingStrokeConverter}}" Fill="{Binding Occupant, Converter={StaticResource PlayerColorConverter}}">
  29.                                             <Ellipse.Effect>
  30.                                                 <DropShadowEffect Opacity=".5" ShadowDepth="3" />
  31.                                             </Ellipse.Effect>
  32.                                         </Ellipse>
  33.                                     </ControlTemplate>
  34.                                 </Button.Template>
  35.                             </Button>
  36.                         </Border>
  37.                     </Grid>
  38.                     <Border.Effect>
  39.                         <DropShadowEffect />
  40.                     </Border.Effect>
  41.                 </Border>
  42.             </DataTemplate>
  43.         </ItemsControl.ItemTemplate>
  44.     </ItemsControl>
  45. </Border>

That's all there is to the board itself. It's a bunch of templated items within a WrapPanel. The ItemsSource of the ItemsControl is bound to the Game.Board.Squares collection. If you look inside the DataTemplate for each square you'll see:

  • A rectangle bound to IsStartPosition using a BoolToVisibilityConverter - Represents the lighter colored squares
  • A rectangle bound to IsRestricted using a BoolToVisibilityConverter - Represents the center, dark square
  • A border bound to Occupant using a HideOnNullConverter surrounding a Rectange bound to Occupant.IsActive - Represents an active piece (dark green when you click on it)
  • A button bound to IsValidMove using a BoolToVisibilityConverter - Represents possible moves (light green after activating a piece)
  • A border bound to Occupant using a HideOnNullConverter surrounding a button with IsEnabled bound to IsMoveable surrounding an Ellipse using various color converters to symbolize the color of the player, and whether the piece is a king.

The bindings here automatically update what is visible/enabled based upon the PropertyChanged event of the object it is bound to. The ItemsControl is a container for multiple objects that utilize a template, and the ItemsPanel is the parent panel that the items are laid out upon.

The AI controls reuse the same converters and bind in a few commands:

  1. <Grid HorizontalAlignment="Stretch" Margin="0 10" Height="25">
  2.     <Grid.Resources>
  3.         <Style TargetType="TextBlock">
  4.             <Setter Property="FontWeight" Value="Bold" />
  5.             <Setter Property="FontSize" Value="14" />
  6.             <Setter Property="VerticalAlignment" Value="Center" />
  7.         </Style>
  8.     </Grid.Resources>
  9.     <Border Margin="0" CornerRadius="3 0 0 3" BorderThickness="0" Background="{Binding Game.CurrentPlayer, Converter={StaticResource PlayerColorConverter}}" Opacity=".15" />
  10.     <Border Margin="0" CornerRadius="3" BorderThickness="1" Background="Transparent" BorderBrush="{Binding Game.CurrentPlayer, Converter={StaticResource PlayerColorConverter}}" />
  11.     <StackPanel Orientation="Horizontal">
  12.         <Grid Width="8" VerticalAlignment="Stretch" Margin="0 0 5 0">
  13.             <Border x:Name="FillRectangle" BorderBrush="Navy" BorderThickness="0" Background="{Binding Game.CurrentPlayer, Converter={StaticResource PlayerColorConverter}}" Width="8" Margin="0" CornerRadius="3 0 0 3" />
  14.             <Border x:Name="FillShadingRectangle" Width="8" Margin="0" CornerRadius="3 0 0 3">
  15.                 <Border.Background>
  16.                     <LinearGradientBrush StartPoint="1,1" EndPoint="0,0">
  17.                         <GradientStop Color="#AA010000" Offset="0" />
  18.                         <GradientStop Color="Transparent" Offset=".5" />
  19.                         <GradientStop Color="#BBFFFFFF" Offset="1" />
  20.                     </LinearGradientBrush>
  21.                 </Border.Background>
  22.             </Border>
  23.         </Grid>
  24.         <TextBlock Text="{Binding Game.CurrentPlayer, StringFormat='Current Player: {0}'}" Margin="0 0 10 0" />
  25.     </StackPanel>
  26.     <Button FontSize="14" Cursor="Hand" Command="{Binding RestartGameCommand}" HorizontalAlignment="Right" Margin="2" Padding="10 0">
  27.         <Button.Template>
  28.             <ControlTemplate>
  29.                 <Grid HorizontalAlignment="Stretch">
  30.                     <Border x:Name="FillRectangle" BorderBrush="#555" BorderThickness="2 0 0 0" Background="{Binding Game.CurrentPlayer, Converter={StaticResource PlayerColorConverter}}" Margin="0" CornerRadius="0 3 3 0" />
  31.                     <Border x:Name="FillShadingRectangle" Margin="0" CornerRadius="0 3 3 0">
  32.                         <Border.Background>
  33.                             <LinearGradientBrush StartPoint="1,1" EndPoint="0,0">
  34.                                 <GradientStop Color="#AA010000" Offset="0" />
  35.                                 <GradientStop Color="Transparent" Offset=".25" />
  36.                                 <GradientStop Color="#BBFFFFFF" Offset="1" />
  37.                             </LinearGradientBrush>
  38.                         </Border.Background>
  39.                     </Border>
  40.                     <ContentPresenter Margin="10 1" />
  41.                 </Grid>
  42.             </ControlTemplate>
  43.         </Button.Template>
  44.         Restart
  45.     </Button>
  46. </Grid>
  47. <Grid>
  48.     <Grid.ColumnDefinitions>
  49.         <ColumnDefinition Width="*" />
  50.         <ColumnDefinition Width="*" />
  51.     </Grid.ColumnDefinitions>
  52.  
  53.     <Button Command="{Binding TogglePlayerTypeCommand}" CommandParameter="{Binding AttackerPlayer}">
  54.         <TextBlock Text="{Binding Game.NextAttacker, StringFormat='Attacker: {0}'}" />
  55.     </Button>
  56.     <Button Grid.Column="1" Command="{Binding TogglePlayerTypeCommand}" CommandParameter="{Binding DefenderPlayer}">
  57.         <TextBlock Text="{Binding Game.NextDefender, StringFormat='Defender: {0}'}" />
  58.     </Button>
  59. </Grid>
  60. <TextBlock HorizontalAlignment="Center"><Italic>(Player type changes go into effect next game)</Italic></TextBlock>

Next time I'll go over the commands and the converters.