2017-03-31 52 views
6

目前我有一个网格,我试图让一个单元格带有验证规则。为了验证它,我需要该行的最小值和最大值。使用验证规则和依赖项属性的WPF网格

校验类:

public decimal Max { get; set; } 

public decimal Min { get; set; } 

public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) 
{ 
    var test = i < Min; 
    var test2 = i > Max; 

    if (test || test2) 
     return new ValidationResult(false, String.Format("Fee out of range Min: ${0} Max: ${1}", Min, Max)); 
    else 
     return new ValidationResult(true, null); 
} 

用户控制:

<telerik:RadGridView SelectedItem ="{Binding SelectedScript}" 
        ItemsSource="{Binding ScheduleScripts}"> 
    <telerik:RadGridView.Columns> 
     <telerik:GridViewDataColumn 
      DataMemberBinding="{Binding Amount}" Header="Amount" 
      CellTemplate="{StaticResource AmountDataTemplate}" 
      CellEditTemplate="{StaticResource AmountDataTemplate}"/> 
     <telerik:GridViewComboBoxColumn 
      Header="Fee Type" 
      Style="{StaticResource FeeTypeScriptStyle}" 
      CellTemplate="{StaticResource FeeTypeTemplate}"/>   
    </telerik:RadGridView.Columns> 
</telerik:RadGridView> 

FeeType类:

public class FeeType 
{ 
    public decimal Min { get; set; } 
    public decimal Max { get; set; } 
    public string Name { get; set; } 
} 

我在这里尝试这种解决方案WPF ValidationRule with dependency property和它的伟大工程。但是现在我遇到了代理无法通过视图模型实例化的问题。它基于行的所选ComboBox值的Min和Max属性。

例如,组合框样本值低于

Admin Min: $75 Max $500 
Late Min: $0 Max $50 

由于就是了网格实际上可以具有尽可能多的行,我不能看到如何创建代理会在我的情况下工作。如果我能得到一些指导提示,将不胜感激。

+0

代码中只有一个'ComboBox'。 – AnjumSKhan

+0

@AnjumSKhan只有假设是一个组合框。组合框值是FeeType类的类型。所以无论选择什么都决定了它的最小值和最大值。 – Master

+4

你确定你不是[XY问题](https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem)?使用'ValidationRule'会花费很多精力来完成这项工作,而如果将验证逻辑移动到视图模型中,则很容易完成。 – Grx70

回答

4

警告:这不是权威解决方案,但向您展示了一种实现验证逻辑完全在ViewModels上的正确方法。

对于semplicity目的,我创建FeeTypes的列表作为FeeType类的静态属性:

public class FeeType 
{ 
    public decimal Min { get; set; } 
    public decimal Max { get; set; } 
    public string Name { get; set; } 

    public static readonly FeeType[] List = new[] 
    { 
     new FeeType { Min = 0, Max = 10, Name = "Type1", }, 
     new FeeType { Min = 2, Max = 20, Name = "Type2", }, 
    }; 
} 

这是视图模型对单个网格行。我只把金额和费用属性。现在

public class RowViewModel : INotifyPropertyChanged, INotifyDataErrorInfo 
{ 
    public RowViewModel() 
    { 
     _errorFromProperty = new Dictionary<string, string> 
     { 
      { nameof(Fee), null }, 
      { nameof(Amount), null }, 
     }; 

     PropertyChanged += OnPropertyChanged; 
    } 

    private void OnPropertyChanged(object sender, PropertyChangedEventArgs e) 
    { 
     switch(e.PropertyName) 
     { 
      case nameof(Fee): 
       OnFeePropertyChanged(); 
       break; 
      case nameof(Amount): 
       OnAmountPropertyChanged(); 
       break; 
      default: 
       break; 
     } 
    } 

