WPF ValidationRule предотвращает установку последнего значения - PullRequest
0 голосов
/ 26 сентября 2018

Я использую правило проверки в TextBox для проверки строки ввода пользователя.Текст привязан к свойству float в модели представления, и механизм привязки WPF достаточно заинтересован, чтобы автоматически преобразовать строку в float для меня.

Однако, когда проверка не удалась, привязка, кажется, считывает обратностарое значениеЭто приводит к тому, что я получаю красную рамку вокруг текстового поля, даже если текст вернулся к последнему приемлемому значению с плавающей запятой.

Вопрос: Как убедиться, что неверный вводимый текст неавтоматически перезаписывается механизмом привязки при сбое проверки?Привязка должна быть двухсторонней.

Следует отметить, что я делаю небольшую хитрость в своем ValidationRule, где я позволяю ему находить текущую модель представления из локатора модели представления и использует подход INotifyDataErrorInfo впосмотреть модель.То, что я нашел отличным решением, так как это означает, что ViewModel HasError будет собирать все ошибки валидации для меня (и это позволит мне применять валидацию в правилах валидации или в модели представления при настройке свойства). Преимущество разрешения валидацииПравило применения проверки с использованием INotifyDataErrorInfo в модели представления заключается в том, что проверку можно применять перед автоматическим преобразованием из строки в число с плавающей точкой, гарантируя, что проверка выполняется даже тогда, когда пользователь вводит «Hello World», что приводит к исключению (поглощенномупривязка двигателя) во время автоматического преобразования в плаваниеЭто позволяет мне сохранять тип свойства с плавающей точкой в ​​виртуальной машине и по-прежнему выполнять проверку.

XAML

<TextBox Grid.Row="2" Grid.Column="2" x:Name="txtPreHeight" 
                         HorizontalAlignment="Stretch" 
                         HorizontalContentAlignment="Stretch"
                         VerticalAlignment="Center" 
                         Template="{DynamicResource TextBoxBaseControlTemplateMainScreen}">
  <TextBox.Text>
                    <Binding 
                     Path="PreHeight"    
                        ValidatesOnExceptions="False"
                     NotifyOnValidationError="True"
                     ValidatesOnNotifyDataErrors="True"  
                     UpdateSourceTrigger="LostFocus" 
                     >
                        <Binding.ValidationRules>
                            <validationrules:PreHeightValidationRule ViewModelType="GotoPositionViewModel" Min="0" Max="100" ValidationStep="RawProposedValue"/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
                <i:Interaction.Triggers>
                    <helper:RoutedEventTrigger RoutedEvent="{x:Static Validation.ErrorEvent}">
                        <cmd:EventToCommand Command="{Binding SetFocusOnValidationErrorCommand}"
                        PassEventArgsToCommand="True" />
                    </helper:RoutedEventTrigger>
                </i:Interaction.Triggers>
            </TextBox>

ValidationRule

class PreHeightValidationRule : ValidationRule
{
    private ValidationService validationService_;

    private Int32 min_ = Int32.MaxValue;
    private Int32 max_ = Int32.MinValue;
    private string viewModelType_ = null;

    public PreHeightValidationRule()
    {
        validationService_ = ServiceLocator.Current.GetInstance<Validation.ValidationService>();
    }

    public Int32 Min
    {
        get { return min_; }
        set { min_ = value; }
    }

    public Int32 Max
    {
        get { return max_; }
        set { max_ = value; }
    }

