WPF BindingGroup and Validation Feedback


Recap


In a previous set of posts I covered the introduction to the BindingGroup feature and I expanded in a following post with an example of using BindingGroup and IEditableCollectionView together.  Last thing on the BindingGroup list to discuss is the additional validation feedback options with item-level validation.


Motivation


For item-level validation, the target element is often a container of some kind, such as a ListViewItem, Panel, or form.  Some common scenarios for showing item-level validation feedback are possibly a ListViewItem wanting to show an exclamation mark in a particular column or a panel wanting to display error information in a dedicated child element. 


Validation Feedback


Two new APIs have been added to the Validation class to enable feedback for BindingGroups.  The APIs include Validation.ValidationAdornerSite and Validation.ValidationAdornerSiteFor.  These attached properties work together to point to the element that will show the validation error.  The remarks in MSDN describe both attached properties quite well actually so I’m not going to describe them again here.  What I will do is go through some different usage scenarios. 


In the BindingGroups and IEditableCollectionView post, I used standard validation that existed before SP1.  I placed it on the entire row item.  That is one option that you have to display validation feedback.  I list it here for reference.



<Style TargetType=”{x:Type ListViewItem}”>


  <Setter Property=”Validation.ErrorTemplate”>


    <Setter.Value>


      <ControlTemplate>


        <DockPanel LastChildFill=”True”>


          <TextBlock DockPanel.Dock=”Right” Foreground=”Red”


                     FontSize=”14″ FontWeight=”Bold”>*</TextBlock>


          <Border BorderBrush=”Red” BorderThickness=”2″>


           <AdornedElementPlaceholder Name=”adornerPlaceholder” />


          </Border>


        </DockPanel>


      </ControlTemplate>


    </Setter.Value>


  </Setter>


  <Style.Triggers>


    <Trigger Property=”Validation.HasError” Value=”true”>


      <Setter Property=”ToolTip”


              Value=”{Binding RelativeSource={RelativeSource Self},


                              Path=(Validation.Errors)[0].ErrorContent,


                              Converter={StaticResource ItemErrorConverter}}”/>


    </Trigger>


  </Style.Triggers>


</Style>


 


Another way that you may want to display validation errors is through a dedicated area in your UI.  Let’s say that you have a TextBlock or ListBox specifically for validation errors.  You can design it in a couple ways using the new Validation APIs.  One way, you can set Validation.ValidationAdornerSiteFor on an error ListBox to point to the element that is being validated (which in this case is the ListViewItem).  Here is an example:       



<!–Validation Feedback–>


<StackPanel Grid.Row=”3″>


  <!–set the DataContext to the itemsList ListViewItem–>


  <StackPanel.DataContext>


    <MultiBinding Converter=”{StaticResource ValidationAdornerConverter}”>


      <Binding ElementName=”itemsList” Path=”.”/>


      <Binding ElementName=”itemsList” Path=”ItemsSource.CurrentItem” />


    </MultiBinding>


  </StackPanel.DataContext>


 


  <TextBlock Text=”Errors: ” />


  <ListBox MinHeight=”30″


            Validation.ValidationAdornerSiteFor=”{Binding}”


            ItemsSource=”{Binding Path=(Validation.Errors)}”>


    <ListBox.ItemTemplate>


      <DataTemplate>


        <TextBlock Text=”{Binding Path=ErrorContent}” />


      </DataTemplate>


    </ListBox.ItemTemplate>


  </ListBox>


</StackPanel>


 


I had to use a MultiBinding to get the current ListViewItem visual to set as the DataContext for the error ListBox.  As you can see, there is a bit of work involved taking this route.  The next approach I am going to discuss uses less code and may be a better approach for lists.  But there are other scenarios where this particular approach is more convenient.  The reason it is a bit involved in the first place is because you are dealing with a list of items and you need to find the correct item.  If you were dealing with a form instead of a ListView all you have to do is set the ValidationAdornerSiteFor on the form element instead of having to find it in a converter.  That is just something to keep in mind.


The second approach that you can do is set the Validation.ValidationAdornerSite on the ListViewItem to point to the error ListBox.  Here is an initial implementation:                   



<!—WARNING: Setting ValidationAdornerSite this way may have an undesired side effect–>


<Style TargetType=”{x:Type ListViewItem}”>


  <Setter Property=”Validation.ValidationAdornerSite”


           Value=”{Binding ElementName=lb_ErrorList}” />


</Style>


 


 


<!–Validation Feedback–>


