2017-03-31 77 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'事件只在更復雜的場景中才有用,而且由於我的代碼已經很長了,我不想再爲了解決這個問題而做更多的事情,這種情況只是理論上的。 –