Row-level validation

Sep 2, 2008 at 9:15 PM
Features list indicates support for row-level validation. Can anyone point me in the right direction (an example would be best).
Coordinator
Sep 2, 2008 at 10:18 PM
Hi Bryan,

Sorry about that - it's actually a mistake in the documentation.  Row-level validation did not make it into the CTP, but it will be available in v1.  Row-level validation will be supported through the use of BindingGroups (a new feature in 3.5 SP1) and as a convenience, the DataGrid will have a property to hold a collection of ValidationRules which can be applied to all of the rows in the grid.

Thanks!
Samantha
Sep 2, 2008 at 11:26 PM

Thanks Samantha,

I'm also having trouble with standard validation (applying a ValidationRule to the binding of a DataGridTextColumn). Namely, using Validation.HasErrors as the property for a trigger which alters basic styling. Is there anything I should be aware of here?

I think I remember someone saying triggers weren't working right.

Thanks for your help.

Coordinator
Sep 4, 2008 at 7:14 PM

Hi Bryan,

Something like this should work:

<DataGrid>

<DataGrid.Columns>

<DataGridTemplateColumn Header=”…”>

<DataGridTemplateColumn.CellEditingTemplate>

   <TextBox>

   <TextBox.Text>

     <Binding Path=”…”>

     <Binding.ValidationRules>

       <SomeRule … />

     </Binding.ValidationRules>

     </Binding>

   </TextBox.Text>

   <Validation.ErrorTemplate>

     <ControlTemplate>

       <DockPanel>

         <TextBlock Foreground=”Red” FontSize=”20” />

         <AdornedElementPlaceHolder />

       </DockPanel>

     </ControlTemplate>

   </Validation.ErrorTemplate>

   <TextBox.Style>

     <Style TargetType=”TextBox”>

     <Style.Triggers>

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

         <DoSomething … />

       </Trigger>

     </Style.Triggers>

     </Style>

   </TextBox.Style>

   </TextBox>

</DataGridTemplateColumn.CellEditingTemplate>

</DataGridTemplateColumn>

</DataGrid.Columns>

</DataGrid>

 

public class SomeRule : ValidationRule

{

    public override ValidationResult Validate(object value, CultureInfo culture)

    {

    }

}

If this is similar to what you're doing and it's not working, please send me a code snippet and I can take a look at it.

Alternatively, if you implement the IDataErrorInfo class when defining your data objects (and you define your validation rules in the data object class), then you could do something like this in the markup:

<dg:DataGridTextColumn x:Name="BoundAgeColumn" Width="Auto" Header="Age">
          <dg:DataGridTextColumn.DataFieldBinding>
            <Binding Path="Age">
              <Binding.ValidationRules>
                <DataErrorValidationRule/>
              </Binding.ValidationRules>
            </Binding>
          </dg:DataGridTextColumn.DataFieldBinding>
        </dg:DataGridTextColumn>

Let me know if this helps.

Thanks!
Samantha

Sep 5, 2008 at 2:00 PM
Samantha

I am just playing around with the ctp datagrid, got your snippet using IDataErrorInfo to run.
The first snippet I had problems with because VS said "AdornerElementPlaceHolder" is not defined in the xml

What is missing (for me) is a "better" adornment, as the red border in the datagrid is only visible after leaving the cell

Do you have an example of how to accomplish this (e.g. red background on the error column(s))?

Regards
Klaus
Coordinator
Sep 10, 2008 at 6:36 PM

Hi Klaus,

This article might help you with customizing the error template: http://www.codeproject.com/KB/WPF/wpfvalidation.aspx.  If you have questions about how to access specific parts of the DataGrid to use them in the error template, please let me know.

Thanks,
Samantha

Sep 10, 2008 at 9:32 PM
Samantha

thanks, after adding a missing  <DataTemplate> .. </DataTemplate> and repeating the whole for <...CellTemplate> it works, at least in principal.

Please take a look at my template:
                        <DataTemplate>
                           <TextBox BorderThickness="0" >
                              <TextBox.Text >
                                 <Binding Path="UserName">
                                    <Binding.ValidationRules>
                                       <DataErrorValidationRule />
                                    </Binding.ValidationRules>
                                 </Binding>
                              </TextBox.Text>
                              <Validation.ErrorTemplate>
                                 <ControlTemplate>
                                    <DockPanel>
                                       <AdornedElementPlaceholder Name="MyAdorner" />
                                    </DockPanel>
                                 </ControlTemplate>
                              </Validation.ErrorTemplate>
                              <TextBox.Style>
                                 <Style TargetType="TextBox" >
                                    <Style.Triggers>
                                       <Trigger Property="Validation.HasError"
                                                Value="true">
                                          <Setter Property="Background"
                                                  Value="Red" />
                                       </Trigger>
                                    </Style.Triggers>
                                 </Style>
                              </TextBox.Style>
                           </TextBox>
                        </DataTemplate>

