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,676 questions
0 comments No comments
{count} votes

Accepted answer
  1. Peter Fleischer (former MVP) 19,231 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. Peter Fleischer (former MVP) 19,231 Reputation points
    2020-06-15T04:40:01.893+00:00

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