WPF ValidationRule предотвращает установку последнего значения
Я использую правило проверки в 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>
Правило проверки
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 ответ
Мне удалось это решить! Я нашел подсказку от Джоша, которая привлекла мое внимание в правильном направлении.
Используя конвертер можно установить 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>
ИМО, это отличное решение, о котором стоит помнить.
Pros
- Проверка выполняется на виртуальной машине с использованием INotifyDataErrorInfo
- Элементы представления могут связываться непосредственно с INOtifyDataErrorInfo HasError.
- Поддержка нескольких ValdiationResult (сбоев) / свойство.
- Поддерживает перекрестную проверку свойств.
- Проверка может быть выполнена с использованием ValidationRule для RawProposedValue (строка), нет необходимости добавлять дополнительный слой строк в виртуальной машине.
- Когда нет необходимости выполнять проверку в RawProposedValue, можно выполнить проверку в установщике свойств в ViewModel.
- Последний пункт подразумевает, что проверка может быть выполнена до того, как автоматическое преобразование (в данном случае из строки в число с плавающей запятой) завершится неудачей с исключением, перехваченным механизмом привязки WPF, что обычно препятствует тому, чтобы проверка не выполнялась, и предотвращает обновление элементов, привязывающихся к HasError. их состояние.
- Неверное значение (в данном случае строка) не будет перезаписано в представлении об ошибке проверки.