There is a textbox in each datagridcell. I can set BorderThickNess to 0 to get rid of the "internal" border of the checkbox, but I use the alterating rows feature, and the textbox is always with white background.
If I set the background using Background="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=Background}", all is well for the display without errors, but when there are errors, the cell does not change background to "red" but it stays the same color. Now I switched off Altrnating rows, all is well.
My questions:
1) Is there an easy way to use alternating colors and validation visuals as background?
2) Does it have to be this complicated? Is there a shortcut I miss? Especially the double declarations for celltemplate and celleditingtemplate !

Thank you for your support, this really helps!!!
Klaus

Regards
Klaus




Sep 10, 2008 at 9:49 PM
Samantha

I forgot a third question:
Is there a way to define a data template for a DataGridTemplateColumn and in this data template to refer to a columns property (e.g. Header or, better an attached Framework.Tag-Property) ?
The only thing which is different for the columns in the grid is the DataFieldBinding
Do you have an example for this?

Regards
Klaus
Coordinator
Sep 10, 2008 at 11:08 PM
For your third quesiton, does this thread help, http://www.codeplex.com/wpf/Thread/View.aspx?ThreadId=35309?
Coordinator
Sep 10, 2008 at 11:16 PM

Hi Klaus,

I have a couple clarification questions:

1. You mentioned in your previous post that you needed to include double declarations for CellTemplate and CellEditingTemplate.  Could you explain where you are repeating these templates?  What does your XAML look like?  I like to understand better where you are repeating the templates (in the TemplateColumn?  In a style?) so that I can answer your question about how you could improve your strategy.

2. Could you expand on your scenario for referring to a column's property (such as Header or Tag) in a TemplateColumn.  Are you looking to refer to that TemplateColumn's property or to that of another column in the grid?

3. You had mentioned in an earlier post that you wanted to apply a red background to the column with an error on it.  I want to understand better what exactly you're trying to achieve since it may help answer your other questions.  If, for example, one of the cells in the grid has an error, do you just want that cell to have a red background?  Or do you want all of the cells in the column to have a red background?  Or do you want to put an adorner on the header of the column which contains that cell?

Thank you!
Samantha

Sep 11, 2008 at 9:42 AM
Samantha

thanks for your support,

The xaml I send you at the end of this text is "working" for me right now, but it has some (hehe...) redundancies as you may notice.

Regarding your questions:
Q1: As you can see by the xaml, I eliminated the mentioned CellEditingTemplate completetly, maybe it was too late yesterday evening
Q3: I would like to have error visuals in all cells in which data is invalid, an additional ROW adorner would be fine, maybe with a collection of all errors in the row? This would help, if invalid cells scroll out of view!!
Q2: I'm looking to refer to that TemplateColumn's property. But maybe I explain a little more in detail:

In my first tries yesterday, I coloured the cell background in red in case of an error, but as you read above, this was in kind of conflict with the alternating rows.
So I searched for some other visualization.
I copied the style "myErrorTemplate" from an article of a colleague of yours, Beth Massi, nice article btw
To get it "working" I had to copy most of this stuff to my datagrid column (you find it by searching <dg:DataGridTemplateColumn Header="MyUserName...)
Now I see this ellipsis when I enter data which are not valid, and it disapperas if I correct the data.
But: my xaml is a mess, I look for the right syntax to define a base style above and reference it / or use it via anonymous style in each datagrid column
As you can see, I tried that already but did not succeed, I commented this out:

      <!--<Style TargetType="{x:Type dg:DataGridTemplateColumn}"
             BasedOn="{StaticResource myErrorTemplate}" />-->

I could "connect" this to my column "MyUserName".
If I had such a default style, this should be able to get the most things done, so my wish is to eliminate all that is not necessary from my <dg:DataGrid.Columns> section.
I know that something like this must be possible:
               <dg:DataGridTextColumn DataFieldBinding="{Binding Path=ModuleName}" />
All other stuff should be expanded in the template, for example the Header, which I now read using
Header="{StaticResource P.ModuleName}", but this in fact is somthing like Header="{StaticResource P. & **DataFieldBinding**}" (as I have my ressource keys with "P." as prefix for properties),
but I can not get this to work, maybe something like this
                  <!--<dg:DataGridTextColumn DataFieldBinding="{Binding Path=ModuleName}"
                                         Header="{StaticResource {Binding Path=DataFieldBinding.Path}}"
?

So, what remains:
1) How can I define my master style that works for all datagrid cols
2) How can I refer to the templated column in my template (tag is not there, and attaching FrameworkElement.Tag does not work either!?) so that I can use my StaticResource for getting the header and maybe control other things.
3) How would a row adorner be done

Thank you so much
Regards
Klaus


Regards
Klaus

