question

Allanjb avatar image
0 Votes"
Allanjb asked ·

WPF BindingGroup Validation

I am using a StackPanel to host several TextBoxes that have validation rules attached.
I also have a StackPanel.BindingGroup validation as follows:
<StackPanel.BindingGroup>
<BindingGroup Name="ValidateAllFields" NotifyOnValidationError="True">
<BindingGroup.ValidationRules>
<local:ValidateAll ValidationStep="ConvertedProposedValue"/>
</BindingGroup.ValidationRules>
</BindingGroup>
</StackPanel.BindingGroup>

I have a BindingGroup validation rule called: ValidateAll from which I would like to display the error message in a TextBlock on my StatusBar.
I only want to display the BindingGroup:ValidateAll message as the TextBox validation messages are displayed below the TextBoxes.
I know I can do this in code by handling the ItemError event, where I can get the rule associated with an error message through the ValidationError.RuleInError property.
I would like to be able to accomplish this in xaml, possibly by setting up a Style/Trigger/Setter combination to my StatusBar TextBlock.
Any help would be much appreciated.

windows-wpf
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

Hi, if you want to see default error information you can include your own conversion and use IDataError in dataobject like in following demo:

XAML:

 <Window x:Class="WpfApp1.Window44"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApp44"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         mc:Ignorable="d"
         Title="Demo Validation IDataError" Height="450" Width="800">
   <Window.DataContext>
     <local:ViewModel/>
   </Window.DataContext>
   <StackPanel x:Name="panel">
     <StackPanel.BindingGroup>
       <BindingGroup x:Name="ValidateAllFields" NotifyOnValidationError="True">
         <BindingGroup.ValidationRules>
           <local:ValidateAll ValidationStep="ConvertedProposedValue"/>
         </BindingGroup.ValidationRules>
       </BindingGroup>
     </StackPanel.BindingGroup>
     <StackPanel.Resources>
       <DataTemplate DataType="{x:Type ValidationError}">
         <TextBlock Text="{Binding ErrorContent}"/>
       </DataTemplate>
     </StackPanel.Resources>
     <i:Interaction.Behaviors>
       <local:StackPanelBehavior/>
     </i:Interaction.Behaviors>
     <StackPanel DataContext="{Binding View}">
       <TextBox Margin="5">
         <TextBox.Text>
           <Binding Path="Name" BindingGroupName="ValidateAllFields" UpdateSourceTrigger="PropertyChanged"/>
         </TextBox.Text>
       </TextBox>
       <TextBox Margin="5">
         <TextBox.Text>
           <Binding Path="Age" BindingGroupName="ValidateAllFields" UpdateSourceTrigger="PropertyChanged"/>
         </TextBox.Text>
       </TextBox>
     </StackPanel>
     <Button Content="Button to execute BindingGroup.CommitEdit" Command="{Binding Cmd}" Margin="5"/>
     <StatusBar Margin="5 20 5 0">
       <ContentPresenter Content="{Binding ElementName=panel, Path=(Validation.Errors).CurrentItem}"/>
     </StatusBar>
   </StackPanel>
 </Window>



 using System;
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Globalization;
 using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Input;
 using System.Windows.Interactivity;
    
 namespace WpfApp44
 {
   public class ViewModel
   {
     public Data View { get; set; } = new Data() { Name = "xxx" };
     public ICommand Cmd { get => new RelayCommand((state) => { ValidateAllFields?.CommitEdit(); }, null); }
     public BindingGroup ValidateAllFields { get; set; }
   }
    
   public class Data : IDataErrorInfo, INotifyPropertyChanged
   {
     private string _name = string.Empty;
     public string Name
     {
       get => this._name;
       set { this._name = value; errorMessages[nameof(Name)] = this[nameof(Name)]; OnPropertyChanged(); }
     }
    
     private int _age = 0;
     private string _ageString = null;
     public object Age
     {
       get => (this._ageString == null) ? this._age.ToString() : this._ageString;
       set
       {
         if (value == null) this._ageString = "?";
         else
         {
           this._ageString = value.ToString();
           int.TryParse(value.ToString(), out this._age);
           errorMessages[nameof(Age)] = this[nameof(Age)];
           OnPropertyChanged();
         }
       }
     }
    
     private Dictionary<string, string> errorMessages = new Dictionary<string, string>();
    
     public string Error
     {
       get
       {
         String result = String.Empty;
         foreach (var item in errorMessages)
           if (!string.IsNullOrEmpty(item.Value)) result += (string.IsNullOrEmpty(result)) ? item.Value : Environment.NewLine + item.Value;
         return result;
       }
     }
     public string this[string columnName]
     {
       get
       {
         string result = string.Empty;
         switch (columnName)
         {
           case "Name": if (string.IsNullOrEmpty(Name)) result = "Name may not be null or empty"; break;
           case "Age":
             if (string.IsNullOrEmpty(this._ageString)) result = "Age may not be null or empty";
             else if (this._age < 18 || this._age > 65) result = "Age must be beetween 18 an 65"; break;
         };
         return result;
       }
     }
    
     public event PropertyChangedEventHandler PropertyChanged;
     protected void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
       PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
   }
    
   public class ValidateAll : ValidationRule
   {
     public override ValidationResult Validate(object value, CultureInfo cultureInfo)
     {
       BindingGroup bindingGroup = (BindingGroup)value;
       ViewModel vm = (ViewModel)bindingGroup.Items[0];
       if (!string.IsNullOrEmpty(vm.View.Error)) return new ValidationResult(false, vm.View.Error);
       return ValidationResult.ValidResult;
     }
   }
    
   public class StackPanelBehavior : Behavior<StackPanel>
   {
     protected override void OnAttached()
     {
       var vm = AssociatedObject.DataContext as ViewModel;
       var bg = AssociatedObject.BindingGroup;
       if (vm == null || bg == null) return;
       vm.ValidateAllFields = bg;
     }
   }
    
   public class RelayCommand : ICommand
   {
     private readonly Predicate<object> _canExecute;
     private readonly Action<object> _action;
     public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
     public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
     public void Execute(object o) => _action(o);
     public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
     public event EventHandler CanExecuteChanged
     {
       add { CommandManager.RequerySuggested += value; }
       remove { CommandManager.RequerySuggested -= value; }
     }
   }
 }

