我陷入了一个看似普遍的要求。我有一个WPF Prism(用于MVVM)应用程序。我的模型实现 IDataErrorInfo 进行验证。 IDataErrorInfo 非常适合非数字属性。但是,对于数字属性,如果用户输入无效字符(非数字),则数据甚至不会到达模型,因为wpf无法将其转换为数字类型。
因此,我必须使用WPF ValidationRule为用户提供一些有意义的无效数字条目消息。视图中的所有按钮都绑定到棱镜的DelegateCommand(在视图模型中),按钮的启用/禁用在视图模型本身中完成。
现在,如果某个TextBox的wpf ValidationRule失败,如何将此信息传递给View Model,以便它可以适当地禁用视图中的按钮?
答案 0 :(得分:10)
对于MVVM,我更喜欢将附加属性用于此类事物,因为它们是可重用的,它可以保持视图模型的清洁。
为了将Validation.HasError属性绑定到视图模型,您必须创建一个附加属性,该属性具有CoerceValueCallback,该属性将附加属性的值与您正在验证用户输入的控件上的Validation.HasError属性同步上。
This文章解释了如何使用此技术来解决通知WPF ValidationRule错误的视图模型的问题。代码在VB中,所以如果你不是VB的话,我把它移植到C#。
附加财产
public static class ValidationBehavior
{
#region Attached Properties
public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached(
"HasError",
typeof(bool),
typeof(ValidationBehavior),
new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceHasError));
private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached(
"HasErrorDescriptor",
typeof(DependencyPropertyDescriptor),
typeof(ValidationBehavior));
#endregion
private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
{
return (DependencyPropertyDescriptor)d.GetValue(HasErrorDescriptorProperty);
}
private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
{
d.SetValue(HasErrorDescriptorProperty, value);
}
#region Attached Property Getters and setters
public static bool GetHasError(DependencyObject d)
{
return (bool)d.GetValue(HasErrorProperty);
}
public static void SetHasError(DependencyObject d, bool value)
{
d.SetValue(HasErrorProperty, value);
}
#endregion
#region CallBacks
private static object CoerceHasError(DependencyObject d, object baseValue)
{
var result = (bool)baseValue;
if (BindingOperations.IsDataBound(d, HasErrorProperty))
{
if (GetHasErrorDescriptor(d) == null)
{
var desc = DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.Validation.HasErrorProperty, d.GetType());
desc.AddValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, desc);
result = System.Windows.Controls.Validation.GetHasError(d);
}
}
else
{
if (GetHasErrorDescriptor(d) != null)
{
var desc = GetHasErrorDescriptor(d);
desc.RemoveValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, null);
}
}
return result;
}
private static void OnHasErrorChanged(object sender, EventArgs e)
{
var d = sender as DependencyObject;
if (d != null)
{
d.SetValue(HasErrorProperty, d.GetValue(System.Windows.Controls.Validation.HasErrorProperty));
}
}
#endregion
}
在XAML中使用附加属性
<Window
x:Class="MySolution.MyProject.MainWindow"
xmlns:v="clr-namespace:MyNamespace;assembly=MyAssembly">
<TextBox
v:ValidationBehavior.HasError="{Binding MyPropertyOnMyViewModel}">
<TextBox.Text>
<Binding
Path="ValidationText"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<v:SomeValidationRuleInMyNamespace/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</ Window >
现在,视图模型上的属性将与文本框上的Validation.HasError同步。
答案 1 :(得分:6)
Nirvan
解决此特定问题的最简单方法是使用数字文本框,这可以防止用户输入无效值(您可以通过第三方供应商执行此操作,也可以查找打开源解决方案,例如从Textbox派生的类,它禁止非数字输入。)
在没有执行上述操作的情况下在MVVM中处理此问题的第二种方法是在ViewModel中定义另一个字段,该字段是字符串,并将该字段绑定到文本框。然后,在字符串字段的setter中,您可以设置Integer,并为数字字段赋值:
这是一个粗略的例子:(注意我没有测试它,但它应该给你的想法)
// original field
private int _age;
int Age
{
get { return _age; }
set {
_age = value;
RaisePropertyChanged("Age");
}
}
private string _ageStr;
string AgeStr
{
get { return _ageStr; }
set {
_ageStr = value;
RaisePropertyChanged("AgeStr");
if (!String.IsNullOrEmpty(AgeStr) && IsNumeric(AgeStr) )
Age = intVal;
}
}
private bool IsNumeric(string numStr)
{
int intVal;
return int.TryParse(AgeStr, out intVal);
}
#region IDataErrorInfo Members
public string this[string columnName]
{
get
{
if (columnName == "AgeStr" && !IsNumeric(AgeStr)
return "Age must be numeric";
}
}
#endregion
答案 2 :(得分:4)
自.NET 4.5起,ValidationRule具有Validate方法的重载:
public ValidationResult Validate(object value, CultureInfo cultureInfo,
BindingExpressionBase owner)
您可以覆盖它并以这种方式获取视图模型:
public override ValidationResult Validate(object value,
CultureInfo cultureInfo, BindingExpressionBase owner)
{
ValidationResult result = base.Validate(value, cultureInfo, owner);
var vm = (YourViewModel)((BindingExpression)owner).DataItem;
// ...
return result;
}
答案 3 :(得分:1)
您必须根据绑定类型属性指定用户控件。 例如,如果您的属性是int类型,则必须放置不允许除intenger类型之外的不同值的控件。
您可以在PreviewTextInput中输入的逻辑=&#34; NumberValidationTextBox&#34;。
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^0-9]+");
e.Handled = regex.IsMatch(e.Text);
}
只需插入逻辑或放置客户控制权即可完成。
也必须实施mvvm验证。
答案 4 :(得分:1)
根据绑定属性的逻辑,在模型或Viewmodel中实现IDataErrorInfo
。您可以在两个类中实现。
在基础验证课程中也实现此功能。此处验证将在绑定IDataErrorInfo
不起作用时触发。
public virtual bool HasError
{
get { return _hasError; }
set
{
// if (value.Equals(_hasError)) return;
_hasError = value;
RaisePropertyChanged(() => HasError);
}
}
接下来,添加全局类
public class ProtocolSettingsLayout
{
public static readonly DependencyProperty MVVMHasErrorProperty = DependencyProperty.RegisterAttached("MVVMHasError", typeof(bool), typeof(ProtocolSettingsLayout), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceMVVMHasError));
public static bool GetMVVMHasError(DependencyObject d)
{
return (bool)d.GetValue(MVVMHasErrorProperty);
}
public static void SetMVVMHasError(DependencyObject d, bool value)
{
d.SetValue(MVVMHasErrorProperty, value);
}
private static object CoerceMVVMHasError(DependencyObject d, Object baseValue)
{
bool ret = (bool)baseValue;
if (BindingOperations.IsDataBound(d, MVVMHasErrorProperty))
{
if (GetHasErrorDescriptor(d) == null)
{
DependencyPropertyDescriptor desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType());
desc.AddValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, desc);
ret = System.Windows.Controls.Validation.GetHasError(d);
}
}
else
{
if (GetHasErrorDescriptor(d) != null)
{
DependencyPropertyDescriptor desc = GetHasErrorDescriptor(d);
desc.RemoveValueChanged(d, OnHasErrorChanged);
SetHasErrorDescriptor(d, null);
}
}
return ret;
}
private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor",
typeof(DependencyPropertyDescriptor),
typeof(ProtocolSettingsLayout));
private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
{
var ret = d.GetValue(HasErrorDescriptorProperty);
return ret as DependencyPropertyDescriptor;
}
private static void OnHasErrorChanged(object sender, EventArgs e)
{
DependencyObject d = sender as DependencyObject;
if (d != null)
{
d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty));
}
}
private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
{
var ret = d.GetValue(HasErrorDescriptorProperty);
d.SetValue(HasErrorDescriptorProperty, value);
}
}
xaml
<TextBox PreviewTextInput="NumValidationTextBox" Text="{Binding ESec, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=true, NotifyOnValidationError=true, ValidatesOnExceptions=True, NotifyOnSourceUpdated=True, NotifyOnTargetUpdated=True, TargetNullValue='0', FallbackValue='0' }" Validation.ErrorTemplate="{StaticResource validationTemplate}" viewmodels:ProtocolSettingsLayout.MVVMHasError="{Binding Path=HasError}" />
答案 5 :(得分:1)
我遇到了同样的问题,但是我以另一种方式解决了,当输入无效时,我使用触发器来禁用按钮。同时,文本框绑定应使用ValidatesOnExceptions=true
<Style TargetType="{x:Type Button}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=tbInput1, Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=tbInput2, Path=(Validation.HasError)}" Value="True">
<Setter Property="IsEnabled" Value="False"></Setter>
</DataTrigger>
</Style.Triggers>
答案 6 :(得分:0)
如果您提供自定义ValidationRule
实施,您可以存储收到的值,以及存储最后的结果。伪代码:
public class IsInteger : ValidationRule
{
private int parsedValue;
public IsInteger() { }
public string LastValue{ get; private set; }
public bool LastParseSuccesfull{ get; private set; }
public int ParsedValue{ get{ return parsedValue; } }
public override ValidationResult Validate( object value, CultureInfo cultureInfo )
{
LastValue = (string) value;
LastParseSuccesfull = Int32.TryParse( LastValue, cultureInfo, ref parsedValue );
return new ValidationResult( LastParseSuccesfull, LastParseSuccesfull ? "not a valid number" : null );
}
}
答案 7 :(得分:0)
有人通过在VM中创建依赖属性HasError来解决它(不幸的是它在VB中),它似乎绑定到Validation.HasError。我还没有完全理解它,但它可以帮到你:
http://wpfglue.wordpress.com/2009/12/03/forwarding-the-result-of-wpf-validation-in-mvvm/
答案 8 :(得分:0)
我遇到了同样的问题并用技巧解决了它。 请参阅下面的转换器:
public class IntValidationConverter : IValueConverter
{
static string[] AllValuse = new string[100000];
static int index = 1;
public static int StartOfErrorCodeIndex = -2000000000;
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return null;
if (value.ToString() == "") return null;
int iValue = (int)(value);
if (iValue == int.MinValue) return null;
if (iValue >= StartOfErrorCodeIndex) return value;
if ((iValue < IntValidationConverter.StartOfErrorCodeIndex) && (iValue > int.MinValue)) return AllValuse[StartOfErrorCodeIndex - iValue];
return null;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value == null) return int.MinValue;
if (value.ToString() == "") return int.MinValue;
int result;
bool success = int.TryParse(value.ToString(), out result);
if (success) return result;
index++;
AllValuse[index] = value.ToString();
return StartOfErrorCodeIndex - index;
}
}