Please find the xaml here:
<Window x:Class="ModulesWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:dg="clr-namespace:Microsoft.Windows.Controls;assembly=WpfToolkit"
        xmlns:sys="clr-namespace:System;assembly=mscorlib"
        xmlns:wis="clr-namespace:Infor.Blending.WindowSettings;assembly=Infor.Blending.WindowSettings"
        xmlns:jas="clr-namespace:PropertyFilteringLib;assembly=PropertyFilteringLib"
        xmlns:local="clr-namespace:Infor.Blending.Admin.Main"
        Title="{StaticResource W.Modules}"
        Left="210"
        Top="0"
        Height="800"
        Width="1000">
   <Window.CommandBindings>
      <CommandBinding Command="ApplicationCommands.Delete"
                      Executed="OnDelete"
                      CanExecute="CanDelete" />
      <CommandBinding Command="ApplicationCommands.New"
                      Executed="OnNew"
                      CanExecute="CanNew" />
      <CommandBinding Command="ApplicationCommands.Save"
                      Executed="OnSave"
                      CanExecute="CanSave" />
   </Window.CommandBindings>
   <Window.InputBindings>
      <KeyBinding Command="Save"
                  Key="F3" />
   </Window.InputBindings>

   <Window.Resources>
      <Storyboard x:Key="FlashErrorIcon">
         <ObjectAnimationUsingKeyFrames BeginTime="00:00:00"
                                        Storyboard.TargetProperty="(UIElement.Visibility)">
            <DiscreteObjectKeyFrame KeyTime="00:00:00"
                                    Value="{x:Static Visibility.Hidden}" />
            <DiscreteObjectKeyFrame KeyTime="00:00:00.2000000"
                                    Value="{x:Static Visibility.Visible}" />
            <DiscreteObjectKeyFrame KeyTime="00:00:00.4000000"
                                    Value="{x:Static Visibility.Hidden}" />
            <DiscreteObjectKeyFrame KeyTime="00:00:00.6000000"
                                    Value="{x:Static Visibility.Visible}" />
            <DiscreteObjectKeyFrame KeyTime="00:00:00.8000000"
                                    Value="{x:Static Visibility.Hidden}" />
            <DiscreteObjectKeyFrame KeyTime="00:00:01"
                                    Value="{x:Static Visibility.Visible}" />
         </ObjectAnimationUsingKeyFrames>
      </Storyboard>
      <Style x:Key="myErrorTemplate"
             TargetType="Control">
         <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
               <ControlTemplate>
                  <DockPanel LastChildFill="True">
                     <Ellipse DockPanel.Dock="Right"
                              ToolTip="{Binding ElementName=myTextbox,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                              Width="15"
                              Height="15"
                              Margin="-25,0,0,0"
                              StrokeThickness="1"
                              Fill="Red">
                        <Ellipse.Stroke>
                           <LinearGradientBrush EndPoint="1,0.5"
                                                StartPoint="0,0.5">
                              <GradientStop Color="#FFFA0404"
                                            Offset="0" />
                              <GradientStop Color="#FFC9C7C7"
                                            Offset="1" />
                           </LinearGradientBrush>
                        </Ellipse.Stroke>
                        <Ellipse.Triggers>
                           <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                              <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                           </EventTrigger>
                        </Ellipse.Triggers>
                     </Ellipse>
                     <TextBlock DockPanel.Dock="Right"
                                ToolTip="{Binding ElementName=myControl,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                Foreground="White"
                                FontSize="11pt"
                                Margin="-15,5,0,0"
                                FontWeight="Bold">!
                            <TextBlock.Triggers>
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                                </EventTrigger>
                            </TextBlock.Triggers>
                     </TextBlock>
                     <Border BorderBrush="Red"
                             BorderThickness="1">
                        <AdornedElementPlaceholder Name="myControl" />
                     </Border>
                  </DockPanel>
               </ControlTemplate>
            </Setter.Value>
         </Setter>
         <Style.Triggers>
            <Trigger Property="Validation.HasError"
                     Value="true">
               <Setter Property="ToolTip"
                       Value="{Binding RelativeSource={x:Static RelativeSource.Self},
                        Path=(Validation.Errors)[0].ErrorContent}" />
            </Trigger>
         </Style.Triggers>
      </Style>
      <!--<Style TargetType="{x:Type dg:DataGridTemplateColumn}"
             BasedOn="{StaticResource myErrorTemplate}" />-->
     
      <Style TargetType="{x:Type dg:DataGridCell}"
             BasedOn="{StaticResource myErrorTemplate}">
          
            <EventSetter Event="PreviewMouseLeftButtonDown"
                      Handler="DataGridCell_PreviewMouseLeftButtonDown" />
      </Style>
      <DataTemplate x:Key="dtCompaniesInCombo">
         <StackPanel Orientation="Horizontal">
            <TextBlock Text="{Binding Path=DbUserId}" />
            <TextBlock Text=" - " />
            <TextBlock Text="{Binding Path=ServerNode}" />
         </StackPanel>
      </DataTemplate>

      <Style TargetType="{x:Type Border}">
         <Setter Property="BorderBrush"
                 Value="Blue" />
         <Setter Property="BorderThickness"
                 Value="1" />
         <Setter Property="BorderThickness"
                 Value="1" />
         <Setter Property="Margin"
                 Value="2" />
         <Setter Property="CornerRadius"
                 Value="3" />
         <Setter Property="DockPanel.Dock"
                 Value="Top" />
      </Style>
   </Window.Resources>

   <Grid Name="Grid1">
      <DockPanel HorizontalAlignment="Stretch">
         <Menu Background="White"
               DockPanel.Dock="Top">
            <MenuItem Header="_File">
               <MenuItem Header="_New" />
               <MenuItem Header="_Save"
                         Command="Save" />
               <MenuItem Header="_Delete" />
            </MenuItem>
         </Menu>
         <Border>

            <StackPanel Orientation="Horizontal">
               <TextBlock Text="{DynamicResource C.coCompanies}"
                          Margin="5">

               </TextBlock>
               <ComboBox x:Name="coCompanies"
                         Margin="5"
                         IsSynchronizedWithCurrentItem="False"
                         ItemsSource="{Binding}"
                         ItemTemplate="{StaticResource dtCompaniesInCombo}"
                         SelectionChanged="coCompanies_SelectionChanged">
               </ComboBox>
            </StackPanel>
         </Border>
         <Border>

            <Expander Header="Filter Options"
                      IsExpanded="False"
                      x:Name="FilterExpander"
                      Margin="2">
               <jas:PropertyFilterGroupView CollectionView="{Binding}">
                  <jas:PropertyFilterGroupView.FilterGroup>
                     <jas:PropertyFilterGroup>
                        <jas:PropertyFilter DisplayName="Module Name"
                                            PropertyName="ModuleName"
                                            PropertyType="{x:Type sys:String}" />
                        <jas:PropertyFilter DisplayName="Module SeqNo"
                                            PropertyName="ModuleSeqNo"
                                            PropertyType="{x:Type sys:Int32}" />
                        <jas:PropertyFilter DisplayName="Private"
                                            PropertyName="PrivateYN"
                                            PropertyType="{x:Type sys:Boolean}" />
                     </jas:PropertyFilterGroup>
                  </jas:PropertyFilterGroupView.FilterGroup>
               </jas:PropertyFilterGroupView>
            </Expander>
         </Border>
         <StatusBar x:Name="stBar"
                    DockPanel.Dock="Bottom"
                    DataContext="{Binding}">
            <TextBlock Text="#"
                       Margin="3" />
            <TextBlock Text="{Binding Path=Count}"
                       Margin="3" />
            <TextBlock Text="{Binding Path=Filter.ToString}"
                       Margin="3" />
         </StatusBar>
         <Border>

            <dg:DataGrid x:Name="dgModules"
                         AutoGenerateColumns="false"
                         IsSynchronizedWithCurrentItem="false"
                         Background="Transparent"
                         RowHeaderWidth="25"
                         RowBackground="White"
                         SelectionUnit="CellOrRowHeader"
                         ItemsSource="{Binding}">
               <dg:DataGrid.Columns>

                  <dg:DataGridTemplateColumn Header="{StaticResource P.RowSelectedYN}">
                     <dg:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                           <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
                        </DataTemplate>
                     </dg:DataGridTemplateColumn.CellTemplate>
                     <dg:DataGridTemplateColumn.CellEditingTemplate>
                        <DataTemplate>
                           <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />
                        </DataTemplate>
                     </dg:DataGridTemplateColumn.CellEditingTemplate>
                  </dg:DataGridTemplateColumn>
                  <!--<dg:DataGridTextColumn DataFieldBinding="{Binding Path=ModuleName}"
                                         Header="{StaticResource {Binding Path=DataFieldBinding.Path}}"
                                         />-->
                  <dg:DataGridTextColumn Header="{StaticResource P.ModuleName}"
                                         DataFieldBinding="{Binding Path=ModuleName}" />
                  <dg:DataGridTextColumn Header="{StaticResource P.ModuleSeqNo}"
                                         DataFieldBinding="{Binding Path=ModuleSeqNo, ValidatesOnDataErrors=True}" />
                  <dg:DataGridTextColumn Header="{StaticResource P.MergeModuleName}"
                                         DataFieldBinding="{Binding Path=MergeModuleName}" />
                  <dg:DataGridCheckBoxColumn Header="{StaticResource P.PrivateYN}"
                                             DataFieldBinding="{Binding Path=PrivateYN}" />
                  <dg:DataGridTextColumn Header="{StaticResource P.TimestampImport}"
                                         DataFieldBinding="{Binding Path=TimestampImport, StringFormat=dd.MM.yyyy hh:mm:ss}" />
                  <dg:DataGridTextColumn Header="{StaticResource P.TimestampMDB}"
                                         DataFieldBinding="{Binding Path=TimestampMDB, StringFormat=dd.MM.yyyy hh:mm:ss}" />
                  <dg:DataGridCheckBoxColumn Header="{StaticResource P.OutOfSyncYN}"
                                             DataFieldBinding="{Binding Path=OutOfSyncYN}" />
                  <dg:DataGridCheckBoxColumn Header="{StaticResource P.MdbExistsYN}"
                                             DataFieldBinding="{Binding Path=MdbExistsYN}" />

                  <dg:DataGridTemplateColumn Header="MyUserName">
                     <dg:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                           <TextBox BorderThickness="0" x:Name="myTextBox">
                              <TextBox.Text>
                                 <Binding Path="UserName">
                                    <Binding.ValidationRules>
                                       <DataErrorValidationRule />
                                    </Binding.ValidationRules>
                                 </Binding>
                              </TextBox.Text>
                              <Validation.ErrorTemplate>
                                 <ControlTemplate>
                                    <DockPanel LastChildFill="True">
                                       <Ellipse DockPanel.Dock="Right"
                                                ToolTip="{Binding ElementName=myTextbox,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                                Width="15"
                                                Height="15"
                                                Margin="-25,0,0,0"
                                                StrokeThickness="1"
                                                Fill="Red">
                                          <Ellipse.Stroke>
                                             <LinearGradientBrush EndPoint="1,0.5"
                                                                  StartPoint="0,0.5">
                                                <GradientStop Color="#FFFA0404"
                                                              Offset="0" />
                                                <GradientStop Color="#FFC9C7C7"
                                                              Offset="1" />
                                             </LinearGradientBrush>
                                          </Ellipse.Stroke>
                                          <Ellipse.Triggers>
                                             <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                                <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                                             </EventTrigger>
                                          </Ellipse.Triggers>
                                       </Ellipse>
                                       <TextBlock DockPanel.Dock="Right"
                                                  ToolTip="{Binding ElementName=myControl,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                                  Foreground="White"
                                                  FontSize="11pt"
                                                  Margin="-15,5,0,0"
                                                  FontWeight="Bold">!
                            <TextBlock.Triggers>
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                                </EventTrigger>
                            </TextBlock.Triggers>
                                       </TextBlock>
                                       <Border BorderBrush="Red"
                                               BorderThickness="1">
                                          <AdornedElementPlaceholder Name="myControl" />
                                       </Border>
                                    </DockPanel>
                                 </ControlTemplate>
                              </Validation.ErrorTemplate>
                           </TextBox>
                        </DataTemplate>
                     </dg:DataGridTemplateColumn.CellTemplate>
                  </dg:DataGridTemplateColumn>

                  <dg:DataGridTextColumn x:Name="UserName"
                                         Width="Auto"
                                         Header="{StaticResource P.UserName}">
                     <dg:DataGridTextColumn.DataFieldBinding>
                        <Binding Path="UserName"
                                 ValidatesOnDataErrors="True">
                           <Binding.ValidationRules>
                              <DataErrorValidationRule />
                           </Binding.ValidationRules>
                        </Binding>
                     </dg:DataGridTextColumn.DataFieldBinding>
                  </dg:DataGridTextColumn>
               </dg:DataGrid.Columns>
            </dg:DataGrid>
         </Border>
      </DockPanel>
   </Grid>
