WPF Using a trigger when Validation has error

There is much documentation out there regarding how to use ValidationRules together with an ErrorTemplate in order to highlight invalid user data. However, I found it a little trickier to find out how to trigger some other kind of behaviour.

Here is my scenario:
I have two text boxes, the second of which is collapsed unless a validation error occurs in the first text box. This seems like pretty much a run of the mill scenario but it was a little trickier to get working than simply putting a red border round the first text box.

I put all the validation and triggers in a Style.  Here is the XAML describing the two text boxes:

<StackPanel Name="ExpanderStackPnl" MinWidth="440">
  <Expander x:Name="TitleExpander"
                      Header="{Binding ElementName=InspectorRoot, Path=LongTitle}"
              Collapsed="TitleExpander_Collapsed" Expanded="TitleExpander_Expanded">
    <StackPanel Margin="5,5,5,5" x:Name="StackInsideExpander">
		   <spCntrls:SportTitle x:Name="spTitle"
                             SpLabel="Desired title (fits on most platforms):"
                             Style="{StaticResource LongTitleStyle}"
                             TitleChanged="LongTitle_Changed"
                             LostFocus="LongTitle_LostFocus"
                             ErrorStatusChanged="LongTitle_ErrorStatusChanged"
                              GotFocus="LongTitle_GotFocus">

          <spCntrls:SportTitle.UserText>
            <Binding Path="LongTitle"
                              ElementName="InspectorRoot"
                              UpdateSourceTrigger="PropertyChanged"
                              NotifyOnValidationError="True"
                             Mode="TwoWay">
              <Binding.ValidationRules>
                <rules:FitsAllValidationRule/>
              </Binding.ValidationRules>
            </Binding>
          </spCntrls:SportTitle.UserText>
        </spCntrls:SportTitle>

        <spCntrls:SportTitle x:Name="spShortTitle"
                             SpLabel="Abbreviated Title (must fit ALL platforms)"
                             Style="{StaticResource ShortTitleStyle}"
                             TitleChanged="ShortTitle_Changed"
                             GotFocus="ShortTitle_GotFocus"
                             LostFocus="ShortTitle_LostFocus">
          <spCntrls:SportTitle.UserText>
            <Binding Path="ShortTitle"
                              ElementName="InspectorRoot"
                              UpdateSourceTrigger="PropertyChanged"
                              NotifyOnValidationError="True"
                             Mode="TwoWay">
              <Binding.ValidationRules>
                <rules:FitsAllValidationRule/>
              </Binding.ValidationRules>
            </Binding>
          </spCntrls:SportTitle.UserText>
        </spCntrls:SportTitle>
     </StackPanel>
   </Expander>
</StackPanel>

As you can see, I have attached a style to each of them and that is where the behaviour is described.  I used a GlobalDictionary to store the styles creating a namespace to refer to the class (named Inspector) where my text boxes reside.

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:edtr="clr-namespace:BBCNewsI.Cps2Dtext.SportEditor"
    xmlns:spCntrls="clr-namespace:BBCNewsI.Cps2Dtext.SportEditor.Controls">

The XAML which is of interest is in the style named ShortTitleStyle. Using a DataTrigger you can trigger on changes to another control:

<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" Value="true">
	<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" Value="false">
	<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>

The thing that I found interesting is that a sibling control is simply referred to by its name, there is no need for any fancy namespace references or Ancestor like binding.

You might have noticed that these controls are inside an expander. This creates in interesting problem when there is an error and the expander is collapsed, namely that you can still see the border of the error template! To get round this, I used another DataTrigger with a dependency property which indicates the expanded state of the expander to set the error template to null:

<DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="False">
  <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
 </DataTrigger>

<DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="True">
   <Setter Property="spCntrls:SportTitle.ToolTip"
                Value="Enter title for scene, both text fields usually have the same text " +
                      "unless the desired title is too long to fit on all platforms"/>
</DataTrigger>

Here are both styles that have been applied to the text boxes in their entirety:

<Style x:Key="LongTitleStyle" TargetType="{x:Type spCntrls:SportTitle}">
    <Setter Property="Validation.ErrorTemplate">
      <Setter.Value>
        <ControlTemplate x:Name="InspectorErrorTemplate">
          <StackPanel Orientation="Vertical">
            <Border BorderBrush="Goldenrod" BorderThickness="1">
              <AdornedElementPlaceholder Name="adornerPlaceholder"/>
            </Border>
          </StackPanel>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
    <Style.Triggers>
      <DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="False">
        <Setter Property="Validation.ErrorTemplate" Value="{x:Null}"/>
      </DataTrigger>

      <DataTrigger Binding="{Binding Path=TitleExpanded,
        RelativeSource={RelativeSource AncestorType={x:Type spCntrls:Inspector}}}"
                   Value="True">
        <Setter Property="spCntrls:SportTitle.ToolTip"
                Value="Enter title for scene, both text fields usually have the same " +
			"text unless the desired title is too long to fit on all platforms"/>
     </DataTrigger>

      <Trigger Property="Validation.HasError" Value="true">
        <Setter Property="spCntrls:SportTitle.UserTextForeground" Value="Maroon"/>
        <Setter Property="spCntrls:SportTitle.HasError" Value="true"/>
        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self},
          Path=(Validation.Errors)[0].ErrorContent}"/>
      </Trigger>
      <Trigger Property="Validation.HasError" Value="false">
        <Setter Property="spCntrls:SportTitle.HasError" Value="false"/>
      </Trigger>
    </Style.Triggers>
  </Style>

  <!-- Style for short version of the title -->
  <Style x:Key="ShortTitleStyle" BasedOn="{StaticResource LongTitleStyle}"
							TargetType="{x:Type spCntrls:SportTitle}">
    <Setter Property="Validation.ErrorTemplate">
      <Setter.Value>
        <ControlTemplate x:Name="InspectorErrorTemplate">
          <StackPanel Orientation="Vertical">
            <Border BorderBrush="Red" BorderThickness="1">
              <AdornedElementPlaceholder Name="adornerPlaceholder"/>
            </Border>
          </StackPanel>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
    <Style.Triggers>
      <Trigger Property="Validation.HasError" Value="true">
        <Setter Property="spCntrls:SportTitle.UserTextForeground" Value="Red"/>
        <Setter Property="ToolTip" Value="Alternative title is not short enough"/>
      </Trigger>

      <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" 
               Value="true">
        <Setter Property="Visibility" Value="Visible"/>
      </DataTrigger>
      <DataTrigger Binding="{Binding Path=(Validation.HasError), ElementName=spTitle}" 
               Value="false">
        <Setter Property="Visibility" Value="Collapsed"/>
      </DataTrigger>
    </Style.Triggers>
  </Style>
Advertisements

3 thoughts on “WPF Using a trigger when Validation has error

  1. cplotts

    I love it when I search the internet and find exactly what I need! Nice post. I used your DataTrigger to the Validation.HasError attached property … to give more space to my validation error template. +1

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s