    private void OnFeePropertyChanged() 
    { 
     if (Fee == null) 
      _errorFromProperty[nameof(Fee)] = "You must select a Fee!"; 
     else 
      _errorFromProperty[nameof(Fee)] = null; 

     NotifyPropertyChanged(nameof(Amount)); 
    } 

    private void OnAmountPropertyChanged() 
    { 
     if (Fee == null) 
      return; 

     if (Amount < Fee.Min || Amount > Fee.Max) 
      _errorFromProperty[nameof(Amount)] = $"Amount must be between {Fee.Min} and {Fee.Max}!"; 
     else 
      _errorFromProperty[nameof(Amount)] = null; 
    } 

    public decimal Amount 
    { 
     get { return _Amount; } 
     set 
     { 
      if (_Amount != value) 
      { 
       _Amount = value; 
       NotifyPropertyChanged(); 
      } 
     } 
    } 
    private decimal _Amount; 

    public FeeType Fee 
    { 
     get { return _Fee; } 
     set 
     { 
      if (_Fee != value) 
      { 
       _Fee = value; 
       NotifyPropertyChanged(); 
      } 
     } 
    } 
    private FeeType _Fee; 

    #region INotifyPropertyChanged 
    public event PropertyChangedEventHandler PropertyChanged; 
    public void NotifyPropertyChanged([CallerMemberName] string propertyName = null) 
    { 
     PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 
    } 
    #endregion 

    #region INotifyDataErrorInfo 
    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged; 

    public bool HasErrors 
    { 
     get 
     { 
      return _errorFromProperty.Values.Any(x => x != null); 
     } 
    } 

    public IEnumerable GetErrors(string propertyName) 
    { 
     if (string.IsNullOrEmpty(propertyName)) 
      return _errorFromProperty.Values; 

     else if (_errorFromProperty.ContainsKey(propertyName)) 
     { 
      if (_errorFromProperty[propertyName] == null) 
       return null; 
      else 
       return new[] { _errorFromProperty[propertyName] }; 
     } 

     else 
      return null; 
    } 

    private Dictionary<string, string> _errorFromProperty; 
    #endregion 
} 

,我与本地DataGrid测试,但结果应该是一样的Telerik的:

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Rows}"> 
    <DataGrid.Columns> 
    <DataGridTextColumn Binding="{Binding Amount}"/> 
    <DataGridComboBoxColumn SelectedItemBinding="{Binding Fee, UpdateSourceTrigger=PropertyChanged}" 
          ItemsSource="{x:Static local:FeeType.List}" 
          DisplayMemberPath="Name" 
          Width="200"/> 
    </DataGrid.Columns> 
</DataGrid> 

public partial class MainWindow : Window 
{ 
    public MainWindow() 
    { 
     InitializeComponent(); 

     Rows = new List<RowViewModel> 
     { 
      new RowViewModel(), 
      new RowViewModel(), 
     }; 

     DataContext = this; 
    } 

    public List<RowViewModel> Rows { get; } 
} 

如果FeeType实例可以在运行时修改MinMax,你需要实现INotifyPropertyChanged也在那个类上,适当地处理值的变化。

如果你是新东西“MVVM”,“ViewModels”,“Notification changes”等,请看看this article。如果您通常在WPF上处理中大项目,那么值得学习如何通过MVVM模式来分离视图和逻辑。这允许您以更快,更自动的方式测试逻辑,并保持组织和聚焦。

+0

尽管在这个特定的例子中可能不需要,但也会引发'INotifyDataErrorInfo.ErrorsChanged'事件。 – Grx70

+0

@ Grx70,实际上,WPF并不要求您在简单场景中引发'ErrorsChanged'事件:当引发'PropertyChanged'事件时,它会重新检查错误。如果您将我的代码复制到VS解决方案中并运行它,您可以看到这一点。提高'ErrorsChanged'事件只在更复杂的场景中才有用,而且由于我的代码已经很长了,我不想再为了解决这个问题而做更多的事情,这种情况只是理论上的。 –