</Window>

Sep 21, 2008 at 9:46 PM
Samantha

unfortunately I did not get any step further. My problem stays: How can I define a error template for all my datagrid cols?

Error Checking is done using IDAtaErrorInfo in my business layer.

As it is no problem to define a "flashing error mark" for one cell,I surely miss only a little missing link. Could you drop me in the right direction?

Here Is my XAML which works fine for a single column (bound to "username"), and which I'd like to use for each of my columns, so it would be great to get Binding.Path for the textbox from the datagrid column.

Thanks again
Klaus

                  <dg:DataGridTemplateColumn Header="MyUserName">
                     <dg:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                           <TextBox BorderThickness="0"
                                    x:Name="myTextBox">
                              <TextBox.Text>
                                 <Binding Path="UserName">
                                    <Binding.ValidationRules>
                                       <DataErrorValidationRule />
                                    </Binding.ValidationRules>
                                 </Binding>
                              </TextBox.Text>
                              <Validation.ErrorTemplate>
                                 <ControlTemplate>
                                    <DockPanel LastChildFill="True">
                                       <Ellipse DockPanel.Dock="Right"
                                                ToolTip="{Binding ElementName=myTextbox,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                                Width="15"
                                                Height="15"
                                                Margin="-25,0,0,0"
                                                StrokeThickness="1"
                                                Fill="Red">
                                          <Ellipse.Stroke>
                                             <LinearGradientBrush EndPoint="1,0.5"
                                                                  StartPoint="0,0.5">
                                                <GradientStop Color="#FFFA0404"
                                                              Offset="0" />
                                                <GradientStop Color="#FFC9C7C7"
                                                              Offset="1" />
                                             </LinearGradientBrush>
                                          </Ellipse.Stroke>
                                          <Ellipse.Triggers>
                                             <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                                <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                                             </EventTrigger>
                                          </Ellipse.Triggers>
                                       </Ellipse>
                                       <TextBlock DockPanel.Dock="Right"
                                                  ToolTip="{Binding ElementName=myControl,
                                     Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                                  Foreground="White"
                                                  FontSize="11pt"
                                                  Margin="-15,5,0,0"
                                                  FontWeight="Bold">!
                            <TextBlock.Triggers>
                                <EventTrigger RoutedEvent="FrameworkElement.Loaded">
                                    <BeginStoryboard Storyboard="{StaticResource FlashErrorIcon}" />
                                </EventTrigger>
                            </TextBlock.Triggers>
                                       </TextBlock>
                                       <Border BorderBrush="Red"
                                               BorderThickness="1">
                                          <AdornedElementPlaceholder Name="myControl" />
                                       </Border>
                                    </DockPanel>
                                 </ControlTemplate>
                              </Validation.ErrorTemplate>
                           </TextBox>
                        </DataTemplate>
                     </dg:DataGridTemplateColumn.CellTemplate>
                  </dg:DataGridTemplateColumn>

