我被困在一个看似普通的要求。 我有一个WPF棱镜(用于MVVM)应用程序。 我的模型实现了IDataErrorInfo的进行验证。 该IDataErrorInfo的非数值属性的伟大工程。 然而,对于数值属性,如果用户输入无效字符(其不是数字),则数据甚至不到达模型,因为WPF不能将其转换到数字类型。
所以,我不得不使用WPF有效性规则为无效的数字输入的用户提供一些有意义的信息。 在视图中所有的按键绑定到棱镜DelegateCommand(在视图模型)和按钮的启用/禁用在视图模型本身完成。
现在,如果一个WPF有效性规则失败一些文本框,我如何将此信息传递给视图模型,以便它可以适当地在视图中禁用按钮?
Answer 1:
NIRVAN
要解决这个特定问题的最简单方法是使用数字文本框 ,它可以防止用户输入无效的值(您可以通过第三方供应商做到这一点,或者找到一个开源的解决方案,如从文本框派生的类抑制非数字输入)。
在MVVM来处理这个而不做上述的第二种方法,是定义在你的ViewModel它是一个字符串的另一个领域,该领域绑定到文本框。 然后,在你串场您可以设置整数,和值分配给您的数字字段的setter方法:
下面是一个粗略的例子:(注意我没有测试它,但它应该给你的想法)
// 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
Answer 2:
对于MVVM我喜欢,因为它们是可重复使用的使用附加属性这种类型的东西,它使视图模型干净。
为了绑定到Validation.HasError财产到您的视图模型,你必须创造出具有同步与您正在验证用户输入控件的属性Validation.HasError的附加属性值的CoerceValueCallback附加属性。
此文章解释了如何使用这种技术来解决,通知WPF有效性规则的错误的视图模型的问题。 该代码是在VB,所以我把它移植到C#,如果你不是一个VB的人。
附加属性
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你的文本同步。
Answer 3:
由于.NET 4.5,有效性规则具有验证方法的重载:
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;
}
Answer 4:
您必须指定具体取决于绑定类型属性custome用户控制。 例如,如果你的财产为int类型,你必须把控制是不允许除intenger型diferent值。
逻辑你可以将在PreviewTextInput =“NumberValidationTextBox”。
private void NumberValidationTextBox(object sender, TextCompositionEventArgs e)
{
Regex regex = new Regex("[^0-9]+");
e.Handled = regex.IsMatch(e.Text);
}
只要插入你的逻辑或地方custome控制和你做。
Defently必须得实现MVVM验证。
Answer 5:
实现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}" />
Answer 6:
我和你同样的问题,但我解决以另一种方式,我用触发器禁用按钮时输入无效。 同时,结合文本应使用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>
Answer 7:
如果你提供一个自定义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 );
}
}
Answer 8:
有人从创建VM中的依赖属性HasError这似乎是必然的Validation.HasError这里(不幸的是它在VB)解决了这个问题。 我不完全理解这些事,但它可以帮助你:
http://wpfglue.wordpress.com/2009/12/03/forwarding-the-result-of-wpf-validation-in-mvvm/
Answer 9:
我遇到同样的问题,一招解决它。 请参见下面的转换:
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;
}
}
文章来源: Passing state of WPF ValidationRule to View Model in MVVM