如何通过包装将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

下一篇: batch unfaulting using NSFetchRequest