9983-13-06-2020-14-02-52.gif



· 1 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks for getting back to me, I have learned a lot from your approach and will accept your answer as it goes a long way to resolving my problem. I was looking for a slightly different scenario where all the field validation error messages are displayed with the text boxes in an adornment layer. Only the Window level validation to be displayed in the StatusBar. I have been working on this in the meantime and have worked out another approach, different from yours which I will share with you.

0 Votes 0 ·
DaisyTian-MSFT avatar image
0 Votes"
DaisyTian-MSFT answered ·

You can use Style/Trigger/Setter to implement the validation in the xaml and create the error message in the model class:

Model class:

  public class Person : IDataErrorInfo, INotifyPropertyChanged
     {
         private int age;
    
         public int Age
         {
             get { return age; }
             set {
                 age = value;
                 RaisePropertyChanged("Age");
             }
         }
    
         private string name;
    
         public string Name
         {
             get { return name; }
             set {
                 name = value;
                 RaisePropertyChanged("Name");
             }
         }
    
         #region IDataErrorInfo Members
    
         public string Error
         {
             get { return null; }
         }
    
         public string this[string columnName]
         {
             get
             {
                 string result = string.Empty;
                 switch (columnName)
                 {
                     case "Name": if (string.IsNullOrEmpty(Name)) result = "Name is required!"; break;
                     case "Age": if ((Age < 1) || (Age > 100)) result = "Age must be between 1 and 100"; break;
                 };
                 return result;
             }
         }
    
         #endregion
    
         #region INotifyPropertyChanged Members
    
         public event PropertyChangedEventHandler PropertyChanged;
    
         private void RaisePropertyChanged(string propertyName)
         {
             if (PropertyChanged != null)
             {
                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
             }
         }
    
         #endregion
     }

