WPF BindingGroup Validation

Allanjb 246 Reputation points
2020-06-09T02:02:00.597+00:00

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 Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,784 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,326 Reputation points
    2020-06-13T12:03:51.073+00:00

    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 person found this answer helpful.

5 additional answers

Sort by: Most helpful
  1. DaisyTian-1203 11,626 Reputation points
    2020-06-09T07:34:06.763+00:00

    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


  2. Peter Fleischer (former MVP) 19,326 Reputation points
    2020-06-09T09:01:22.37+00:00

    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; }
        }
      }
    }
    

  3. Allanjb 246 Reputation points
    2020-06-10T23:16:49.827+00:00

    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.

    0 comments No comments

  4. Allanjb 246 Reputation points
    2020-06-15T01:08:08.12+00:00

    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;
            }
        }
    
    }
    
    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.