c# - Silverlight ValidationSummary 不保留具有相同名称的 ListBox 元素的失败

标签 c# silverlight validation xaml

概述:

我有一个 Silverlight 4 应用程序,当它与 ListBox 一起使用时,我发现 ValidationSummary 控件有问题,我希望有人能帮助我。

在高层次上,我有一个列表框,其中每一行都用一个名为“txtInput”的文本框定义(通过 ItemTemplate)。文本框绑定(bind)(双向)到标有 DataAnnotation.Range 属性的对象属性。 ListBox 绑定(bind)到这些对象的集合。在同一个父控件中,我还有一个 ValidationSummary 控件。

场景:

想象一下,项目集合中有两个或多个对象的情况。用户会看到包含多行的列表框,每行包含一个文本框。如果用户在第一个文本框中键入无效数据,则会按预期抛出 ValidationException,并且 ValidationSummary 控件会按预期显示错误。文本框还获取验证错误样式(红色边框)。

然后,如果用户在第二行的文本框中输入了无效数据(没有修复第一个文本框中的数据),第二个文本框也会抛出 ValidationException 并获得验证错误样式(红色边框),正如预期的那样,但是, ValidationSummary 控件仅显示错误消息的一个实例。

然后,如果用户修复其中一个(但不是两个)无效文本条目,修复的文本框将移除验证样式(红色边框),并且 ValidationSummary 框消失(意思是它认为所有验证错误都已解决并且 .HasErrors 设置为 false)。第二个(仍然无效)文本框仍然应用了验证错误样式(红色边框)。

我的预期是仍然存在的无效文本框会导致 ValidationSummary 控件继续显示。我的假设是 ValidationSummary 控件只是通过属性名称跟踪失败,一旦成功尝试设置该名称的属性,它就会清除错误标记(即:它不考虑多个实例的情况出现同名)。

想要的结果:

最终,我要做的是防止用户在存在无效数据时单击屏幕上的“保存”按钮。我目前通过将按钮的 IsEnabled 属性绑定(bind)到 ValidationSummary 的 HasErrors 属性来执行此操作,但如果 ValidationSummary 显示上述行为,这将不起作用。

谁能告诉我一种方法让 ValidationSummary 控件尊重同一(重复)文本框的多次失败,或者提供一种可行的替代方法来在这些失败存在时禁用“保存”按钮? (注意:在我的实际应用中,每一行都有多个输入控件,因此任何解决方案都需要考虑这一点)

XAML 片段:

    <sdk:ValidationSummary x:Name="valSummary" />
    <ListBox ItemsSource="{Binding DomainObjectCollection, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=true, NotifyOnValidationError=true}" >
        <ListBox.ItemTemplate>
            <DataTemplate>
                <TextBox Name="txtInput" Text="{Binding DecimalValue, Mode=TwoWay, ValidatesOnDataErrors=True, ValidatesOnNotifyDataErrors=True, ValidatesOnExceptions=true, NotifyOnValidationError=true}"/>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>

    <Button x:Name="btnSave" Content="Save" Command="{Binding SaveButtonCommand}"  IsEnabled="{Binding HasErrors, ElementName=valSummary, Converter={StaticResource NotBoolConverter}}" />

Domain Object classes:

[System.Runtime.Serialization.CollectionDataContractAttribute()]
public partial class DomainObjectCollection : System.Collections.ObjectModel.ObservableCollection<DomainObject>
{
}

[System.Runtime.Serialization.DataContractAttribute()]
public partial class DomainObject : System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.IDataErrorInfo, System.ComponentModel.INotifyDataErrorInfo
{

    private int DomainObjectId_BackingField;

    private decimal DecimalValue_BackingField;

    private System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>> _errors;

    [System.Runtime.Serialization.DataMemberAttribute()]
    public virtual int DomainObjectId
    {
        get { return this.DomainObjectId_BackingField; }
        set 
        {
            if (!DomainObjectId_BackingField.Equals(value))
            {
                this.DomainObjectId_BackingField = value;
                this.RaisePropertyChanged("DomainObjectId");
            }
        }
    }

    [System.Runtime.Serialization.DataMemberAttribute()]
    [System.ComponentModel.DataAnnotations.RangeAttribute(typeof(decimal), "0", "100", ErrorMessage = "Value must be from 0 to 100.")]
    public virtual decimal DecimalValue
    {
        get { return this.DecimalValue_BackingField; }
        set
        {
            if (!DecimalValue_BackingField.Equals(value))
            {
                this.DecimalValue_BackingField = value;
                this.RaisePropertyChanged("DecimalValue");
            }
        }
    }

    string System.ComponentModel.IDataErrorInfo.Error
    {
        get { return string.Empty; }
    }

    string System.ComponentModel.IDataErrorInfo.this[string propertyName]
    {
        get
        {
            var results = Validate(propertyName);
            return results.Count == 0 ? null : string.Join(System.Environment.NewLine, results.Select(x => x.ErrorMessage));
        }
    }

    private System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>> Errors
    {
        get
        {
            if (_errors == null)
                _errors = new System.Collections.Generic.Dictionary<string, System.Collections.Generic.List<object>>();

            return _errors;
        }
    }

    bool System.ComponentModel.INotifyDataErrorInfo.HasErrors
    {
        get { return Errors.Count > 0; }
    }