    public string ViewModelType
    {
        get { return viewModelType_; }
        set { viewModelType_ = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
    {
        ValidationResult result = base.Validate(value, cultureInfo, owner);
        ViewModel.ViewModelBaseWithNavigation vm;
        System.Reflection.Assembly asm = typeof(ViewModelLocator).Assembly;
        Type type = null;

        if (type == null)
            type = asm.GetType(ViewModelType);

        if (type == null)
            type = asm.GetType("TeachpendantControl.ViewModel." + ViewModelType);

        vm = (ViewModel.ViewModelBaseWithNavigation)ServiceLocator.Current.GetInstance(type);
        ICollection<string> validationErrors = new List<string>();

        try
        {
            validationService_.ValidatePreHeight(value.ToString(), ref validationErrors, Min, Max);
        }
        catch (Exception e)
        {
            validationErrors.Add("Failed to validate, Exception thrown " + e.Message);
        }
        finally
        {
            vm.UpdateValidationForProperty(((BindingExpression)owner).ResolvedSourcePropertyName, validationErrors, validationErrors.Count == 0);
        }

        return new ValidationResult(validationErrors.Count == 0, validationErrors);
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        return new ValidationResult(false, null);
    }
}

1 Ответ

0 голосов
/ 26 сентября 2018

Мне удалось это решить!Я нашел подсказку от Джоша, которая привлекла мое внимание в правильном направлении.

Используя конвертер, можно установить Binding.DoNothing.Я изменил его в конвертер, который проверяет HasError на виртуальной машине.В случае HasError я возвращаю Binding.DoNothing, в противном случае я просто пересылаю значение.

using CommonServiceLocator;
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;   

namespace Converters
{
   class HasErrorToBindingDoNothingConverter : DependencyObject, IValueConverter
   {
      public static readonly DependencyProperty ViewModelTypeProperty =
        DependencyProperty.Register("ViewModelType", typeof(string), typeof(HasErrorToBindingDoNothingConverter), new UIPropertyMetadata(""));

    public string ViewModelType
    {
        get { return (string)GetValue(ViewModelTypeProperty); }
        set { SetValue(ViewModelTypeProperty, value); }
    }

    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        try
        {
            ViewModel.ViewModelBaseWithNavigation vm;
            System.Reflection.Assembly asm = typeof(ViewModelLocator).Assembly;
            Type type = null;

            if (type == null)
                type = asm.GetType(ViewModelType);

            if (type == null)
                type = asm.GetType("TeachpendantControl.ViewModel." + ViewModelType);

            vm = (ViewModel.ViewModelBaseWithNavigation)ServiceLocator.Current.GetInstance(type);

            if (vm.HasErrors)
                return Binding.DoNothing;
            else
                return value;
        }
        catch { return value; }
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return value;
    }
}
}

Мне пришлось изменить XAML на этот

  <TextBox.Text>
                    <Binding 
                     Path="PreHeight"    
                     ValidatesOnExceptions="False"
                     NotifyOnValidationError="False"
                     ValidatesOnNotifyDataErrors="False"     
                     UpdateSourceTrigger="PropertyChanged" 
                     Mode="TwoWay"
                     >
                        <Binding.ValidationRules>
                            <validationrules:PreHeightValidationRule ViewModelType="GotoPositionViewModel" Min="0" Max="100" ValidationStep="RawProposedValue"/>
                        </Binding.ValidationRules>
                        <Binding.Converter>
                            <converters:HasErrorToBindingDoNothingConverter ViewModelType="GotoPositionViewModel"/>
                        </Binding.Converter>
                    </Binding>
                </TextBox.Text>

            </TextBox>

IMO, это отличное решение, которое стоит тогоимейте в виду.

Плюсы

  • Валидация выполняется на виртуальной машине с использованием INotifyDataErrorInfo
  • Элементы представления могут напрямую связываться с INotifyDataErrorInfo HasError.
  • Поддержка нескольких ValdiationResult (сбоев) / свойство.
  • Поддерживает перекрестную проверку свойств.
  • Проверка может быть выполнена с использованием ValidationRule для RawProposedValue (строка), нет необходимости добавлять дополнительный слой строк в ВМ.
  • Когданет необходимости выполнять проверку в RawProposedValue, можно проверить в установщике свойств в ViewModel.
  • Последняя точка подразумевает, что проверка может быть выполнена до того, как автоматическое преобразование (в данном случае из строки в число с плавающей запятой) завершится неудачей с исключением, перехваченным механизмом привязки WPF, что обычно препятствует выполнению проверки и предотвращает элементыпривязка к HasError, чтобы не обновлять их состояние.
  • Неверное значение (в данном случае строка) не будет перезаписано в представлении об ошибке проверки.
...