Accessing WPF control validation rules from code

前端 未结 3 1668
长情又很酷
长情又很酷 2021-02-01 20:36

XAML:

  
      
          
              
                  <         


        
相关标签:
3条回答
  • 2021-02-01 21:07

    Much thanks to Fredrik Hedblad for his solution. It helped me as well. I also agree with Lukáš Koten that it would best be used as a behavior. That way there's not a mix of application logic mixed in the view layer and the view-model doesn't have to worry about dubplicating validation just to simply have it there. Here's my version via behavior:

    As Fredrik Hedblad stated, first make sure any control validating has binding attribute NotifyOnValidationError="True".

    Here's the view logic... much simpler...

    xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
    

    and then just under the Window start tag

        Height="Auto" Width="Auto">
    <i:Interaction.Behaviors>
        <behavior:ValidationErrorMappingBehavior HasValidationError="{Binding IsInvalid, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
    </i:Interaction.Behaviors
    

    Then for the button, just bind the command like normal. We'll use basic view-model binding principles to disable it using the RelayCommand.

    <Button x:Name="OKButton" Content="OK" Padding="5,0" MinWidth="70" Height="23"
                    HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="5,5,0,0"
                    Command="{Binding OKCommand}"/>
    

    And now the view model with its basic property and command

        private bool _isInvalid = false;
        public bool IsInvalid
        {
            get { return _isInvalid; }
            set { SetProperty<bool>(value, ref _isInvalid); }
        }
    
        private ICommand _okCommand;
        public ICommand OKCommand
        {
            get
            {
                if (_okCommand == null)
                {
                    _okCommand = new RelayCommand(param => OnOK(), canparam => CanOK());
                }
    
                return _okCommand;
            }
        }
    
        private void OnOK()
        {
            //  this.IsInvalid = false, so we're good... let's just close
            OnCloseRequested();
        }
    
        private bool CanOK()
        {
            return !this.IsInvalid;
        }
    

    And now, the behavior

    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    
    namespace UI.Behavior
    {
    public class ValidationErrorMappingBehavior : Behavior<Window>
    {
        #region Properties
    
        public static readonly DependencyProperty ValidationErrorsProperty = DependencyProperty.Register("ValidationErrors", typeof(ObservableCollection<ValidationError>), typeof(ValidationErrorMappingBehavior), new PropertyMetadata(new ObservableCollection<ValidationError>()));
    
        public ObservableCollection<ValidationError> ValidationErrors
        {
            get { return (ObservableCollection<ValidationError>)this.GetValue(ValidationErrorsProperty); }
            set { this.SetValue(ValidationErrorsProperty, value); }
        }
    
        public static readonly DependencyProperty HasValidationErrorProperty = DependencyProperty.Register("HasValidationError", typeof(bool), typeof(ValidationErrorMappingBehavior), new PropertyMetadata(false));
    
        public bool HasValidationError
        {
            get { return (bool)this.GetValue(HasValidationErrorProperty); }
            set { this.SetValue(HasValidationErrorProperty, value); }
        }
    
        #endregion
    
        #region Constructors
    
        public ValidationErrorMappingBehavior()
            : base()
        { }
    
        #endregion
    
        #region Events & Event Methods
    
        private void Validation_Error(object sender, ValidationErrorEventArgs e)
        {
            if (e.Action == ValidationErrorEventAction.Added)
            {
                this.ValidationErrors.Add(e.Error);
            }
            else
            {
                this.ValidationErrors.Remove(e.Error);
            }
    
            this.HasValidationError = this.ValidationErrors.Count > 0;
        }
    
        #endregion
    
        #region Support Methods
    
        protected override void OnAttached()
        {
            base.OnAttached();
            Validation.AddErrorHandler(this.AssociatedObject, Validation_Error);
        }
    
        protected override void OnDetaching()
        {
            base.OnDetaching();
            Validation.RemoveErrorHandler(this.AssociatedObject, Validation_Error);
        }
    
        #endregion
      }
    }
    
    0 讨论(0)
  • 2021-02-01 21:20

    you need to get the Binding first before you get the rules

        Binding b=  BindingOperations.GetBinding(textboxMin,TextBox.TextProperty);
        b.ValidationRules
    

    else you can have BindingExpression and check for HasError property

     BindingExpression be1 = BindingOperations.GetBindingExpression (textboxMin,TextBox.TextProperty);
    
    be1.HasError
    
    0 讨论(0)
  • 2021-02-01 21:21

    Validation.HasError is an attached property so you can check it for textboxMin like this

    void buttonOK_Click(object sender, RoutedEventArgs e)
    {
        if (Validation.GetHasError(textboxMin) == true)
             return;
    }
    

    To run all ValidationRules for the TextProperty in code behind you can get the BindingExpression and call UpdateSource

    BindingExpression be = textboxMin.GetBindingExpression(TextBox.TextProperty);
    be.UpdateSource();
    

    Update

    It will take some steps to achieve the binding to disable the button if any validation occurs.

    First, make sure all bindings add NotifyOnValidationError="True". Example

    <TextBox Name="textboxMin">
        <TextBox.Text>
            <Binding Path="Max" NotifyOnValidationError="True">
                <Binding.ValidationRules>
                    <local:IntValidator/>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
    

    Then we hook up an EventHandler to the Validation.Error event in the Window.

    <Window ...
            Validation.Error="Window_Error">
    

    And in code behind we add and remove the validation errors in an observablecollection as they come and go

    public ObservableCollection<ValidationError> ValidationErrors { get; private set; } 
    private void Window_Error(object sender, ValidationErrorEventArgs e)
    {
        if (e.Action == ValidationErrorEventAction.Added)
        {
            ValidationErrors.Add(e.Error);
        }
        else
        {
            ValidationErrors.Remove(e.Error);
        }
    }
    

    And then we can bind IsEnabled of the Button to ValidationErrors.Count like this

    <Button ...>
        <Button.Style>
            <Style TargetType="Button">
                <Setter Property="IsEnabled" Value="False"/>
                <Style.Triggers>
                    <DataTrigger Binding="{Binding ValidationErrors.Count}" Value="0">
                        <Setter Property="IsEnabled" Value="True"/>
                    </DataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>
    </Button>
    
    0 讨论(0)
提交回复
热议问题