Sunday, February 28, 2010

Shatranj – WPF Dependency Property value providers & Resolving Conflicts

We’ve couple of wrinkles in the “final” code there.

  • Border has a name specified.  It’s not a problem in itself.  Problem is the name is used by the template style trigger to set the value.
  • Both values are specified using Template triggers and when there’s a conflict, the last one wins.  This is a big one!

TemplateTriggerConflicts

  • Border properties values do not respect the values set at RadioButton level

Fix

1. Use TemplateBinding

<Border BorderBrush="{TemplateBinding BorderBrush}"

    BorderThickness="{TemplateBinding BorderThickness}"

2. Move the ‘IsSelected’ trigger to an a value provider of higher precedence.  From Adam Nathan’s WPF Unleashed, the precedence is as below:

    • Local value
    • Style triggers
    • Template triggers
    • Style setters
    • Theme style triggers
    • Theme style setters
    • Property value inheritance
    • Default value

 

So we have 2 options:

  1. Use code-behind and directly set values (Local value)
  2. Use Style triggers

        protected override void OnClick()

        {

            base.OnClick();

            var context = this.DataContext as BoardSquare;

            this.Background = new SolidColorBrush(Colors.DarkBlue);

            this.Foreground = new SolidColorBrush(Colors.LightGreen);

            this.BorderBrush = new SolidColorBrush(Colors.LightGreen);

            this.BorderThickness = new Thickness(2);

            //context.Select(true, MoveOptions.AllApplicable);

        }

Style Triggers

 

 

    1 <RadioButton x:Class="Shatranj.BoardSquareView"

    2             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4             xmlns:Shatranj="clr-namespace:Shatranj">

    5 

    6   <RadioButton.Resources>

    7     <Shatranj:ForceToGroupNameConverter x:Key="ForceToGroupNameConverter" />

    8     <Style TargetType="{x:Type Shatranj:BoardSquareView}">

    9       <Setter Property="IsChecked" Value="false" />

   10       <Setter Property="Background"

   11               Value="{Binding Converter={StaticResource LocationToColorConverter},

   12                             Mode=OneWay}" />

   13       <Setter Property="BorderThickness" Value="0" />

   14       <Setter Property="GroupName"

   15               Value="{Binding Converter={StaticResource ForceToGroupNameConverter}}" />

   16       <Setter Property="Template">

   17         <Setter.Value>

   18           <ControlTemplate TargetType="{x:Type Shatranj:BoardSquareView}">

   19             <ControlTemplate.Resources>

   20               <Style TargetType="RadioButton">

   21                 <Setter Property="Background"

   22                         Value="{Binding Converter={StaticResource LocationToColorConverter},

   23                                       Mode=OneWay}" />

   24               </Style>

   25               <Shatranj:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />

   26             </ControlTemplate.Resources>

   27 

   28             <Grid Background="{TemplateBinding Background}">

   29               <Grid.RowDefinitions>

   30                 <RowDefinition Height="0.200*" />

   31                 <RowDefinition Height="0.800*" />

   32               </Grid.RowDefinitions>

   33               <Grid.ColumnDefinitions>

   34                 <ColumnDefinition />

   35               </Grid.ColumnDefinitions>

   36               <Border BorderBrush="{TemplateBinding BorderBrush}"

   37                       BorderThickness="{TemplateBinding BorderThickness}"

   38                       Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" />

   39               <Viewbox Grid.Row="0" Grid.Column="0"

   40                       HorizontalAlignment="Right" VerticalAlignment="Stretch">

   41                 <TextBlock FontSize="12"

   42                           FontFamily="Consolas"

   43                           Text="{Binding Path=AlgebraicIdentity, Mode=OneWay}" />

   44               </Viewbox>

   45               <Viewbox Grid.RowSpan="2" Grid.Row="0"

   46                       HorizontalAlignment="Center" VerticalAlignment="Center">

   47                 <TextBlock FontFamily="Chess Cases"

   48                           Margin="3,3,3,3"

   49                           Text="{Binding Path=CurrentPiece.AltChar,

   50                                         Mode=OneWay}" />

   51               </Viewbox>

   52 

   53               <Ellipse

   54                 MaxHeight="30" MaxWidth="30"

   55                 MinHeight="10" MinWidth="10"

   56                 Grid.Row="0" Grid.RowSpan="2"

   57                 Visibility="{Binding IsHit,

   58                                     Converter={StaticResource BoolToVisibilityConverter}}">

   59                 <Ellipse.Fill>

   60                   <SolidColorBrush Color="Black" Opacity="0.5" />

   61                 </Ellipse.Fill>

   62               </Ellipse>

   63 

   64             </Grid>

   65 

   66             <ControlTemplate.Triggers>

   67               <Trigger Property="IsMouseOver" Value="true">

   68                 <Setter Property="Foreground" Value="Red" />

   69                 <Setter Property="Background" Value="Black" />

   70                 <Setter Property="BorderBrush" Value="Red" />

   71                 <Setter Property="BorderThickness" Value="2" />

   72               </Trigger>

   73             </ControlTemplate.Triggers>

   74           </ControlTemplate>

   75         </Setter.Value>

   76       </Setter>

   77       <Style.Triggers>

   78         <Trigger Property="IsChecked" Value="true">

   79           <Setter Property="Background" Value="DarkBlue" />

   80           <Setter Property="Foreground" Value="LightGreen" />

   81           <Setter Property="BorderBrush" Value="LightGreen" />

   82           <Setter Property="BorderThickness" Value="2" />

   83         </Trigger>

   84       </Style.Triggers>

   85     </Style>

   86   </RadioButton.Resources>

   87 </RadioButton>

