我有一个实现 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);
        }
    }

试图使用它的 View
<ComboBox Name="SectionComboBox"
          ItemsSource="{Binding Path=ProjectSections}"
          SelectedItem="{Binding Path=SelectedPayItem.Section,
                         NotifyOnValidationError=True,
                         UpdateSourceTrigger=PropertyChanged}">

该应用程序是用 WPF 编写的,WPF 文档非常稀缺。我已经通读了 Silverlight documentation 上的 along with 其他一些 blog posts 我发现了 on the internet 并以博客作者建议的每种不同方式实现。每次结果相同时, GetErrors() 方法永远不会被绑定(bind)引擎命中。

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

在此先感谢您的帮助。

乔纳森

编辑

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

最佳答案

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

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



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

关于wpf - UI 未调用 INotifyDataErrorInfo.GetErrors(),我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/24518520/

10-11 00:56