DataGridRow错误指示符不适用于INotifyDataErrorInfo

时间:2013-11-04 12:06:45

标签: c# .net wpf .net-4.5 inotifydataerrorinfo

我试图更新我的ViewModel以使用INotifyDataErrorInfo而不是IDataErrorInfo并遇到以下问题:

当前编辑字段的验证似乎正常工作,但行级错误指示器不会出现,直到我在字段上结束编辑并出现错误,然后开始重新编辑它。之后,错误指示器消失,修复验证错误后的事件。

换句话说: 第一次编辑行时,TextBox轮廓正确变为红色,但行指示符不会出现。重新编辑行会导致行指示符出现。修复验证错误会导致字段轮廓消失,但会留下感叹号。

请注意,IDataErrorInfo似乎工作正常。它的INotifyDataErrorInfo我遇到了麻烦。

解决方案的一半:将绑定更改为TwoWay会导致行指示符正确显示,但它仍然不想消失。

以下是观点:

<DataGrid ItemsSource="{Binding Items, ValidatesOnNotifyDataErrors=True}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Name" Binding="{Binding Name, ValidatesOnNotifyDataErrors=True,Mode=TwoWay}" />
    </DataGrid.Columns>
</DataGrid>

以下是视图模型:

public class Item : INotifyDataErrorInfo, INotifyPropertyChanged
{
    Dictionary<string, IEnumerable<string>> _errors = new Dictionary<string,IEnumerable<string>>();
    string _name;

    public string Name
    {
        get { return _name; }
        set
        {
            if (_name != value)
            {
                ValidateProperty("Name", value);
                _name = value;
                RaisePropertyChanged("Name");
            }
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void RaisePropertyChanged(string p)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(p));
    }

    private void ValidateProperty(string p, object value)
    {
        if (p == "Name")
        {
            if (string.IsNullOrWhiteSpace((string)value))
                _errors["Name"] = new[] { "Name is required." };
            else
                _errors["Name"] = new string[0];
        }

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

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        if (string.IsNullOrEmpty(propertyName))
            return _errors.Values.SelectMany(es2 => es2);

        IEnumerable<string> es;
        _errors.TryGetValue(propertyName ?? "", out es);
        return es;
    }

    public bool HasErrors
    {
        get
        {
            var e = _errors.Values.Any(es => es.Any());
            return e;
        }
    }
}

似乎问题已经在SO上提出,但原作者删除了https://stackoverflow.com/questions/18113718/wpf-datagridrow-inotifydataerrorinfo-as-tooltip-buggy?answertab=active 原始问题的副本,但没有答案:http://bolding-techaswere1.blogspot.com.au/2013/08/wpf-datagridrow-inotifydataerrorinfo-as.html

修改

这是我的测试源代码: https://github.com/dcrowe/WPF-DataGrid-Validation-Issue/tree/master/DataGrid%20Validation%20Issue

以下是我提交给MS的报告: https://connect.microsoft.com/VisualStudio/feedback/details/807728/datagridrow-error-indicator-not-working-with-inotifydataerrorinfo

1 个答案:

答案 0 :(得分:1)

我知道我之前告诉过你将Binding Mode设置为TwoWay并且这是正确的,尽管你还必须小心如何定义验证规则。如果你想在这两者之间找到平衡,一切都会正常工作。

这是一个例子,一切都很好。就像我提到的那样,我无法用你的例子来表明自己,我错过了一些细节,无法重现你的问题,因此这里有一个简短的例子。

<Grid>
    <DataGrid CanUserAddRows="True"  AutoGenerateColumns="False"  ItemsSource="{Binding Pricelist}" >
        <DataGrid.Columns>
            <DataGridTextColumn Header="Price" Width="60" Binding="{Binding Price, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"></DataGridTextColumn>
        </DataGrid.Columns>
    </DataGrid>
</Grid>

这就是ViewModel的样子:

public partial class MainWindow : Window, INotifyPropertyChanged
{
    private ObservableCollection<MyProduct> priceList;

    public MainWindow()
    {
        InitializeComponent();
        Pricelist = new ObservableCollection<MyProduct>();
        this.DataContext = this;
    }

    public ObservableCollection<MyProduct> Pricelist
    {
        get
        {
            return this.priceList;
        }

        set
        {
            this.priceList = value; if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("PriceList"));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

public class MyProduct : INotifyPropertyChanged, IDataErrorInfo
{
    private string _price;
    public string Price
    {
        get
        {
            return _price;
        }
        set
        {
            _price = value;

            this.RaisePropertyChanged("Price");
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        var handler = this.PropertyChanged;
        if (handler != null)
        {
            handler(this, e);
        }
    }

    protected void RaisePropertyChanged(String propertyName)
    {
        OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
    }

    public string Error
    {
        get
        {
            return string.Empty;
        }
    }

    public string this[string columnName]
    {
        get
        {
            string result = null;
            switch (columnName)
            {
                case "Price":
                    {
                        decimal temdecimal = 0.00m;

                        if (Price != null && !decimal.TryParse(Price, out temdecimal))
                        {
                            result = "Price is invalid";
                        }
                        break;
                    }
                default:
                    {
                        break;
                    }
            }
            return result;
        }
    }
}

在我的情况下,验证可能允许NULL为Price属性的值,但它不允许string.Empty和任何其他包含字母的文本。

我认为如果您更改示例的验证,它也适用于您。

我希望无论如何我都会帮助你。如果您觉得有帮助,请随时标记此答案或投票。

该示例应该在您身边运行得很好,它应该按照您的要求执行。

<强> EDIT2:

INotifyDataErrorInfo变得简单:

<TextBox Text="{Binding LastName, Mode=TwoWay, NotifyOnValidationError=true }" />

<Button x:Name="OKButton" Content="OK" Click="OKButton_Click" Width="75" Height="23" />

这是点击处理程序:

private void ValidateButton_Click(object sender, RoutedEventArgs e)
{
   owner.FireValidation();
}

这是实现INotifyDataErrorInfo

的类
public class Owner : INotifyPropertyChanged, INotifyDataErrorInfo
{
   public Owner()
   {
      FailedRules = new Dictionary<string, string>();
   }

   private Dictionary<string, string> FailedRules
   {
      get;
      set;
   }

   public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

   public IEnumerable GetErrors(string propertyName)
   {
      if (FailedRules.ContainsKey(propertyName))
         return FailedRules[propertyName];
      else
         return FailedRules.Values;
   }

   internal void FireValidation()
   {
      if (lastName.Length > 20)
      {
         if (!FailedRules.ContainsKey("LastName"))
            FailedRules.Add("LastName", "Last name cannot have more than 20 characters");
      }
      else
      {
         if (FailedRules.ContainsKey("LastName"))
            FailedRules.Remove("LastName");
      }

      NotifyErrorsChanged("LastName");
   }

   public bool HasErrors
   {
      get { return FailedRules.Count > 0; }
   }

   private void NotifyErrorsChanged(string propertyName)
   {
      if (ErrorsChanged != null)
         ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
   }
}