<StackPanel Grid.Row=”3″>


  <TextBlock Text=”Errors: ” />


  <ListBox Name=”lb_ErrorList” MinHeight=”30″


           ItemsSource=”{Binding RelativeSource={RelativeSource Self},


           Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)}”>


    <ListBox.ItemTemplate>


      <DataTemplate>


        <TextBlock Text=”{Binding Path=ErrorContent}” />


      </DataTemplate>


    </ListBox.ItemTemplate>


  </ListBox>


</StackPanel>


 


By setting Validation.ValidationAdornerSite on the ListViewItem, lb_ErrorList now has access to the ListViewItem through Validation.ValidationAdornerSiteFor on itself.  There is one problem with the above code however.  Validation.ValidationAdornerSite is a static property and therefore will only exist as a single entity.  When each ListViewItem attaches to Validation.ValidationAdornerSite, the value of Validation.ValidationAdornerSite will end up being the last ListViewItem that attached to it.  What this means is that anytime there is a validation error, lb_ErrorList will check the last ListViewItem for Validation.Errors (instead of the current ListViewItem that is being edited).  To correct this, you can update Validation.ValidationAdornerSite dynamically.  A possible implementation for this is to remove the Style set on the ListViewItem and attach to the Validation.Error event.  In this event, you can set Validation.ValidationAdornerSite like so:                        



<ListView Name=”itemsList” Validation.Error=”itemsList_Error” … />            


 


 


private void itemsList_Error(object sender, ValidationErrorEventArgs e)


{


  Validation.SetValidationAdornerSite((DependencyObject)e.OriginalSource, this.lb_ErrorList);


}


 


Now when a validation error occurs, lb_ErrorList will check the correct ListViewItem for validation errors.  I have the full code for this solution attached.


One last scenario that I want to cover is showing feedback on a particular property within an item.  Let’s say as the item-level validation occurs a failure occurs between two properties and you want to show an error on the specific property.  The approach for this one is very similar to the second approach I discussed.  In my example I use the column header to show any validation error feedback and it is in the Validation.Error event where I set the ValidationAdornerSite to the correct column header.  Here is a snippet of the template for the GridViewColumnHeader and the full code you can see from the solution attached.                       



<Style TargetType=”{x:Type GridViewColumnHeader}”>


  <Setter Property=”Template”>


    <Setter.Value>


      <ControlTemplate TargetType=”{x:Type GridViewColumnHeader}” >


        <StackPanel Orientation=”Horizontal”>


          <ContentPresenter />


          <TextBlock Text=”*” Foreground=”Red”>


           <TextBlock.Visibility>


                 <MultiBinding Converter=”{StaticResource ErrorVisibilityConverter}”>


                       <Binding RelativeSource=”{RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}” Path=”(Validation.ValidationAdornerSiteFor).(Validation.HasError)”/>


                       <Binding RelativeSource=”{RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}” Path=”(Validation.HasError)” />


                 </MultiBinding>


           </TextBlock.Visibility>


          </TextBlock>


        </StackPanel>


      </ControlTemplate>


    </Setter.Value>


  </Setter>


  <Setter Property=”ToolTip”


          Value=”{Binding RelativeSource={RelativeSource Self}, Path=(Validation.ValidationAdornerSiteFor).(Validation.Errors)[0].ErrorContent}” />


</Style>


 


Download the complete solution here.

BindingGroupSample_IECV_Validation.zip