Coordinator
Sep 22, 2008 at 5:46 PM
Hi Klaus,

I apologize for the delay in my response.  I wanted to put together answers to all of your questions before I replied.  I think I've covered everything below - let me know if you need any additional help.

Thanks!
Samantha

1.       Is there an easy way to use alternating colors and validation visuals as background?

We weren’t able to repro the issue you were seeing here.  Here are a few changes you can add to the XAML that you gave me which should work:

 

1.       On the DataGrid, make sure you set a RowBackground and AlternatingRowBackground like so:

                     RowBackground="Yellow"

                     AlternatingRowBackground="Cyan"

2.       In the CellTemplate of your TemplateColumn, set the following Style on the TextBox in the cell template:

<TextBox.Style>

                      <Style TargetType="{x:Type TextBox}">

                        <Setter Property="Background" Value="{Binding Path=Background, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}}"></Setter>

                        <Style.Triggers>

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

                            <Setter Property="Background" Value="Green"></Setter>

                          </Trigger>

                        </Style.Triggers>

                      </Style>

                    </TextBox.Style>

This should work with the validation error templates you already had set up.

 

2.       Do you need to set both CellTemplate and CellEditingTemplate in TemplateColumn?

You only need both if you want the editing template to be different from the default editing template of the control in the CellTemplate.  For example, if you put a Button in the CellTemplate and wanted the Button’s content to be editable, since Button does not have a default editing template, you would need to specify one by making the CellEditingTemplate a TextBox.  In your markup you have the following TemplateColumn:

 

            <dg:DataGridTemplateColumn Header="{StaticResource P.RowSelectedYN}">

              <dg:DataGridTemplateColumn.CellTemplate>

                <DataTemplate>

                  <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />

                </DataTemplate>

              </dg:DataGridTemplateColumn.CellTemplate>

              <dg:DataGridTemplateColumn.CellEditingTemplate>

                <DataTemplate>

                  <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />

                </DataTemplate>

              </dg:DataGridTemplateColumn.CellEditingTemplate>

            </dg:DataGridTemplateColumn>

 

