Правило проверки не обновляется корректно с двумя правилами проверки

Я просмотрел несколько постов, касающихся Правил валидации, но не столкнулся с проблемой, с которой столкнулся.

Я использую правило проверки для текстового поля в окне WPF. У меня есть две проверки, одна для пустого текстового поля, а другая для недопустимых символов с использованием соответствия RegEx.

Моя проблема такая:

В моем текстовом поле:

Тип А - Работает, Отображает ничтоНажмите Backspace для пустой строки - Работает, отображает сообщение об ошибке проверки «Пожалуйста, введите значение во все поля»Тип ! - Не работает - Должно отображаться «Недопустимые символы были найдены», но по-прежнему отображается «Пожалуйста, введите значение во всех полях».Возврат на пустую строку - технически работает, потому что все равно отображается первая ошибка «Пожалуйста, введите значение во все поля».Тип А - Работает, без сообщения об ошибкеТип ! - Отлично, выводит сообщение «Недопустимые символы были найдены.

То же самое происходит и наоборот.

Открытое окно

Тип ! - Отлично, отображает «Недопустимые символы были найдены.Возврат в пустую строку - по-прежнему отображается «Недопустимые символы были найдены» вместо «Пожалуйста, введите значение во всех полях.

Мой код выглядит следующим образом:

ProviderNew.xaml:

<Label Name="lblProviderName" Content="Provider Name: " 
                   Grid.Row="1" Grid.Column="0"/>

<TextBox Name="txtBoxProviderName" Margin="2" 
    MaxLength="20" CharacterCasing="Upper"
    Grid.Row="1" Grid.Column="1" LostFocus="TxtBoxProviderNameLostFocus"   TextChanged="TxtBoxProviderNameTextChanged">
                <TextBox.Text>
                    <Binding ElementName="This" Path="ProviderName" UpdateSourceTrigger="PropertyChanged">
                        <Binding.ValidationRules>
                            <Domain:ProviderValidation />
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>

            <TextBlock x:Name="tbNameValidation"  Foreground="Red" FontWeight="Bold" Margin="10,0,0,0"
                       Text="{Binding ElementName=txtBoxProviderName, Path=(Validation.Errors), Converter={StaticResource eToMConverter}}"
                       Grid.Row="1" Grid.Column="2" />

Код привилегии позади - ProviderNew.xaml.cs

public static readonly DependencyProperty NewProviderNameProperty = DependencyProperty.Register("ProviderName",
        typeof (string), typeof(ProviderNew), new UIPropertyMetadata(""));


    public string ProviderName
    {
        get { return (string) GetValue(NewProviderNameProperty); }
        set { SetValue(NewProviderNameProperty, value); }
    }

Класс преобразователя значений

public class ErrorsToMessageConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        var sb = new StringBuilder();
        var errors = value as ReadOnlyCollection<ValidationError>;

        if (errors != null && errors.Count > 0)
        {

            foreach (var e in errors.Where(e => e.ErrorContent != null))
            {
                sb.AppendLine(e.ErrorContent.ToString());
            }
        }
        return sb.ToString();
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Класс провайдера

private string _providerName;
    public string ProviderName
    {
        get { return _providerName; }
        set
        {
            _providerName = value;
            RaisePropertyChanged("ProviderName");
        }
    }

private void RaisePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = this.PropertyChanged;

        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

Класс Правил валидации

public class ProviderValidation : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        var str = value as string;

        if (str != null && !Regex.IsMatch(str, @"^[a-zA-Z0-9]*$"))
        {
           return new ValidationResult(false, "Invalid characters were found.");
        }


        if (String.IsNullOrEmpty(str))
        {
            return new ValidationResult(false, "Please enter a value in all fields.");
        }

        return new ValidationResult(true, null);
    }
}

Я попытался установить события LostFocus и TextChanged для принудительного обновления, используя:

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
    expression.UpdateSource();

Установка точек останова в методе Validate показывает, что правильные совпадения выполнены и возвращается правильный ValidationResult, но он не обновляет текст корректно.

Я делаю что-то невероятно глупое?

Любые предложения будут оценены.

Редактировать 1.

Да, у меня это работает, используя MultiDataTrigger и Binding к текстовым полям.