Then set the trigger for the TextBox in xaml:

  <Window.Resources>
         <Style TargetType="TextBox">
             <Setter Property="Validation.ErrorTemplate">
                 <Setter.Value>
                     <ControlTemplate>
                         <StackPanel Orientation="Horizontal">
                             <Border BorderThickness="1" BorderBrush="#FFdc000c" VerticalAlignment="Top">
                                 <Grid>
                                     <AdornedElementPlaceholder x:Name="adorner" Margin="-1"/>
                                 </Grid>
                             </Border>
                             <Border x:Name="errorBorder" Background="#FFdc000c" Margin="8,0,0,0"
                                 Opacity="0" CornerRadius="0"
                                 IsHitTestVisible="False"
                                 MinHeight="24" >
                                 <TextBlock Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                        Foreground="White" Margin="8,2,8,3" TextWrapping="Wrap" VerticalAlignment="Center"/>
                             </Border>
                         </StackPanel>
                         <ControlTemplate.Triggers>
                             <DataTrigger Value="True">
                                 <DataTrigger.Binding>
                                     <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" />
                                 </DataTrigger.Binding>
                                 <DataTrigger.EnterActions>
                                     <BeginStoryboard x:Name="fadeInStoryboard">
                                         <Storyboard>
                                             <DoubleAnimation Duration="00:00:00.15"
                                                          Storyboard.TargetName="errorBorder"
                                                          Storyboard.TargetProperty="Opacity"
                                                          To="1"/>
                                         </Storyboard>
                                     </BeginStoryboard>
                                 </DataTrigger.EnterActions>
                                 <DataTrigger.ExitActions>
                                     <StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
                                     <BeginStoryboard x:Name="fadeOutStoryBoard">
                                         <Storyboard>
                                             <DoubleAnimation Duration="00:00:00"
                                                          Storyboard.TargetName="errorBorder"
                                                          Storyboard.TargetProperty="Opacity"
                                                          To="0"/>
                                         </Storyboard>
                                     </BeginStoryboard>
                                 </DataTrigger.ExitActions>
                             </DataTrigger>
                         </ControlTemplate.Triggers>
                     </ControlTemplate>
                 </Setter.Value>
             </Setter>
         </Style>
     </Window.Resources>
     <Grid >
         <Grid.RowDefinitions>
             <RowDefinition Height="40" />
             <RowDefinition Height="40" />
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="140" />
             <ColumnDefinition Width="300" />
         </Grid.ColumnDefinitions>
         <Label Grid.Row="0"
                Height="28"
                Margin="0,0,10,0"
                HorizontalAlignment="Right"
                VerticalAlignment="Center"
                Content="Name:" />
         <Label Grid.Row="1"
                Height="28"
                Margin="0,0,10,0"
                HorizontalAlignment="Right"
                VerticalAlignment="Center"
                Content="Age(Integer):" />
    
    
         <TextBox Name="txtName"
                  Grid.Row="0"
                  Grid.Column="1"
                  Width="200"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Center"
                  Text="{Binding Name, ValidatesOnDataErrors=True}" />
    
         
         <TextBox Name="txtAge"
                  Grid.Row="1"
                  Grid.Column="1"
                  Width="200"
                  HorizontalAlignment="Left"
                  VerticalAlignment="Center"
                  Text="{Binding Age, ValidatesOnDataErrors=True}" />
     </Grid>

Add DataContext = new Person(); in this xaml.cs

· 1 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thanks for the response, I have managed to review the solution you proposed, (Answer 1) and while It has given me a lot of help, it's not the solution I am looking for.
I have no problem binding and validating my individual TextBoxes by using INotifyPropertyChanged and Validation Rules, What I would like to do is use the BindingGroup class to run a ValidateAll rule for all TextBoxes.

Basically, I want to bind my StatusBar:TextBlock text to my StackPanel as follows:

 TextBlock Text="{Binding ElementName=textBoxes, Path=(Validation.Errors)[0].ErrorContent}"

I want to make this conditional on selecting only the message being triggered by the ValidateAll rule. Currently I will get all the error messages in the collection.
It's the Style/Setter/Trigger syntax for this scenario I am struggling with.

