我在TextBox上使用验证规则来验证用户输入字符串。 文本绑定到视图模型上的float属性,WPF绑定引擎非常热衷,可以自动将字符串转换为float。
但是,当验证失败时,绑定似乎会回读旧值。这导致即使文本已恢复为最后一个可接受的浮点值,我在文本框周围仍显示红色边框。
问题:如何确保验证失败时绑定引擎不会自动覆盖错误的输入文本? 绑定需要双向。
我应该提到,我在ValidationRule中做了一些小技巧,让我从视图模型定位器中查找当前视图模型,并在视图模型上使用INotifyDataErrorInfo方法。我发现这是一个很好的解决方案,因为这意味着ViewModel HasError将为我收集所有验证错误(并且在设置属性时让我在验证规则或视图模型中应用验证)。在视图模型上使用INotifyDataErrorInfo进行验证的规则是,验证可以在从字符串到浮点的自动转换之前应用,即使在用户键入“ Hello World”导致异常时,也要确保执行验证自动转换为浮动期间的绑定引擎)。这使我可以将属性的类型保留在vm上,并仍然执行验证。
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);
}
}
答案 0 :(得分:0)
我设法解决了! 我从乔希(Josh)找到了提示,使我的注意力转向了正确的方向。
使用转换器可以设置Binding.DoNothing。我将其修改为一个转换器,用于检查VM上的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这是一个非常值得记住的伟大解决方案。
专业人士