Comments (11)

  1. wonderliza says:

    hi,

    I tryed all your samples and I always get the same error… ‘BoatRentalCustomers’ (could not create an instance) in the file ‘BindingGroupAndIECVSample;component/iecvandbgwithvalidationsite_sample.xaml’ line 9 position 10.

    I performed the steps described in the DB Install Instructions.txt.

    Am I the only one ?

  2. wonderliza,

    Could you give me the exact repro steps that is giving you this error.  Since it is with BoatRentalCustomers it shouldn’t be a db problem.

  3. wonderliza says:

    thanks,

    I launched the sln, I executed the project "as is", and i clicked on the button named "IECVandBGwithValidationSiteSample" (or "IECVandBGwithValidationSite2Sample" or "IECVandBGwithValidationSiteForSample", the result is the same)

    and I get this (sorry, it’s in french)

    Impossible de créer une instance de ‘BoatRentalCustomers’ définie dans l’assembly ‘BindingGroupAndIECVSample, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null’. Une exception a été levée par la cible d’un appel.  Erreur dans le fichier de balisage ‘BindingGroupAndIECVSample;component/iecvandbgwithvalidationsite_sample.xaml’ ligne 9 position 10.

    showing me the following line :

           <local:BoatRentalCustomers x:Key="BoatRentalCustomers"/>

    into IECVandBGwithValidationSite_Sample.xaml

  4. Elise's blog says:

    Stéphane Goudeau m’a remonté un lien ce matin : Un nouveau billet vient de sortir, qui présente une nouvelle

  5. wonderliza says:

    hi, so, am I the only one that can’t run this sample ?

  6. I’ve tried on both Vista and XP and didn’t run into your issue.  Do you have SP1 installed?

  7. wonderliza says:

    yes, i do have SP1 installed in fact. Weird…

  8. DataSource.cs contains prevalent error: DateTime.Parse method calls don’t include CultureInfo. As result, sample crushes on some computers. To fix this bug your must replace

    public BoatRentalCustomers()

    {

       Add(new BoatRentalCustomer { FirstName = "David", LastName = "Wright", DateOfBirth = DateTime.Today, StartDate = DateTime.Today, EndDate = DateTime.Today });

       Add(new BoatRentalCustomer { FirstName = "Ryan", LastName = "Zimmerman", DateOfBirth = DateTime.Today, StartDate = DateTime.Today, EndDate = DateTime.Today });

       Add(new BoatRentalCustomer { FirstName = "Mark", LastName = "Reynolds", DateOfBirth = DateTime.Today, StartDate = DateTime.Today, EndDate = DateTime.Today });

       Add(new BoatRentalCustomer { FirstName = "BJ", LastName = "Upton", DateOfBirth = DateTime.Today, StartDate = DateTime.Today, EndDate = DateTime.Today });

    }

    with

       Add(new BoatRentalCustomer { FirstName = "David", LastName = "Wright", DateOfBirth = DateTime.Parse("12/20/1982", CultureInfo.InvariantCulture), StartDate = DateTime.Parse("08/01/2008", CultureInfo.InvariantCulture), EndDate = DateTime.Parse("08/02/2008", CultureInfo.InvariantCulture) });

       Add(new BoatRentalCustomer { FirstName = "Ryan", LastName = "Zimmerman", DateOfBirth = DateTime.Parse("09/28/1984", CultureInfo.InvariantCulture), StartDate = DateTime.Parse("07/25/2008", CultureInfo.InvariantCulture), EndDate = DateTime.Parse("07/27/2008", CultureInfo.InvariantCulture) });

       Add(new BoatRentalCustomer { FirstName = "Mark", LastName = "Reynolds", DateOfBirth = DateTime.Parse("08/03/1983", CultureInfo.InvariantCulture), StartDate = DateTime.Parse("08/01/2008", CultureInfo.InvariantCulture), EndDate = DateTime.Parse("08/01/2008", CultureInfo.InvariantCulture) });

       Add(new BoatRentalCustomer { FirstName = "BJ", LastName = "Upton", DateOfBirth = DateTime.Parse("08/21/1984", CultureInfo.InvariantCulture), StartDate = DateTime.Parse("07/25/2008", CultureInfo.InvariantCulture), EndDate = DateTime.Parse("08/02/2008", CultureInfo.InvariantCulture) });

  9. Sergei Gaidukov,

    Thanks a lot for your help.  I’ve updated the solution accordingly.

  10. Niclas says:

    Hi there, i wonder if you could help me out with a question of mine. I have a usercontrol that has a border and a Image as its child. If the image source hasnt been set by the app user i set an error via the IDataErrorInfo interface in my viewmodel. My problem is now how i can update the sourrounding borders style when this happens. I have tried to play around with the Validation.ValidationAdornerSiteFor property but with no luck. Below you can see my "attempt". Could you point me in direction how to accomplish this, to update another control that didnt fire the errors style?

    <UserControl x:Class="Admin.Thumbnail" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot;

           xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"&gt;

       <UserControl.Resources>

           <!– Style setting the border as validation adorner site. –>

           <Style x:Key="ThumbnailStyle" TargetType="{x:Type Image}">

               <Setter Property="Validation.ValidationAdornerSite" Value="{Binding ElementName=theBorder}" />

           </Style>

           <!–

               This style checks if any errors have been set and should change the border color.

           –>

           <Style x:Key="ErrorAd" TargetType="{x:Type Border}">

               <Style.Triggers>

                   <Trigger Property="Validation.HasError" Value="false">

                       <Setter Property="ToolTip"

                               Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />

                   </Trigger>

               </Style.Triggers>

           </Style>

       </UserControl.Resources>

       <DockPanel>

           <Border Name="theBorder" BorderThickness="1" Background="Red" BorderBrush="Black"

                   Validation.ValidationAdornerSiteFor="{Binding ElementName=imageMain}"

                   Style="{DynamicResource ErrorAd}">

               <Image x:Name="imageMain" HorizontalAlignment="Center" VerticalAlignment="Center"

                       Source="{Binding Path=Source, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True,NotifyOnValidationError=True}" />

           </Border>

       </DockPanel>

    </UserControl>