0 Votes 0 ·
PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

Hi, try following MVVM demo. Button executes BindingGroup.CommitEdit. Error information will be displayed in DataTemplate in ContentPresenter in StatusBar.

XAML:

 <Window x:Class="WpfApp1.Window42"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfApp42"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         mc:Ignorable="d"
         Title="Demo Validation" Height="450" Width="800">
   <Window.DataContext>
     <local:ViewModel/>
   </Window.DataContext>
   <StackPanel x:Name="panel">
     <StackPanel.BindingGroup>
       <BindingGroup x:Name="ValidateAllFields" NotifyOnValidationError="True">
         <BindingGroup.ValidationRules>
           <local:ValidateAll ValidationStep="ConvertedProposedValue"/>
         </BindingGroup.ValidationRules>
       </BindingGroup>
     </StackPanel.BindingGroup>
     <StackPanel.Resources>
       <DataTemplate DataType="{x:Type ValidationError}">
         <TextBlock Text="{Binding ErrorContent}"/>
       </DataTemplate>
     </StackPanel.Resources>
     <i:Interaction.Behaviors>
       <local:StackPanelBehavior/>
     </i:Interaction.Behaviors>
     <TextBox Margin="5">
       <TextBox.Text>
         <Binding Path="Name" BindingGroupName="ValidateAllFields" UpdateSourceTrigger="PropertyChanged"/>
       </TextBox.Text>
     </TextBox>
     <Button Content="Button to execute BindingGroup.CommitEdit" Command="{Binding Cmd}" Margin="5"/>
     <StatusBar Margin="5 20 5 0" Height="30">
       <ContentPresenter Content="{Binding ElementName=panel, Path=(Validation.Errors).CurrentItem}"/>
     </StatusBar>
   </StackPanel>
 </Window>

Code:

 using System;
 using System.Globalization;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.Windows.Input;
 using System.Windows.Interactivity;
    
 namespace WpfApp42
 {
   public class ViewModel
   {
     public string Name { get; set; } =string.Empty;
    
     public ICommand Cmd { get => new RelayCommand((state) => { ValidateAllFields?.CommitEdit(); }, null); }
    
     public BindingGroup ValidateAllFields { get; set; }
   }
    
   public class ValidateAll : ValidationRule
   {
     public override ValidationResult Validate(object value, CultureInfo cultureInfo)
     {
       BindingGroup bindingGroup = (BindingGroup)value;
       ViewModel vm = (ViewModel)bindingGroup.Items[0];
       if (string.IsNullOrEmpty(vm.Name)) return new ValidationResult(false, "Name may not be null or empty");
       return ValidationResult.ValidResult;
     }
   }
    
   public class StackPanelBehavior : Behavior<StackPanel>
   {
     protected override void OnAttached()
     {
       var vm = AssociatedObject.DataContext as ViewModel;
       var bg = AssociatedObject.BindingGroup;
       if (vm == null || bg == null) return;
       vm.ValidateAllFields = bg;
     }
   }
    
   public class RelayCommand : ICommand
   {
     private readonly Predicate<object> _canExecute;
     private readonly Action<object> _action;
     public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
     public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
     public void Execute(object o) => _action(o);
     public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
     public event EventHandler CanExecuteChanged
     {
       add { CommandManager.RequerySuggested += value; }
       remove { CommandManager.RequerySuggested -= value; }
     }
   }
 }
· 2 ·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Trying to get your code to build but have a problem with the RelayCommand. Are you using the MVVM Light Toolkit: mvvm-commands-relaycommands-and-eventtocommand
described in the MVVM article or are you using your own implementation not included above? Also, you have a using statement: using WpfApp01, where do I get the source for this?


0 Votes 0 ·

Hi, I correct my code (delete using WpfApp01, add RelayCommand).

0 Votes 0 ·
Allanjb avatar image
0 Votes"
Allanjb answered ·

Hi: Have corrected your code as requested and added:

 using Microsoft.TeamFoundation.MVVM;
 using ValidationRule = System.Windows.Controls.ValidationRule;


