4

我有一个模型同时实现INotifyPropertyChangedINotifyDataErrorInfo的模型。当我修改了属性时,Property changed 事件就会触发,但由于某种原因,当我引发 Error 事件处理程序时,UI 确实会调用 GetErrors 方法。这会导致验证错误不会呈现到 UI。

有人可以看看我是如何设置 INotifyDataErrorInfo 并告诉我是否做错了什么吗?

基本模型实现

public class BaseChangeNotify : INotifyPropertyChanged, INotifyDataErrorInfo
{
    private bool isDirty;

    private Dictionary<string, List<string>> errors = new Dictionary<string, List<string>>();

    public BaseChangeNotify()
    {
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool IsDirty
    {
        get
        {
            return this.isDirty;
        }

        set
        {
            this.isDirty = value;
            this.OnPropertyChanged();
        }
    }

    public bool HasErrors
    {
        get
        {
            return this.errors.Count(e => e.GetType() == typeof(ErrorMessage)) > 0;
        }
    }

    public IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) ||
            !this.errors.ContainsKey(propertyName))
        {
            return null;
        }

        return this.errors[propertyName];/*.Where(e => (e is ErrorMessage));*/
    }

    protected virtual void AddError(string propertyName, string error, bool isWarning = false)
    {
        if (!this.errors.ContainsKey(propertyName))
        {
            this.errors[propertyName] = new List<string>();
        }

        if (!this.errors[propertyName].Contains(error))
        {
            if (isWarning)
            {
                this.errors[propertyName].Add(error);
            }
            else
            {
                this.errors[propertyName].Insert(0, error);
            }

            this.OnErrorsChanged(propertyName);
        }
    }

    protected virtual void RemoveError(string propertyName, string error)
    {
        if (this.errors.ContainsKey(propertyName) &&
            this.errors[propertyName].Contains(error))
        {
            this.errors[propertyName].Remove(error);

            if (this.errors[propertyName].Count == 0)
            {
                this.errors.Remove(propertyName);
            }

            this.OnErrorsChanged(propertyName);
        }
    }

    public virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        // Perform the IsDirty check so we don't get stuck in a infinite loop.
        if (propertyName != "IsDirty")
        {
            this.IsDirty = true; // Each time a property value is changed, we set the dirty bool.
        }

        if (this.PropertyChanged != null)
        {
            // Invoke the event handlers attached by other objects.
            try
            {
                // When unit testing, this will always be null.
                if (Application.Current != null)
                {
                    try
                    {
                        Application.Current.Dispatcher.Invoke(() =>
                            this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)));

                    }
                    catch (Exception)
                    {

                        throw;
                    }
                }
                else
                {
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                }
            }
            catch (Exception)
            {
                throw;
            }
        }
    }

    /// <summary>
    /// Called when an error has changed for this instance.
    /// </summary>
    /// <param name="propertyName">Name of the property.</param>
    public virtual void OnErrorsChanged([CallerMemberName] string propertyName = "")
    {
        if (string.IsNullOrWhiteSpace(propertyName))
        {
            return;
        }

        if (this.ErrorsChanged != null)
        {
            this.ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        }
    }
}

使用实现的模型

public class PayItem : BaseChangeNotify
{
    private Section section;

    public Section Section
    {
        get
        {
            return this.section;
        }

        set
        {
            this.section = value;
            this.ValidateSection();
            this.OnPropertyChanged();
        }
    }

    private void ValidateSection([CallerMemberName] string propertyName = "")
    {
        const string sectionError = "You must select a Section.";
        if (this.Section == null || this.Section.Name.Length > 1)
        {
            this.AddError(propertyName, sectionError);
        }
        else
        {
            this.RemoveError(propertyName, sectionError);
        }
    }

视图试图使用它

<ComboBox Name="SectionComboBox"
          ItemsSource="{Binding Path=ProjectSections}"
          SelectedItem="{Binding Path=SelectedPayItem.Section, 
                         NotifyOnValidationError=True,
                         UpdateSourceTrigger=PropertyChanged}">

该应用程序是用 WPF 编写的,而 WPF 文档非常稀缺。我已经阅读了有关它的Silverlight 文档以及在 Internet 上找到的其他一些博客文章,并以博客作者建议的每种不同方式实施。每次结果都相同时,该方法永远不会被 Binding 引擎命中。GetErrors()

谁能看到我做错了什么?当我的模型设置了它的属性时,我可以单步调试调试器并最终在OnErrorsChanged事件处理程序中结束,并且事件被调用。但是,当它被调用时什么都没有发生,所以我很难过。

提前感谢您的帮助。

乔纳森

编辑

另外我想指出,过去几个月我一直在基类中使用 IDataErrorInfo ,没有任何问题。绑定工作,错误报告给视图,一切都很开心。当我从 IDataErrorInfo 更改为 INotifyDataErrorInfo 时,验证似乎停止与视图通信。

4

1 回答 1

10

引发 ErrorsChanged 事件时,INotifyDataErrorInfo.HasErrors 属性必须返回 true。否则绑定引擎会忽略错误。您的 HasErrors 属性将始终返回 false。发生这种情况是因为您正在检查 ErrorMessage 类型的项目,但您的字典包含 KeyValuePair<string, List<string>> 类型的项目。除此之外,计算所有项目的效率非常低。您应该改用 .Any() 。

顺便说一句,INotifyDataErrorInfo 的 MSDN 文档说如下:

请注意,绑定引擎从不使用 HasErrors 属性,尽管您可以在自定义错误报告中使用它。

这是完全错误的,我花了几个小时才发现这一点。

于 2014-07-19T05:19:25.073 回答