ChessBoardWith3GroupsOfRadioButtons

Yes, now when you hover d2, d3 or d7, the selected highlight remains (remember, we have three distinct RadioButton groups there)!  Of course, we need to make sure only one group is selectable at any one point of time.  Topic for further posts!

Shatranj – Convert to Control (3 of 3): TemplateBinding, Styles & Triggers

 

So we’ve a background color issue!  Let’s call Snoop to rescue:

SnoopShowsBackgroundSetByStyleCorrectly

Snoop shows that the background of square is – correctly – set to LightBlue by Style!  Still it doesn’t show up on the UI.  That’s simple, because any property not set by Template is replaced by the defaults!

Since I couldn’t figure out how to set the Background on the RadioButton from within ControlTemplate, the best thing I could do was:

<Grid

      Background="{Binding Path=Background,

      RelativeSource={RelativeSource Mode=FindAncestor,

      AncestorType={x:Type Shatranj:BoardSquareView}}}"

>

It worked!  Then:

<Grid Background="{TemplateBinding Background}">

One problem down!  Let’s refactor the code to distinguish between two players.  Their squares must belong to different groups so they can be selected independently.

Let’s refactor the code to distinguish between two players.  Their squares must belong to different groups so they can be selected independently:

    1 <RadioButton x:Class="Shatranj.BoardSquareView" 

    2             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4             xmlns:Shatranj="clr-namespace:Shatranj" >

    5 

    6     <RadioButton.Resources>

    7         <Shatranj:ForceToGroupNameConverter x:Key="ForceToGroupNameConverter"/>

 

 

Remember, each BoardSquareView has its DataContext set to a “BoardSquare” object.  BoardSquare object among other things has a CurrentPiece property (of type ChessPiece), which in turn has a property called ‘Force’:

 

 

ChessPieceSquare

 

 

 

ForceToGroupNameCoverter.cs has three pertinent lines:

 

   16             var square = value as BoardSquare;

   17             if(square == null || square.CurrentPiece == null) return "None";

   18             return Enum.GetName(typeof (Army), square.CurrentPiece.Force);

This divides the whole board into three groups.  One with White pieces, one with Black pieces and one another with no pieces (Grouped into ‘None’).

  1. When it’s a player’s turn (playing White/Black), the player should only be able to select one of his/her piece for making the move
  2. This also applies to the opponent
  3. When any of the empty squares are captured by either players, that square is owned by the piece occupying it.