Since the CellTemplate and CellEditingTemplate are the same, you should be able to delete the CellEditingTemplate without any adverse effects.

 

3.       In TemplateColumn.Cell(Editing)Template, how do you refer to a property of that column (e.g. Header or Tag)?

This blog post from Vincent Sibal has a bunch of examples which might help you: http://blogs.msdn.com/vinsibal/archive/2008/09/16/wpf-datagrid-styling-rows-and-columns-based-on-header-conditions-and-other-properties.aspx

Notice, though, that you can’t do a regular binding through the CellTemplate or CellEditingTemplate because the Header and the Column are actually in different trees.  Also, DataGridColumns don’t have the Tag property because they derive from DependencyObject.

 

4.       How do you define a style which will affect all columns?

Since DataGridColumns are not UI objects, they do not derive from FrameworkElement and therefore do not have a Style property (which is why your style with a TargetType of DataGridTemplateColumn wouldn’t work).  If you’re just trying to keep your markup in the DataGrid.Columns as clean as possible, what you can do is define a DataTemplate(s) for the CellTemplate (and CellEditingTemplate if needed) in the Resources of your DataGrid and then apply it in your markup like so:

 

<dg:DataGrid.Resources>

            <DataTemplate x:Key="MyCellTemplate">

                  <CheckBox IsChecked="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridRow}}, Path=IsSelected}" />

</DataTemplate>

</dg:DataGrid.Resources>

 

<dg:DataGridTemplateColumn Header="{StaticResource P.RowSelectedYN}" CellTemplate="{StaticResource MyCellTemplate}" />


You should be able to follow a similar model (using a ControlTemplate) for the Validation.ErrorTemplate. 

5.       How do you add a row adorner (for validation error purposes)?

The following markup in the RowValidationErrorTemplate will create a row adorner.  This will not work in the CTP, but should be functional in version 1 of the DataGrid.

 