    public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;

    public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs> ErrorsChanged;

    protected internal void RaisePropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new System.ComponentModel.PropertyChangedEventArgs(propertyName));
        }
    }

    private void Raise(string propertyName)
    {
        if (ErrorsChanged != null)
            ErrorsChanged(this, new System.ComponentModel.DataErrorsChangedEventArgs(propertyName));
    }

    System.Collections.IEnumerable System.ComponentModel.INotifyDataErrorInfo.GetErrors(string propertyName)
    {
        System.Collections.Generic.List<object> propertyErrors;

        if (Errors.TryGetValue(propertyName, out propertyErrors))
            return propertyErrors;

        return new System.Collections.Generic.List<object>();
    }

    public void AddError(string propertyName, object error)
    {
        System.Collections.Generic.List<object> propertyErrors;

        if (!Errors.TryGetValue(propertyName, out propertyErrors))
        {
            propertyErrors = new System.Collections.Generic.List<object>();
            Errors.Add(propertyName, propertyErrors);
        }

        if (propertyErrors.Contains(error))
            return;

        propertyErrors.Add(error);
        Raise(propertyName);
    }

    public void RemoveError(string propertyName)
    {
        Errors.Remove(propertyName);
        Raise(propertyName);
    }

    public virtual System.Collections.Generic.List<System.ComponentModel.DataAnnotations.ValidationResult> Validate(string propertyName)
    {
        var results = new System.Collections.Generic.List<System.ComponentModel.DataAnnotations.ValidationResult>();
        var propertyInfo = GetType().GetProperty(propertyName);

        if (propertyInfo == null)
            return results;

        RemoveError(propertyName);

        var context = new System.ComponentModel.DataAnnotations.ValidationContext(this, null, null)
        {
            MemberName = propertyName
        };

        if (!System.ComponentModel.DataAnnotations.Validator.TryValidateProperty(propertyInfo.GetValue(this, null), context, results))
        {
            foreach (var validationResult in results)
                AddError(propertyName, validationResult.ErrorMessage);
        }

        return results;
    }
}

最佳答案

我不久前遇到了这个问题,我发现它的发生是因为 ValidationSummary 控件使用控件的名称来查看它的错误集合中是否已经有错误。我正在研究一个解决方案并提出了以下行为。由于我遇到了一些问题,我们最终走上了不同的路线,因为很多 UI 是动态生成的。

您可以看一下并尝试一下,看看它是否可以解决您的问题:

public class ValidationSummaryCountFixBehavior : Behavior<ValidationSummary>
{
    private Dictionary<string, ValidationSummaryItem> _validationErrors = new Dictionary<string, ValidationSummaryItem>();

    protected override void OnAttached()
    {
        base.OnAttached();

        AssociatedObject.Loaded += new RoutedEventHandler(AssociatedObject_Loaded);
    }

    void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
    {
        var target = AssociatedObject.Target as FrameworkElement ?? VisualTreeHelper.GetParent(AssociatedObject) as FrameworkElement;
        if (target != null)
        {
            target.BindingValidationError += new EventHandler<ValidationErrorEventArgs>(target_BindingValidationError);
        }
        AssociatedObject.Loaded -= new RoutedEventHandler(AssociatedObject_Loaded);
    }

    void target_BindingValidationError(object sender, ValidationErrorEventArgs e)
    {
        FrameworkElement inputControl = e.OriginalSource as FrameworkElement;

        if (((e != null) && (e.Error != null)) && ((e.Error.ErrorContent != null) && (inputControl != null)))
        {
            string message = e.Error.ErrorContent.ToString();
            string goodkey = inputControl.GetHashCode().ToString(CultureInfo.InvariantCulture);
            goodkey = goodkey + message;

            if (e.Action == ValidationErrorEventAction.Added && ValidationSummary.GetShowErrorsInSummary(inputControl))
            {
                string messageHeader = null;
                ValidationSummaryItem item = new ValidationSummaryItem(message, messageHeader, ValidationSummaryItemType.PropertyError, new ValidationSummaryItemSource(messageHeader, inputControl as Control), null);
                _validationErrors[goodkey] = item;
            }
            else
            {
                _validationErrors.Remove(goodkey);
            }
        }

        UpdateDisplayedErrors();
    }

    private void UpdateDisplayedErrors()
    {
        AssociatedObject.Errors.Clear();
        foreach (ValidationSummaryItem item in _validationErrors.Values)
        {
            AssociatedObject.Errors.Add(item);
        }
    }
}

关于c# - Silverlight ValidationSummary 不保留具有相同名称的 ListBox 元素的失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6271815/

相关文章:

c# - XAML 多重绑定(bind) StringFormat

c# - SqlConnection On Before Close 事件?

c# - 从 Excel 单元格中读取富文本

wpf - 在 WPF/Silverlight 页面中设置自定义属性

asp.net - Firefox 4.0 始终刷新 Silverlight XAP 文件

c# - 多次调用 HttpContent ReadAsAsync

wpf - C# 中按钮单击命令的绑定(bind)

validation - p :fileUpload required ="true" and custom validator doesn't work

sql-server - SSIS 中禁用的任务是否会在执行或包加载期间进行验证

validation - 哪些字符会使 URL 无效?