Here’s the final version:

    1 <RadioButton x:Class="Shatranj.BoardSquareView" 

    2             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4             xmlns:Shatranj="clr-namespace:Shatranj" >

    5     <RadioButton.Resources>

    6         <Shatranj:ForceToGroupNameConverter x:Key="ForceToGroupNameConverter"/>

    7         <Style TargetType="{x:Type Shatranj:BoardSquareView}">

    8             <Setter Property="IsChecked" Value="false"/>

    9             <Setter Property="Background" Value="{Binding Converter={StaticResource LocationToColorConverter}, Mode=OneWay}"/>

   10             <Setter Property="GroupName" Value="{Binding Converter={StaticResource ForceToGroupNameConverter}}"/>

   11             <Setter Property="Template">

   12                 <Setter.Value>

   13                     <ControlTemplate TargetType="{x:Type Shatranj:BoardSquareView}">

   14                         <ControlTemplate.Resources>

   15                             <Style TargetType="RadioButton">

   16                                 <Setter Property="Background"

   17                                        Value="{Binding Converter={StaticResource LocationToColorConverter}, Mode=OneWay}"/>

   18                             </Style>

   19                             <Shatranj:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />

   20                         </ControlTemplate.Resources>

   21 

   22                         <Grid Background="{TemplateBinding Background}">

   23                                 <Grid.RowDefinitions>

   24                                     <RowDefinition Height="0.200*" />

   25                                     <RowDefinition Height="0.800*" />

   26                                 </Grid.RowDefinitions>

   27                                 <Grid.ColumnDefinitions>

   28                                     <ColumnDefinition />

   29                                 </Grid.ColumnDefinitions>

   30                                 <Border x:Name="Border" Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" />

   31                                 <Viewbox Grid.Row="0" Grid.Column="0"

   32                                         HorizontalAlignment="Right" VerticalAlignment="Stretch">

   33                                     <TextBlock FontSize="12"

   34                                               FontFamily="Consolas"

   35                                               Text="{Binding Path=AlgebraicIdentity, Mode=OneWay}" />

   36                                 </Viewbox>

   37                                 <Viewbox Grid.RowSpan="2" Grid.Row="0"

   38                                         HorizontalAlignment="Center" VerticalAlignment="Center">

   39                                     <TextBlock FontFamily="Chess Cases"

   40                                               Margin="3,3,3,3"

   41                                               Text="{Binding Path=CurrentPiece.AltChar, Mode=OneWay}" />

   42                                 </Viewbox>

   43 

   44                                 <Ellipse MaxHeight="30" MaxWidth="30" MinHeight="10" MinWidth="10"

   45                                         Grid.Row="0" Grid.RowSpan="2"

   46                                         Visibility="{Binding IsHit, Converter={StaticResource BoolToVisibilityConverter}}" >

   47                                     <Ellipse.Fill>

   48                                         <SolidColorBrush Color="Black" Opacity="0.5"/>

   49                                     </Ellipse.Fill>

   50                                 </Ellipse>

   51 

   52                             </Grid>

   53 

   54                         <ControlTemplate.Triggers>

   55                                 <Trigger Property="IsChecked" Value="true">

   56                                     <Setter Property="Background" Value="DarkBlue"/>

   57                                     <Setter Property="Foreground" Value="LightGreen" />

   58                                     <Setter TargetName="Border" Property="BorderBrush" Value="LightGreen"/>

   59                                     <Setter TargetName="Border" Property="BorderThickness" Value="2"/>

   60                                 </Trigger>

   61                                 <Trigger Property="IsMouseOver" Value="true">

   62                                     <Setter Property="Foreground" Value="Red" />

   63                                     <Setter Property="Background" Value="Black"/>

   64                                     <Setter TargetName="Border" Property="BorderBrush" Value="Red"/>

   65                                     <Setter TargetName="Border" Property="BorderThickness" Value="2"/>

   66                                 </Trigger>

   67                         </ControlTemplate.Triggers>

   68                     </ControlTemplate>

   69                 </Setter.Value>

   70             </Setter>

   71         </Style>

   72     </RadioButton.Resources>

   73 </RadioButton>

ShatranjChessBoardWithControlTemplate

 

Yes, WPF is nothing short of revolutionary! 