<dg:DataGrid.RowValidationRules>

        <ValidationRule1 ValidationStep="ConvertedProposedValue"/>

        < ValidationRule2 ValidationStep="ConvertedProposedValue"/>

      </dg:DataGrid.RowValidationRules>     

      <dg:DataGrid.RowValidationErrorTemplate>

        <ControlTemplate>

          <Grid VerticalAlignment="Center" Margin="1,0,0,0">

            <Path HorizontalAlignment="Left" VerticalAlignment="Top" Width="16.575" Height="15.013" Fill="#FFFF0000" Stretch="Fill" Data="M2.1923326,13.126918 C2.1923326,13.126918 9.1242888,3.5218058 11.44987,4.1032011 13.775452,4.6845965 11.006801,6.2345933 11.006801,6.2345933 L2.7678984,12.676864 z" Margin="0,1.221,0,0"/>

            <Path HorizontalAlignment="Left" VerticalAlignment="Top" Width="12.077" Height="14.755" Fill="#FFFF0000" Stretch="Fill" Data="M4.5860118,1.5306513 C4.1381923,2.7258072 11.531427,12.355297 15.344682,14.995039 17.329125,16.368778 11.249546,6.4430705 10.861957,6.4430705 10.474368,6.4430705 5.1923887,-0.087668306 4.5860118,1.5306513 z" Margin="2.832,1.183,0,0"/>

            <Path HorizontalAlignment="Left" Margin="10.032,2.432,0,0" VerticalAlignment="Top" Width="6.52" Height="5.383" Fill="#FFAB0404" Stretch="Fill" Data="M8.3585482,7.6118008 C8.3585482,7.6118008 9.3829609,6.4793429 10.484053,5.7423106 13.2862,3.8666516 12.129617,5.3933616 12.129617,5.3933616 L8.6703291,8.0523064 z"/>

            <Path HorizontalAlignment="Left" Margin="0.287,8.6,0,0" VerticalAlignment="Top" Width="8.35" Height="7.361" Fill="#FFAB0404" Stretch="Fill" Data="M2.9934119,12.280726 C2.9934119,12.280726 5.089625,10.252776 6.167291,9.3225421 7.981912,7.7561752 7.4187557,8.9838185 7.4187557,8.9838185 L2.3691003,12.962527 z"/>

            <Path HorizontalAlignment="Left" Margin="2.815,1.686,0,0" VerticalAlignment="Top" Width="4.308" Height="6.059" Fill="#FFAB0404" Stretch="Fill" Data="M9.0912287,5.4815006 C9.0912287,5.4815006 8.3470667,4.9998377 8.3478827,3.5762162 8.3480212,3.3346328 10.995948,6.9120685 10.995948,6.9120685 L10.684377,7.2172575 z"/>

            <Path HorizontalAlignment="Left" Margin="7.841,8.657,0,0" VerticalAlignment="Top" Width="6.912" Height="7.304" Fill="#FFAB0404" Stretch="Fill" Data="M11.86076,8.6826086 C11.86076,8.6826086 10.862828,7.9143165 11.929675,7.7667842 12.168981,7.733691 15.684906,12.169333 15.684906,12.169333 L14.649119,11.36494 z"/>

          </Grid>

        </ControlTemplate>

      </dg:DataGrid.RowValidationErrorTemplate>

Sep 22, 2008 at 8:28 PM
Samantha
thanks for your quick answer.

Maybe I do not see it, but please take a look at my last reply to you.

There I have my DataTemplate beginning like so
                        <DataTemplate>
                           <TextBox BorderThickness="0"
                                    x:Name="myTextBox">
                              <TextBox.Text>
                                 <Binding Path="UserName">
                                    <Binding.ValidationRules>
                                       <DataErrorValidationRule />
                                    </Binding.ValidationRules>
                                 </Binding>
                              </TextBox.Text>
...
...
This works fine for one of my properties named "UserName".
My problem is: how can I define one Template for all of my columns (this can be up to 20) ? Somehow this template's text box has to use the same property to bind to which is also used by the column itself.
I tried something like this
                        <DataTemplate>
                           <TextBox BorderThickness="0"
                                    x:Name="myTextBox">
                              <TextBox.Text>
                           <Binding Source="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type dg:DataGridColumn}}, Path=DataFieldBinding}">
Which does not work...
Or must I define one CellTemplate for each property / column?

Regards
Klaus

Coordinator
Sep 24, 2008 at 12:01 AM
Hi Klaus,

I think unforunately you're going to have to define a CellTemplate for each column that you want to bind to a specific property.  You can share the Style used within each of those templates, but each column is going to need its own DataTemplate to hook up the Binding.  There are a few reasons for this:

  • First, you can't use FindAncestor in the binding since it walks the visual tree, and since Column is a DependencyObject and not a visual, it doesn't appear in the tree.
  • Second, even if you could walk the tree, DataGridTemplateColumn does not have a Binding or DataFieldBinding property which you could use to set the Binding in the DataTemplate (only the stock columns that subclass DataGridBoundColumn have this property, such as DataGridTextColumn, DataGridCheckBoxColmn, etc.)
  • Third, even if the TemplateColumn did have a Binding or DataFieldBinding property and you could bind to it from the DataTemplate, it wouldn't actually save you much work, because you would still need to set a value in the Binding of each individual column anyway.
  • Finally, you don't want to set the Source of the Binding explicitly because the item object of that row is implicitly set as DataContext and we would lose this if you set the Source.  You need to have the DataContext or all of the cells in the column will display the same thing (a property of the column) instead of displaying the property of the row item.  Instead, you'd want to set the Path of the Binding to some property on the column, but unfortunately this won't work because there is a technical limitation which prevents the Path of a Binding from being another Binding.

So I think you're going to have to take the long route and set the CellTemplate for each column to the property you want it to bind to.

