2

我正在尝试更新我的 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

4

1 回答 1

1

我知道我之前告诉过您将绑定模式设置为 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 和任何其他包含字母的文本。

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

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

该示例应该在您身边运行得很好,它应该可以满足您的要求。

编辑2:

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));
   }
}
于 2013-11-04T22:34:38.303 回答