Home > database >  C# WPF OnPropertyChange doesn't work when using ValidationRules
C# WPF OnPropertyChange doesn't work when using ValidationRules

Time:11-03

I've searched all over google and am still trying to come up with the correct answer. The problem is as follows when I clear this text field, the bound value does not get triggered.

enter image description here

So, the problem is that the bounded value of this text field is not getting changed if it is made empty, but my validation rule does detect it and sends out the warning.

enter image description here

Underneath, you can find my XAML snippet, the belonging validation rule, and the mentioned property.

  • The XAML-Snippet

    <TextBox Style="{StaticResource TextBoxErrorStyle}" 
       Margin="8 0 0 0"
             Visibility="{Binding AdditionalSurfaceTreatmentInfoVisibility, 
                          Converter={StaticResource BoolToVisConverter}}"
      FontSize="12" MaxLength="4" Width="40"
      Height="25">
        <TextBox.Text>
            <Binding Path="AdditionalSurfaceTreatmentInfo"
           UpdateSourceTrigger="PropertyChanged"
           NotifyOnSourceUpdated="True" Mode="TwoWay"
           NotifyOnValidationError="True">
                <Binding.ValidationRules>
          <classes:StandardValidationRule ValidatesOnTargetUpdated="True"/>
          </Binding.ValidationRules>
      </Binding>
        </TextBox.Text>
    </TextBox>
    
  • The StandardValidationRule:

    public class StandardValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            var valueToValidate = value as string;
            if (string.IsNullOrEmpty(valueToValidate))
            {
                return new ValidationResult(false, "Field is mandatory.");
            }
            return new ValidationResult(true, null);
        }
    }
    
  • The property:

    private string _additionalSurfaceTreatmentInfo;
    public string AdditionalSurfaceTreatmentInfo
    {
        get => _additionalSurfaceTreatmentInfo;
        set
        {
            _additionalSurfaceTreatmentInfo = value;
            OnPropertyChanged();
            SetOrModifySurfaceTreatment();
            Console.WriteLine(string.IsNullOrEmpty(_additionalSurfaceTreatmentInfo).ToString());
        }
    }
    

Thank you in advance for your efforts. Any help is much appreciated!

The code above does work as I prefer. I already tried everything regarding the different properties I can fill in within the ValidationRule. The only thing that needs to be changed is that when the textbox is empty, it must trigger the OnPropertyChanged() method. This way, I can later validate the property when I, for example, submit a save command.

CodePudding user response:

Use the ValidationStep property. Example:

<local:StandardValidationRule ValidationStep="UpdatedValue"
                              ValidatesOnTargetUpdated="True"/>

But to validate the source property, more complex logic is needed, since the validator does not receive the value of the property, but the binding expression from the target property.

    public class StandardValidationRule : ValidationRule
    {
        private bool gettingValue = false;
        private bool isValueReceived = false;
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            if (gettingValue)
            {
                isValueReceived = true;
                return ValidationResult.ValidResult;
            }

            string? valueToValidate = value as string;

            ValidationResult? result = null;
            if (valueToValidate is null && value is not null)
            {
                if (value is BindingExpressionBase bindingExpression)
                {
                    gettingValue = true;
                    isValueReceived = false;
                    DependencyObject target = bindingExpression.Target;
                    var gettingValueExpression = BindingOperations.SetBinding(target, SourceValueProperty, bindingExpression.ParentBindingBase);
                    if (!isValueReceived)
                    {
                        gettingValueExpression.UpdateTarget();
                    }
                    valueToValidate = target.GetValue(SourceValueProperty)?.ToString();
                    target.ClearValue(SourceValueProperty);
                    gettingValue = false;
                }
                else
                {
                    result = unvalid;
                }
            }

            if (result is null)
            {
                result = string.IsNullOrEmpty(valueToValidate)
                            ? unvalid
                            : ValidationResult.ValidResult;
            }
            return result;
        }
        private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");



        // Using a DependencyProperty as the backing store for SourceValue.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SourceValueProperty =
            DependencyProperty.RegisterAttached("SourceValue", typeof(object), typeof(StandardValidationRule), new PropertyMetadata(null));


    }

There is another method for getting the source value.
It is based on using the internal method through reflection.
Which is considered by many to be a «bad» way.
But it works much more efficiently.
And I think it is unlikely that someone will make changes to the internal method, which is already used in many places.

    public static class BindingExpressionHelper
    {
        private static readonly Func<BindingExpressionBase, DependencyObject, DependencyProperty, object> GetValueOfBindingExpression;

        static BindingExpressionHelper()
        {
            Type beType = typeof(BindingExpressionBase);
            var beMethod = beType
                .GetMethod("GetValue", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, new Type[] { typeof(DependencyObject), typeof(DependencyProperty) })
                ?? throw new Exception("GetValue method not found.");
            var beFunc = (Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>)
                beMethod.CreateDelegate(typeof(Func<BindingExpressionBase, DependencyObject, DependencyProperty, object>));
            GetValueOfBindingExpression = beFunc;
        }

        /// <summary>Returns the source value of this binding expression.</summary>
        /// <param name="bindingExpression">The binding expression whose value to get.</param>
        /// <returns>The value of the binding expression received.</returns>
        public static object? GetSourceValue(this BindingExpressionBase bindingExpression)
        {
            DependencyObject target = bindingExpression.Target;
            DependencyProperty targetProperty = bindingExpression.TargetProperty;
            var value = GetValueOfBindingExpression(bindingExpression, target, targetProperty);

            return value;
        }
    }
    public class StandardValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            string? valueToValidate = value as string;

            ValidationResult? result = null;
            if (valueToValidate is null && value is not null)
            {
                if (value is BindingExpressionBase bindingExpression)
                {
                    valueToValidate = bindingExpression.GetSourceValue()?.ToString();
                }
                else
                {
                    result = unvalid;
                }
            }

            if (result is null)
            {
                result = string.IsNullOrEmpty(valueToValidate)
                            ? unvalid
                            : ValidationResult.ValidResult;
            }
            return result;
        }
        private static readonly ValidationResult unvalid = new ValidationResult(false, "Field is mandatory.");
    }
  • Related