Shatranj – Convert to Control (2 of 3): ControlTemplate to replace default UI

Our first task is to prevent the RadioButton’s default UI from showing up.  Let’s move the ContentPresenter inside Style into a ControlTemplate:

    1 <RadioButton x:Class="Shatranj.BoardSquareView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Shatranj="clr-namespace:Shatranj" Background="{Binding Converter={StaticResource LocationToColorConverter}}">

    2 

    3     <RadioButton.Resources>

    4         <Style TargetType="{x:Type Shatranj:BoardSquareView}">

    5             <Setter Property="Template">

    6                 <Setter.Value>

    7                     <ControlTemplate>

    8                         <ContentPresenter>

    9                             <ContentPresenter.Content>

   10                                 <Grid>

   11                                     <Grid.RowDefinitions>

   12                                         <RowDefinition Height="0.200*" />

   13                                         <RowDefinition Height="0.800*" />

   14                                     </Grid.RowDefinitions>

   15                                     <Grid.ColumnDefinitions>

   16                                         <ColumnDefinition />

   17                                     </Grid.ColumnDefinitions>

   18 

   19                                     <Viewbox Grid.Row="0" Grid.Column="0"

   20                                             HorizontalAlignment="Right" VerticalAlignment="Stretch">

   21                                         <TextBlock FontSize="12"

   22                                                   FontFamily="Consolas"

   23                                                   Text="{Binding Path=AlgebraicIdentity, Mode=OneWay}" />

   24                                     </Viewbox>

   25                                     <Viewbox Grid.RowSpan="2" Grid.Row="0"

   26                                             HorizontalAlignment="Center" VerticalAlignment="Center">

   27                                         <TextBlock FontFamily="Chess Cases"

   28                                                   Margin="3,3,3,3"

   29                                                   Text="{Binding Path=CurrentPiece.AltChar, Mode=OneWay}" />

   30                                     </Viewbox>

   31 

   32                                     <Ellipse MaxHeight="30" MaxWidth="30"

   33                                             MinHeight="10"

   34                                             MinWidth="10"

   35                                             Fill="Aqua"

   36                                             Grid.Row="0" Grid.RowSpan="2"

   37                                             Visibility="{Binding IsHit,

   38                                                Converter={StaticResource BoolToVisibilityConverter}}" />

   39                                 </Grid>

   40                             </ContentPresenter.Content>

   41                         </ContentPresenter>

   42                     </ControlTemplate>

   43 

   44                 </Setter.Value>

   45             </Setter>

   46             <Style.Triggers>

   47                 <Trigger Property="IsMouseOver" Value="true">

   48                     <Setter Property="Foreground" Value="Red" />

   49                     <Setter Property="Background" Value="Black" />

   50                     <Setter Property="BorderBrush" Value="Red" />

   51                     <Setter Property="BorderThickness" Value="2" />

   52                 </Trigger>

   53             </Style.Triggers>

   54         </Style>

   55         <Shatranj:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />

   56     </RadioButton.Resources>

   57 </RadioButton>

Oops…  The BoolToVisibilityConverter should also be moved into <ControlTemplate.Resources> so it becomes accessible from inside the ControlTemplate.

    5             <Setter Property="Template">

    6                 <Setter.Value>

    7                     <ControlTemplate>

    8                         <ControlTemplate.Resources>

    9                             <Shatranj:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />

   10                         </ControlTemplate.Resources>

   11                         <ContentPresenter>

   12                             <ContentPresenter.Content>

ShatranjWithControlTemplatedBoardSquareView

No color squares?  Strange.  Let’s move the background to Style:

    1 <RadioButton x:Class="Shatranj.BoardSquareView"

    2             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

    3             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

    4             xmlns:Shatranj="clr-namespace:Shatranj">

    5 

    6     <RadioButton.Resources>

    7         <Style TargetType="{x:Type Shatranj:BoardSquareView}">

    8             <Setter Property="Background" Value="{Binding Converter={StaticResource LocationToColorConverter}}"/>

Same result!  Well, I would have been surprised if it’d been otherwise.  Local values always override values set by Styles!  So what could be the problem?