如何通过包装器将IDataErrorInfo验证传递给XAML

Wii*_*axx 7 c# validation xaml mvvm

目前我正面临着一个我无法解决的荒谬问题

我写了一个小包装器,它包装了几乎所有的Property并添加了一个Property但我不知道如何通过他将验证传递给我的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>
Run Code Online (Sandbox Code Playgroud)

视图模型

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
}
Run Code Online (Sandbox Code Playgroud)

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
 }
Run Code Online (Sandbox Code Playgroud)

我还在myWrapper中测试了IDataErrorInfo但没有运气

小智 2

由于您的 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
Run Code Online (Sandbox Code Playgroud)

您还需要传递属性名称,因为实际的 ViewModel 可能会在同一方法中验证多个属性。在您实际的 ViewModel 中,您将 OnValidate 方法作为委托传递,那么就可以了。

现在你将陷入验证的困境。您正在使用数据注释。例如 RangeAttribute 只能验证 int、double 或 string。由于属性只能在编译时在类型级别定义,因此您甚至无法将这些属性动态传递到包装器中。您可以编写自定义属性或使用其他验证机制,例如企业库验证块。

希望它能有所帮助。