I found the Microsoft.TeamFoundation.Controls.dll in my implementation of VisualStudio2019 on my C: drive.
I thought it should be available through a NuGetPackage: the Microsoft.TeamFoundationServer.ExtendedClient, which I installed in my project but could not find it in the list of references or in the project directory.

I have added a property to your view model:

  public int Age { get; set; } = 0;

and the following TextBox to the MainWindow under the Name TextBox:

  <TextBox Margin="5">
                 <TextBox.Text>
                     <Binding Path="Age" UpdateSourceTrigger="PropertyChanged"/>
                 </TextBox.Text>
             </TextBox>

The purpose of adding the TextBox is I wanted to see if the default error handling message, (non integer value or Null) would be displayed in the StatusBar when selecting the Button, which it was. I only want to see the ValidateAll message on the StatusBar, the TestBox validations will be displayed in the adornment layer of the TextBox.

·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Allanjb avatar image
0 Votes"
Allanjb answered ·

Here is my solution:

XAML:

 Window x:Class="WpfGroupValidationDemo4.MainWindow"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:local="clr-namespace:WpfGroupValidationDemo4"
         mc:Ignorable="d"
         Title="MainWindow" Height="450" Width="800">
     <Window.Resources>
    
         <local:ValidationRuleConverter x:Key="RuleConverterClass"/>
    
         <ControlTemplate x:Key="validationTemplate" >
             <StackPanel>
                 <!--Placeholder for the TextBox itself-->
                 <AdornedElementPlaceholder/>
                 <TextBlock Text="{Binding [0].ErrorContent}" Foreground="Red" Background="{DynamicResource {x:Static SystemColors.ControlLightLightBrushKey}}"/>
             </StackPanel>
         </ControlTemplate>
    
         <!-- Add a red border on validation error to a textbox control -->
         <Style x:Key="TextBoxBorderStyle" TargetType="TextBox">
             <Setter Property="Template">
                 <Setter.Value>
                     <ControlTemplate TargetType="{x:Type TextBox}">
                         <Border x:Name="bg" BorderBrush="#FFABADB3" BorderThickness="1">
                             <ScrollViewer x:Name="PART_ContentHost" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                         </Border>
                         <ControlTemplate.Triggers>
                             <Trigger Property="Validation.HasError" Value="True" >
                                 <Trigger.Setters>
                                     <Setter Property="BorderBrush" TargetName="bg"  Value="Red"/>
                                     <Setter Property="BorderThickness" TargetName="bg" Value="1"/>
                                     <Setter Property="SnapsToDevicePixels" TargetName="bg" Value="True"/>
                                 </Trigger.Setters>
                             </Trigger>
                         </ControlTemplate.Triggers>
                     </ControlTemplate>
                 </Setter.Value>
             </Setter>
         </Style>
            
         <Style x:Key="TextBlockStyle" TargetType="TextBlock">
             <Setter Property="Foreground" Value="#FF000000"/>
             <Style.Triggers>
                 <DataTrigger Binding="{Binding ElementName=TextBoxStack, Path=(Validation.Errors)[0].RuleInError, 
                     Converter={StaticResource RuleConverterClass}}" Value="True" >
                     <Setter Property="Foreground" Value="Red" />
                 </DataTrigger>
             </Style.Triggers>
         </Style>
     </Window.Resources>
    
     <Grid>
         <StackPanel HorizontalAlignment="Left" Height="204" Margin="168,125,0,0" VerticalAlignment="Top" Width="409" RenderTransformOrigin="0.5,0.5" Orientation="Horizontal">
             <StackPanel Width="184" HorizontalAlignment="Right">
                 <Label Content="Name:" HorizontalAlignment="Right" Margin="0,3"/>
                 <Label Content="Age:" HorizontalAlignment="Right"/>
             </StackPanel>
             <StackPanel  Name="TextBoxStack" Width="200" Height="202" Validation.ErrorTemplate="{x:Null}" >
                 <StackPanel.BindingGroup>
                     <BindingGroup Name="ValidateAllFields" NotifyOnValidationError="True">
                         <BindingGroup.ValidationRules>
                             <local:ValidateAll ValidationStep="ConvertedProposedValue"/>
                         </BindingGroup.ValidationRules>
                     </BindingGroup>
                 </StackPanel.BindingGroup>
                 <TextBox x:Name="NameTextBox" Style="{StaticResource TextBoxBorderStyle}" TextWrapping="Wrap" Height="26" VerticalContentAlignment="Center" 
                              Margin="0,3,130,3" Validation.ErrorTemplate="{StaticResource validationTemplate}" >
                     <TextBox.Text>
                         <Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
                             <Binding.ValidationRules>
                                 <local:ValidateNameRule ValidationStep="RawProposedValue" ValidatesOnTargetUpdated="True"/>
                             </Binding.ValidationRules>
                         </Binding>
                     </TextBox.Text>
                 </TextBox>
                 <TextBox x:Name="AgeTextBox" Style="{StaticResource TextBoxBorderStyle}" Height="26" TextWrapping="Wrap" VerticalContentAlignment="Center" 
                              Margin="0,0,130,3" Validation.ErrorTemplate="{StaticResource validationTemplate}" >
                     <TextBox.Text>
                         <Binding Path="Age" UpdateSourceTrigger="PropertyChanged">
                             <Binding.ValidationRules>
                                 <local:ValidateAgeRule ValidationStep="RawProposedValue" ValidatesOnTargetUpdated="True"/>
                             </Binding.ValidationRules>
                         </Binding>
                     </TextBox.Text>
                 </TextBox>
                 <Button Content="Button" Click="ButtonClick"/>
             </StackPanel>
         </StackPanel>
         <Label Content="BindingGroup Demo" HorizontalAlignment="Left" Margin="204,78,0,0" VerticalAlignment="Top" Width="305"/>
         <Label Content="Only Visible when All the textboxes pass validation!" HorizontalAlignment="Left" Margin="417,332,0,0" VerticalAlignment="Top" Width="286" >
             <Label.Style>
                 <Style TargetType="{x:Type Label}">
                     <Setter Property="Visibility" Value="Hidden" />
                     <Style.Triggers>
                         <!-- Require the controls to be valid in order to be visible -->
                         <MultiDataTrigger>
                             <MultiDataTrigger.Conditions>
                                 <Condition Binding="{Binding ElementName=NameTextBox, Path=(Validation.HasError)}" Value="false" />
                                 <Condition Binding="{Binding ElementName=AgeTextBox, Path=(Validation.HasError)}" Value="false" />
                                 <Condition Binding="{Binding ElementName=TextBoxStack, Path=(Validation.HasError)}" Value="false" />
                             </MultiDataTrigger.Conditions>
                             <Setter Property="Visibility" Value="Visible" />
                         </MultiDataTrigger>
                     </Style.Triggers>
                 </Style>
             </Label.Style>
         </Label>
         <StatusBar Margin="4,0,0,1" VerticalAlignment="Bottom" VerticalContentAlignment="Bottom" Padding="0,3" >
             <StatusBarItem>
                 <TextBlock Name="StatusTextBlock" Style="{StaticResource TextBlockStyle}" />
             </StatusBarItem>
         </StatusBar>
     </Grid>
 </Window>


