具有验证规则和依赖关系的 WPF 网格 属性
WPF Grid With Validation Rule And Dependency Property
目前我有一个网格,我正在尝试创建一个带有验证规则的单元格。为了验证它,我需要该行的最小值和最大值。
验证Class:
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: [=11=] Max: ", 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>
费用类型Class:
public class FeeType
{
public decimal Min { get; set; }
public decimal Max { get; set; }
public string Name { get; set; }
}
我在这里 WPF ValidationRule with dependency property 尝试过这个解决方案,效果很好。但是现在我遇到了无法通过viewmodel实例化代理的问题。它基于行的选定组合框值的最小值和最大值 属性。
例如,组合框示例值如下
Admin Min: Max 0
Late Min: [=14=] Max
由于网格实际上可以有任意多的行,所以我看不出创建代理在我的情况下如何工作。如果我能得到一些指导提示,将不胜感激。
警告:这不是一个 最终的 解决方案,但向您展示了一种正确的方法来实现将其完全放在 ViewModels 上的验证逻辑。
为了简单起见,我将 FeeTypes 列表创建为 FeeType
class:
的静态 属性
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", },
};
}
这是单个 Grid 行的 ViewModel。我只放了 Amount 和 Fee 属性。
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
实例可以在运行时修改 Min
和 Max
,您还需要在 class 上实现 INotifyPropertyChanged
,处理值适当更改。
如果您不熟悉 "MVVM"、"ViewModels"、"Notification changes" 等,请查看 this article .如果你经常在 WPF 上做中大型项目,那么通过 MVVM 模式将 View 和 Logic 解耦是值得学习的。这使您能够以更快、更自动的方式测试逻辑,并使事情井井有条、重点突出。
目前我有一个网格,我正在尝试创建一个带有验证规则的单元格。为了验证它,我需要该行的最小值和最大值。
验证Class:
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: [=11=] Max: ", 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>
费用类型Class:
public class FeeType
{
public decimal Min { get; set; }
public decimal Max { get; set; }
public string Name { get; set; }
}
我在这里 WPF ValidationRule with dependency property 尝试过这个解决方案,效果很好。但是现在我遇到了无法通过viewmodel实例化代理的问题。它基于行的选定组合框值的最小值和最大值 属性。
例如,组合框示例值如下
Admin Min: Max 0
Late Min: [=14=] Max
由于网格实际上可以有任意多的行,所以我看不出创建代理在我的情况下如何工作。如果我能得到一些指导提示,将不胜感激。
警告:这不是一个 最终的 解决方案,但向您展示了一种正确的方法来实现将其完全放在 ViewModels 上的验证逻辑。
为了简单起见,我将 FeeTypes 列表创建为 FeeType
class:
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", },
};
}
这是单个 Grid 行的 ViewModel。我只放了 Amount 和 Fee 属性。
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
实例可以在运行时修改 Min
和 Max
,您还需要在 class 上实现 INotifyPropertyChanged
,处理值适当更改。
如果您不熟悉 "MVVM"、"ViewModels"、"Notification changes" 等,请查看 this article .如果你经常在 WPF 上做中大型项目,那么通过 MVVM 模式将 View 和 Logic 解耦是值得学习的。这使您能够以更快、更自动的方式测试逻辑,并使事情井井有条、重点突出。