我使用WPF数据绑定实现 IDataErrorInfo 接口的实体。一般来说,我的代码如下所示:
商业实体:
public class Person : IDataErrorInfo
{
public string Name { get; set;}
string IDataErrorInfo.this[string columnName]
{
if (columnName=="Name" && string.IsNullOrEmpty(Name))
return "Name is not entered";
return string.Empty;
}
}
Xaml文件:
<TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=true}" />
当用户点击“创建新人”后执行以下代码:
DataContext = new Person();
问题是,当刚创建人时,其名称为空,WPF 立即绘制红框并显示错误消息。我希望它仅在名称已编辑且焦点丢失时显示错误。有人知道这样做的方法吗?
答案 0 :(得分:16)
只有在更改了Name属性时,才能将您的person类更改为触发验证错误:
public class Person : IDataErrorInfo {
private bool nameChanged = false;
private string name;
public string Name {
get { return name; }
set {
name = value;
nameChanged = true;
}
}
//... skipped some code
string IDataErrorInfo.this[string columnName] {
get {
if(nameChanged && columnName == "Name" && string.IsNullOrEmpty(Name))
return "Name is not entered";
return string.Empty;
}
}
}
答案 1 :(得分:4)
我找到了另一种解决方案,但我不喜欢它。您必须清除页面加载验证。
我的意思是你必须这样做:
Validation.ClearInvalid(...)
例如,如果您有一个不想要验证的文本框,请致电
Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty))
或类似的东西。
您应该为要清除验证的每个控件执行此操作。
我不喜欢这个解决方案,但那是我找到的最好的解决方案。我希望wpf有一些“开箱即用”的东西有效,但没有找到它。
答案 2 :(得分:4)
我认为@Stanislav Kniazev的方法是正确的。您关于不向业务对象添加逻辑的注释也是有效的。要清楚地分离关注点,如何将Person保留在业务层(或数据模型层)中,并引入具有视图逻辑的新类PersonVm。对于VM层,我更喜欢包含模式而不是继承,在这一层我也实现了INotifyPropertyChanged,它也是VM的属性,而不是数据模型。
public class PersonVm : IDataErrorInfo, INotifyPropertyChanged
{
private Person _person;
public PersonVm( ) {
// default constructor
_person = new Person( );
_dirty = false;
}
public PersonVm( Person p ) {
// User this constructor when you get a Person from database or network
_person = p;
_dirty = false;
}
void fire( string prop ) {
PropertyChanged( this, new PropertyChangedEventArgs( prop ) );
}
public string name {
get { return _person.name; }
set { _person.name = value; fire( "name" ); dirty = true; }
}
...
string IDataErrorInfo.this[string columnName] {
get {
if( dirty ) return _person[columnName];
}
}
}
我们的想法是将每一层的逻辑放在适当的类中。在数据模型层,您只进行与纯数据相关的验证。在View Model层中,您可以添加与View Model相关的逻辑(以及notificaton和其他View Model逻辑)。
答案 3 :(得分:3)
该死的这需要一段时间来思考,但一如既往地......附加行为来拯救。
你所看到的实质上是脏状态跟踪。使用ViewModel有很多方法可以做到这一点,但由于您不想更改实体,最好的方法是使用行为。
首先从Xaml绑定中删除ValidatesOnDataErrors。为您正在处理的控件创建行为(如下所示TextBox
)和TextChanged
事件(或任何您想要的事件)将绑定重置为所做的> 验证数据错误。很简单。
通过这种方式,您的实体无需更改,您的Xaml保持相当干净,并且您可以获得行为。
这是行为代码 -
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace IDataErrorInfoSample
{
public static class DirtyStateBehaviours
{
public static string GetDirtyBindingProperty(DependencyObject obj)
{
return (string)obj.GetValue(DirtyBindingPropertyProperty);
}
public static void SetDirtyBindingProperty(DependencyObject obj, string value)
{
obj.SetValue(DirtyBindingPropertyProperty, value);
}
// Using a DependencyProperty as the backing store for DirtyBindingProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DirtyBindingPropertyProperty =
DependencyProperty.RegisterAttached("DirtyBindingProperty", typeof(string), typeof(DirtyStateBehaviours),
new PropertyMetadata(new PropertyChangedCallback(Callback)));
public static void Callback(DependencyObject obj,
DependencyPropertyChangedEventArgs args)
{
var textbox = obj as TextBox;
textbox.TextChanged += (o, s) =>
{
Binding b = new Binding(GetDirtyBindingProperty(textbox));
b.ValidatesOnDataErrors = true;
textbox.SetBinding(TextBox.TextProperty, b);
};
}
}
}
Xaml也非常直接。
<Window x:Class="IDataErrorInfoSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:IDataErrorInfoSample"
xmlns:sys="clr-namespace:System;assembly=mscorlib"
Title="MainWindow"
Height="350"
Width="525">
<Window.DataContext>
<local:Person />
</Window.DataContext>
<StackPanel Margin="20">
<TextBox Height="20"
Margin="0,0,0,10"
local:DirtyStateBehaviours.DirtyBindingProperty="Name"
Text="{Binding Path=Name}">
</TextBox>
<Button Content="Go" />
</StackPanel>
HTH,Stimul8d。
答案 4 :(得分:3)
也许你可以选择将验证转移到View: 您可以在绑定中启用NotifyOnValidationError,并添加执行检查的ValidationRule,而不是实现IDataErrorInfo。 对于ValidationRules,有standard way to control, if the rule should be applied when the object changes(不是直接属性值)
这已经为用户提供了视觉反馈(将应用ErrorTemplate)。 如果您需要更多,例如禁用某些按钮等,您可以将视图的Validation.Error-Event连接到ViewModel或BusinessEntity,s。如果有任何错误,你可以在那里识别。
答案 5 :(得分:2)
我已实施以下解决方案:
public class SkipValidationOnFirstLoadBehavior : Behavior<TextBox>
{
protected override void OnAttached()
{
AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
}
private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
{
//Execute only once
AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;
//Get the current binding
BindingExpression expression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
if (expression == null) return;
Binding parentBinding = expression.ParentBinding;
//Create a new one and trigger the validation
Binding updated = new Binding(parentBinding.Path.Path);
updated.ValidatesOnDataErrors = true;
updated.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
AssociatedObject.SetBinding(TextBox.TextProperty, updated);
}
}
使用示例:
<TextBox Text="{Binding Email}">
<i:Interaction.Behaviors>
<local:SkipValidationOnFirstLoadBehavior/>
</i:Interaction.Behaviors>
</TextBox>
答案 6 :(得分:1)
我只是一名初级开发人员,他的知识不多,但我这样解决了。
在我的validationresult类中,我创建了一个没有参数的构造函数来返回有效的validationresult。
public class NotEmptyValidation : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (string.IsNullOrEmpty(value as string))
{
return new ValidationResult(false,"Veld kan niet leeg zijn");
}
return new ValidationResult(true,null);
}
public NotEmptyValidation() : base()
{
Validate();
}
public ValidationResult Validate()
{
return new ValidationResult(true,null);
}
}
我的xaml代码看起来像这样
<!--TEXTBOXES-->
<TextBox Grid.Column="1" Grid.ColumnSpan="2" Margin="5">
<TextBox.Text>
<Binding Path="SelectedEntity.Code" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<val:NotEmptyValidation />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Margin="5">
<TextBox.Text>
<Binding Path="SelectedEntity.Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<val:NotEmptyValidation />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
当我的表单加载时,验证在窗口加载时不会触发,但如果我清除文本框,则会触发。
这有一个缺点,如果我加载一个无效的实体,它有一个emty名称或代码,验证不会在加载窗口时触发,但是当你填写文本框并清除它时它会这样做。 但是,由于我在创建实体时验证了所有字段,所以这并不会发生。
它不是一个完美的解决方案,但它对我有用。
答案 7 :(得分:0)
我相信这种行为也是很好的解决方案。必要时,它会删除TextBox上的ErrorTemplate,并且还支持多个“有效”无效值(您也可以通过将ValidInputs设置为依赖项属性来改进它)。
public class NotValidateWhenSpecified : Behavior<TextBox>
{
private ControlTemplate _errorTemplate;
public string[] ValidInputs { get; set; } = { string.Empty };
protected override void OnAttached()
{
AssociatedObject.TextChanged += HideValiationIfNecessary;
_errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
Validation.SetErrorTemplate(AssociatedObject, null);
}
protected override void OnDetaching()
{
AssociatedObject.TextChanged -= HideValiationIfNecessary;
}
private void HideValiationIfNecessary(object sender, TextChangedEventArgs e)
{
if (ValidInputs.Contains(AssociatedObject.Text))
{
if (_errorTemplate != null)
{
_errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
Validation.SetErrorTemplate(AssociatedObject, null);
}
}
else
{
if (Validation.GetErrorTemplate(AssociatedObject) != _errorTemplate)
{
Validation.SetErrorTemplate(AssociatedObject, _errorTemplate);
}
}
}
}