Code:

 using System;
 using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Data;
 using System.ComponentModel;
 using System.Runtime.CompilerServices;
 using System.Globalization;
 using System.Windows.Input;
    
    
 namespace WpfGroupValidationDemo4
 {
     /// <summary>
     /// Interaction logic for MainWindow.xaml
     /// </summary>
     public partial class MainWindow : Window
     {
         public MainWindow()
         {
             InitializeComponent();
             this.DataContext = new ViewModel();
         }
    
         // This event occurs when a ValidationRule in the BindingGroup or in a Binding fails.
         //private void ItemError(object sender, ValidationErrorEventArgs e)
         //{
    
         //    if ((e.Action == ValidationErrorEventAction.Added) &&
         //        (e.Error.RuleInError.ToString() == "WpfGroupValidationDemo4.ValidateAll"))
         //    {
         //        StatusTextBlock.Text = e.Error.ErrorContent.ToString();
         //    }
         //    else
         //        StatusTextBlock.Text = String.Empty;
         //}
    
         private void ButtonClick(object sender, RoutedEventArgs e)
         {
             //this.TextBoxStack.BindingGroup.UpdateSources();
    
             if (!this.TextBoxStack.BindingGroup.UpdateSources())
                 StatusTextBlock.Text = (string)this.TextBoxStack.BindingGroup.ValidationErrors[0].ErrorContent;
             else
                 StatusTextBlock.Text = "Calculation Successful";
         }
     }
    
     public class ViewModel : INotifyPropertyChanged
     {
    
         public event PropertyChangedEventHandler PropertyChanged;   // Property changed event, raised when any of the Lease options are changed.
    
         public ViewModel()
         {
             this.name = "Allan";
             this.age = 30;
         }
    
         #region Properties
    
         private string name;
         public string Name
         {
             get { return this.name; }
             set
             {
                 if (value != name)
                 {
                     this.name = value;
                     this.OnPropertyChanged(nameof(Name));
                 }
             }
         }
         private int age;
         public int Age
         {
             get { return this.age; }
             set
             {
                 if (value != this.age)
                 {
                     this.age = value;
                     this.OnPropertyChanged(nameof(Age));
                 }
             }
         }
    
         #endregion Properties
    
         private void OnPropertyChanged([CallerMemberName] String propertyName = "")
         {
             PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
         }
     }
    
     #region Validation Rules
     public class ValidateAgeRule : ValidationRule
     {
         public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
         {
             if (!int.TryParse(value.ToString(), out int i))
                 return new ValidationResult(false, "Please enter a valid integer value.");
    
             if (i < 30 || i > 70)
                 return new ValidationResult(false, "Age must be between 30 and 70");
    
             return new ValidationResult(true, null);
    
         }
     }
    
     public class ValidateNameRule : ValidationRule
     {
         public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
         {
             string name = (string)value;
             if (name != "Allan" && name != "Jim")
                 return new ValidationResult(false, "Please enter the names: Allan or Jim");
    
             return new ValidationResult(true, null);
         }
     }
    
    
     public class ValidateAll : ValidationRule
     {
    
         public override ValidationResult Validate(object value, CultureInfo cultureInfo)
         {
             if (value == null)
                 return ValidationResult.ValidResult;
    
             BindingGroup bg = value as BindingGroup;
    
             ViewModel viewModel = bg.Items[0] as ViewModel;
    
             // Get the proposed values for age and name 
             bool ageResult = bg.TryGetValue(viewModel, "Age", out object ageValue);
             bool nameResult = bg.TryGetValue(viewModel, "Name", out object nameValue);
    
             if (!ageResult || !nameResult)
                 return new ValidationResult(false, "Properties not found");
    
             int age = (int)ageValue;
             string name = (string)nameValue;
    
             if ((age == 30) && (name == "Jim"))
                 return new ValidationResult(false, "Jim cannot be Thirty!");
    
             return ValidationResult.ValidResult;
         }
     }
    
     #endregion Validation Rules
    
     [ValueConversion(typeof(ValidationRule), typeof(Boolean))]
     public class ValidationRuleConverter : IValueConverter
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
             Boolean returnValue = false;
             ValidationRule rule = (ValidationRule)value;
             String name = rule.ToString();
    
             if (name == "WpfGroupValidationDemo4.ValidateAll")
                 returnValue = true;
    
             return returnValue;
         }
           
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
         {
             return value;
         }
     }
    
 }


·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

PeterFleischer-3316 avatar image
0 Votes"
PeterFleischer-3316 answered ·

Hi, it's better to simplified ValidationRuleConverter:

   [ValueConversion(typeof(ValidationRule), typeof(Boolean))]
   public class ValidationRuleConverter : IValueConverter
   {
     public object Convert(object value, Type targetType, object parameter, CultureInfo culture) => value is ValidateAll;
    
     public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => value;
   }
·
10 |1000 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.