I have one more question, though.  From the XAML you sent me, it seemed that you could use the stock columns instead of template columns.  Is there a reason you chose to use template columns instead of the stock columns?  If you're just doing it so that you can do validation, then you might want to try something like this instead:

                <DataGridTextColumn>

                                <DataGridTextColumn.Binding>

                                                <Binding Path=”UserName”>

                                                <Binding.ValidationRules>
                                                                                <DataErrorValidationRule />
                                                                </Binding.ValidationRules>

                                                </Binding>

                                </DataGridTextColumn.Binding>

                                <DataGridTextColumn.EditingElementStyle>

<Style TargetType="{x:Type TextBox}">

<Style.Triggers>

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

<Setter Property="Background" Value="Red"></Setter>

</Trigger>

</Style.Triggers>

</Style>

                                </DataGridTextColumn.EditingElementStyle>

                </DataGridTextColumn>

Sep 24, 2008 at 12:19 PM
Samantha

yesssss!! that comes close to what I search for. I did not took the stock columns into account as I am really new to wpf and simply took your template and played around.

Now I use this XAml which works fine. As I only search for a way to highlight the error cells, I used the style as ElementStyle

                  <Style x:Key="TextBoxColumnStyle" TargetType="{x:Type TextBlock}">
                     <Style.Triggers>
                        <Trigger Property="Validation.HasError"
                                 Value="True">
                           <Setter Property="Background"
                                   Value="LightSalmon"></Setter>
                           <Setter Property="ToolTip"
                                   Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}},Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
                           </Setter>
                        </Trigger>
                     </Style.Triggers>
                  </Style>

               </toolkit:DataGrid.Resources>
               <toolkit:DataGrid.Columns>

                  <toolkit:DataGridTextColumn ElementStyle="{StaticResource TextBoxColumnStyle}" >
                     <toolkit:DataGridTextColumn.DataFieldBinding>
                        <Binding Path="UserName">
                           <Binding.ValidationRules>
                              <DataErrorValidationRule />
                           </Binding.ValidationRules>
                        </Binding>
                     </toolkit:DataGridTextColumn.DataFieldBinding>

What is missing is a tooltip displaying the error for this cell. As you see I tried to set this, which is successful for a constant expression like
Value="123 error!"
but fails for this                                  
Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}},Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"

Thank you for not giving up
Regards
Klaus






Coordinator
Sep 24, 2008 at 6:40 PM

Hi Klaus,

Great!  I'm glad I was able to help :-)

For the Tooltip, I think you just have a few small errors in your Binding syntax (it's confusing, I know!).  You want to set RelativeSource=Self and then just remove the AdornedElement from the Path (since there is no AdornedElement property on TextBlock), like so:

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

Let me know if that works for you.

Thanks!
Samantha

Sep 24, 2008 at 9:10 PM
Samatha

Great, it works, very fine!!

I have one "new" phenomenon, but I will open a new thread.

Thank you so much for your support

Regards
Klaus
Sep 24, 2008 at 9:24 PM
Samantha

this is only one small thing I'd like to optimize:

Is there a way to place the part

                           <Binding.ValidationRules>
                              <DataErrorValidationRule />
                           </Binding.ValidationRules>

in the style, as this will repeat for each property?

Thanks
Klaus
Oct 9, 2008 at 4:34 AM
Edited Oct 9, 2008 at 5:41 AM
Hi Klaus, Samantha,

I am doing in exactly same way the Klaus is doing for validation. My code snippet is....


<dg:DataGrid.Resources>
          <Style x:Key="TextBoxColumnStyle" TargetType="{x:Type TextBlock}">
            <Style.Triggers>
              <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="Background" Value="LightSalmon"></Setter>
                <!--<Setter Property="ToolTip"
                  Value="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type TextBlock}},Path=AdornedElement.(Validation.Errors)[0].ErrorContent}">
          </Setter>-->
              </Trigger>
            </Style.Triggers>
          </Style>
        </dg:DataGrid.Resources>
       
        <dg:DataGrid.Columns>

         <dg:DataGridTextColumn ElementStyle="{StaticResource TextBoxColumnStyle}" Width="42">
           
            <dg:DataGridTextColumn.DataFieldBinding>
              <Binding Path="Name">
                <Binding.ValidationRules>
                  <DataErrorValidationRule/>   <!--<ExceptionValidationRule />-->
                </Binding.ValidationRules>
              </Binding>
            </dg:DataGridTextColumn.DataFieldBinding>
          </dg:DataGridTextColumn>
</dg:DataGrid.Columns>


C# code.....

public string Name
        {
            get
            {
                return _Name;
            }
            set
            {

                if (string.IsNullOrEmpty(value))
                {
                    throw new ApplicationException("name is mandatory.");
                }
              
               _Name=value;

            }
        }

If I leave cell empty, it's throwing an exception (as normal), but nothing happens, it's not changing the style of the cell.

If I do the same thing outside the DataGrid with normal TextBlock than it works!!

Any idea?

thanks.

Coordinator
Nov 2, 2008 at 3:18 PM
I don't see the immediate problem with your code but see this sample here, http://blogs.msdn.com/vinsibal/archive/2008/10/22/wpf-datagrid-and-the-wpftoolkit-have-released.aspx.  It shows both row and cell level validation.  Maybe this can help with your issue.