Что не работает, так это когда я впервые показываю Окно, кнопка включена, чего я не хочу, потому что это может позволить пользователю нажать кнопку «Сохранить» с пустыми текстовыми полями.

Правило валидации не работает сразу с самого начала, когда открывается окно.

Я установил фокус на текстовое поле, и если оно теряет фокус или вводятся неверные данные, то запускается правило проверки и отключается кнопка.

Если кнопка по умолчанию отключена, она отключается при открытии, но затем не включается при отсутствии ошибок проверки.

Я могу заставить его работать, принудительно проверив правило валидации, скажем, на событии Load, используя

var expression = txtBoxProviderName.GetBindingExpression(TextBox.TextProperty);
if (expression != null)
    expression.UpdateSource();

Но затем, когда впервые открывается окно, оно сразу показывает сообщение об ошибке проверки «Пожалуйста, введите значение во все поля», которое мне не очень нравится.

В любом случае, или я не могу иметь лучшее из обоих миров.

Вот код кнопки

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
            Grid.Row="2" Click="BtnSaveClick" >
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="IsEnabled" Value="False" />
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="True" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>     
    </Button>

Спасибо,

Neill

Редактировать 2

Быстрый вопрос Я не смог найти пространство имен RelayCommand. В поисках другого кода я нашел пример MVVM от Microsoft, который реализует класс RelayCommand: ICommand.

Это верно?

Код является:

public class RelayCommand : ICommand
{
    #region Fields

    readonly Action<object> _execute;
    readonly Predicate<object> _canExecute;

    #endregion // Fields

    #region Constructors

    /// <summary>
    /// Creates a new command that can always execute.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    public RelayCommand(Action<object> execute)
        : this(execute, null)
    {
    }

    /// <summary>
    /// Creates a new command.
    /// </summary>
    /// <param name="execute">The execution logic.</param>
    /// <param name="canExecute">The execution status logic.</param>
    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
            throw new ArgumentNullException("execute");

        _execute = execute;
        _canExecute = canExecute;
    }

    #endregion // Constructors

    #region ICommand Members

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return _canExecute == null ? true : _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        _execute(parameter);
    }

    #endregion // ICommand Members
}

Я реализовал следующее в моем ProviderNew.xaml.cs:

private ICommand saveCommand;
    public ICommand SaveCommand
    {
        get
        {
            if (saveCommand == null)
            {
                saveCommand = new RelayCommand(p => DoSave(), p => CanSave() );
            }
            return saveCommand;
        }
    }

    private bool CanSave()
    {
        if (!string.IsNullOrEmpty(ProviderName))
        {
            ??? What goes here? 
        }


        return true;
    }
private bool DoSave()
    {
        // Save the information to DB
    }

Честно говоря, я не уверен, что должно быть закодировано в блоке в 'if (! String.IsNullOrEmpty (ProviderName))'

Также вы сказали добавить код ICommand в DataContext, поэтому не уверены, что он находится в нужном месте, потому что когда я открываю окно, сохранение активируется, и нажатие на него ничего не дает, даже если во всех полях содержатся правильные данные.

Вот код ProviderNew xaml для кнопки

<Button x:Name="btnSave" Height="25" Width="100" Content="Save" HorizontalAlignment="Right" Margin="0,0,120,0"
            Grid.Row="2" Command="{Binding SaveCommand}" >
        <Button.Style>
            <Style TargetType="{x:Type Button}">
                <Setter Property="IsEnabled" Value="False" />
                <Style.Triggers>
                    <MultiDataTrigger>
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding ElementName=txtBoxHelpDesk, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxProviderName, Path=(Validation.HasError)}" Value="False" />
                            <Condition Binding="{Binding ElementName=txtBoxRechargeInstructions, Path=(Validation.HasError)}" Value="False" />
                        </MultiDataTrigger.Conditions>
                        <Setter Property="IsEnabled" Value="True" />
                    </MultiDataTrigger>
                </Style.Triggers>
            </Style>
        </Button.Style>     
    </Button>

Ваша помощь очень ценится.

С Уважением,

Neill

Ответы на вопрос(1)

Ваш ответ на вопрос