如何通过包装将IDataErrorInfo验证传递给XAML
目前我面临着一个我无法解决的荒谬问题
我写了一个小包装器,它包装了几乎任何属性并添加了一个属性,但我不知道如何将验证通过他传递给我的XAML
这是我的代码
XAML
<TextBox Height="23" HorizontalAlignment="Left" Margin="42,74,0,0" Name="textBox2" VerticalAlignment="Top" Width="120"
DataContext="{Binding TB2}"/>
<!-- this Style is be added to the parent of TextBox -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text" Value="{Binding Value,UpdateSourceTrigger=PropertyChanged,ValidatesOnDataErrors=True}"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsDirty}" Value="true">
<Setter Property="BorderBrush" Value="Orange"/>
</DataTrigger>
</Style.Triggers>
</Style>
视图模型
public class vm : IDataErrorInfo, INotifyPropertyChanged
{
[Required]
[Range(4, 6)]
public string TB1 { get; set; }
[Required]
[Range(4, 6)]
public myWrapper TB2
{
get { return tb2; }
set{
tb2 = value;
OnPropertyChanged("TB2");
}
}
private myWrapper tb2;
public vm()
{
TB1 = "";
tb2 = new myWrapper("T");
}
#region IDataErrorInfo
private Dictionary<string, string> ErrorList = new Dictionary<string, string>();
public string Error { get { return getErrors(); } }
public string this[string propertyName] { get { return OnValidate(propertyName); } }
private string getErrors()
{
string Error = "";
foreach (KeyValuePair<string, string> error in ErrorList)
{
Error += error.Value;
Error += Environment.NewLine;
}
return Error;
}
protected virtual string OnValidate(string propertyName)
{
if (string.IsNullOrEmpty(propertyName))
throw new ArgumentException("Invalid property name", propertyName);
string error = string.Empty;
var value = this.GetType().GetProperty(propertyName).GetValue(this, null);
var results = new List<ValidationResult>(2);
var context = new ValidationContext(this, null, null) { MemberName = propertyName };
var result = Validator.TryValidateProperty(value, context, results);
if (!result)
{
var validationResult = results.First();
error = validationResult.ErrorMessage;
}
if (error.Length > 0)
{
if (!ErrorList.ContainsKey(propertyName))
ErrorList.Add(propertyName, error);
}
else
if (ErrorList.ContainsKey(propertyName))
ErrorList.Remove(propertyName);
return error;
}
#endregion //IDataErrorInfo
#region INotifyPropertyChanged
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
myWrapper
public class myWrapper : INotifyPropertyChanged
{
private object currentValue;
private object currentOriginal;
public object Value
{
get { return currentValue; }
set
{
currentValue = value;
OnPropertyChanged("Value");
OnPropertyChanged("IsDirty");
}
}
public bool IsDirty
{
get { return !currentValue.Equals(currentOriginal); }
}
#region cTor
public myWrapper(object original)
{
currentValue = original;
currentOriginal = original.Copy(); // creates an deep Clone
}
#endregion
#region INotifyPropertyChanged
// Declare the event
public event PropertyChangedEventHandler PropertyChanged;
// Create the OnPropertyChanged method to raise the event
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
#endregion
}
我也在myWrapper中测试了IDataErrorInfo,但没有运气
由于您的TextBox实际绑定到包装器,因此必须将IDataErrorInfo添加到包装器类。 现在的问题是如何连接实际ViewModel和包装器之间的验证逻辑。
正如johndsamuels所说的,你可以像下面这样将一个代理传递给包装器:
#region cTor
private string _propertyName;
private Func<string, string> _validationFunc;
public myWrapper(string propertyName, object original, Func<string, string> validationFunc)
{
_propertyName = propertyName;
_validationFunc = validationFunc;
currentValue = original;
currentOriginal = original.Copy(); // creates an deep Clone
}
#endregion
您还需要传递属性名称,因为实际的ViewModel可能会使用相同的方法验证多个属性。 在您的实际ViewModel中,您将OnValidate方法作为委托传递,那么它将会很好。
现在您将进入验证的困境。 您正在使用数据注释。 例如,RangeAttribute只能验证int,double或string。 由于属性只能在编译时在类型级定义,所以甚至不能动态地将这些属性传递给你的包装器。 您可以编写自定义属性,也可以使用其他验证机制(如企业库验证块)。
希望它可以帮助。
我认为你不需要使用包装来保存状态。 如果你使用某个提供者来保存模型的状态会更好。 例如,我编写了可以保存字典中所有公共属性状态的提供者,然后可以恢复它。
public interface IEntityStateProvider
{
void Save(object entity);
void Restore(object entity);
}
public class EntityStateProvider : IEntityStateProvider
{
#region Nested type: EditObjectSavedState
private class SavedState
{
#region Constructors
public SavedState(PropertyInfo propertyInfo, object value)
{
PropertyInfo = propertyInfo;
Value = value;
}
#endregion
#region Properties
public readonly PropertyInfo PropertyInfo;
public readonly object Value;
#endregion
}
#endregion
#region Fields
private static readonly Dictionary<Type, IList<PropertyInfo>> TypesToProperties =
new Dictionary<Type, IList<PropertyInfo>>();
private readonly Dictionary<object, List<SavedState>> _savedStates = new Dictionary<object, List<SavedState>>();
#endregion
#region Implementation of IEntityStateProvider
public void Save(object entity)
{
var savedStates = new List<SavedState>();
IList<PropertyInfo> propertyInfos = GetProperties(entity);
foreach (PropertyInfo propertyInfo in propertyInfos)
{
object oldState = propertyInfo.GetValue(entity, null);
savedStates.Add(new SavedState(propertyInfo, oldState));
}
_savedStates[entity] = savedStates;
}
public void Restore(object entity)
{
List<SavedState> savedStates;
if (!_savedStates.TryGetValue(entity, out savedStates))
throw new ArgumentException("Before call the Restore method you should call the Save method.");
foreach (SavedState savedState in savedStates)
{
savedState.PropertyInfo.SetValue(entity, savedState.Value, null);
}
_savedStates.Remove(entity);
}
#endregion
#region Methods
private static IList<PropertyInfo> GetProperties(object entity)
{
Type type = entity.GetType();
IList<PropertyInfo> list;
if (!TypesToProperties.TryGetValue(type, out list))
{
list = type.GetProperties()
.Where(info => info.CanRead && info.CanWrite)
.ToArray();
TypesToProperties[type] = list;
}
return list;
}
#endregion
}
现在,您只需在编辑前保存视图模型的状态,然后如果需要,您可以恢复以前的视图模型状态。
public class vm : IDataErrorInfo, INotifyPropertyChanged
{
private readonly IEntityStateProvider _stateProvider;
public vm(IEntityStateProvider stateProvider)
{
_stateProvider = stateProvider;
_stateProvider.Save(this);
}
............
}
这是一个简单的代码示例,您可以根据需要更改此代码。
更新0您可以扩展接口并添加HasChanges方法:
public interface IEntityStateProvider
{
void Save(object entity);
void Restore(object entity);
bool HasChanges(object entity, string property);
}
这里的实现:
public bool HasChanges(object entity, string property)
{
List<SavedState> list;
if (!_savedStates.TryGetValue(entity, out list))
throw new ArgumentException("Before call the HasChanges method you should call the Save method.");
SavedState savedState = list.FirstOrDefault(state => state.PropertyInfo.Name == property);
if (savedState == null)
return false;
object newValue = savedState.PropertyInfo.GetValue(entity);
return !Equals(newValue, savedState.Value);
}
在您的视图模型中,您应该将IDataErrorInfo作为显式实现,并创建新的索引器属性,以负责检查更改。
public class vm : INotifyPropertyChanged, IDataErrorInfo
{
private readonly IEntityStateProvider _stateProvider;
private string _property;
public vm(IEntityStateProvider stateProvider)
{
_stateProvider = stateProvider;
Property = "";
_stateProvider.Save(this);
}
public string Property
{
get { return _property; }
set
{
if (value == _property) return;
_property = value;
OnPropertyChanged("Property");
OnPropertyChanged("Item[]");
}
}
public bool this[string propertyName]
{
get { return _stateProvider.HasChanges(this, propertyName); }
}
#region Implementation of IDataErrorInfo
string IDataErrorInfo.this[string columnName]
{
get
{
//Your logic here
return null;
}
}
string IDataErrorInfo.Error
{
get
{
//Your logic here
return null;
}
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}
}
然后你可以这样写绑定,它会起作用。
<TextBox Height="23" HorizontalAlignment="Left" Margin="42,74,0,0" Name="textBox2" VerticalAlignment="Top"
Width="120">
<TextBox.Resources>
<!-- this Style is be added to the parent of TextBox -->
<Style TargetType="{x:Type TextBox}">
<Setter Property="Text"
Value="{Binding Path=Property, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=[Property], UpdateSourceTrigger=PropertyChanged}" Value="true">
<Setter Property="BorderBrush" Value="Orange" />
<Setter Property="BorderThickness" Value="2" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Resources>
</TextBox>
这只是一个粗略的例子,展示了解决方案的本质,您可以在没有包装属性的情况下完成。
更新1为了避免创建新的样式,你可以像下面这样添加附加属性:
public static class ExtendedProperties
{
public static readonly DependencyProperty IsDirtyProperty =
DependencyProperty.RegisterAttached("IsDirty", typeof(bool), typeof(ExtendedProperties), new PropertyMetadata(default(bool)));
public static void SetIsDirty(UIElement element, bool value)
{
element.SetValue(IsDirtyProperty, value);
}
public static bool GetIsDirty(UIElement element)
{
return (bool)element.GetValue(IsDirtyProperty);
}
}
然后编写这个XAML:
<Window.Resources>
<!-- this Style is be added to the parent of TextBox -->
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="internal:ExtendedProperties.IsDirty" Value="True">
<Setter Property="BorderBrush" Value="Orange" />
<Setter Property="BorderThickness" Value="2" />
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<TextBox Height="23" HorizontalAlignment="Left" Margin="42,74,0,0" Name="textBox2" VerticalAlignment="Top"
Width="120"
Text="{Binding Path=Property, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay, ValidatesOnDataErrors=True}"
internal:ExtendedProperties.IsDirty="{Binding Path=[Property], UpdateSourceTrigger=PropertyChanged}" />
链接地址: http://www.djcxy.com/p/71995.html
上一篇: How to pass IDataErrorInfo Validation through an wrapper to the XAML