我有一个DataGrid
,如下:
<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="YColumn" Width="*" Header="Latitude">
<DataGridTextColumn.Binding>
<Binding Path="Y">
<Binding.ValidationRules>
<validation:DoubleValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
<DataGridTextColumn x:Name="XColumn" Width="*" Header="Longitude">
<DataGridTextColumn.Binding>
<Binding Path="X">
<Binding.ValidationRules>
<validation:DoubleValidationRule />
</Binding.ValidationRules>
</Binding>
</DataGridTextColumn.Binding>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
我有两列具有相同的验证规则(检查单元格中的值是否为双精度):
public class DoubleValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (value != null)
{
double proposedValue;
if (!double.TryParse(value.ToString(), out proposedValue))
{
return new ValidationResult(false, "'" + value.ToString() + "' is not a whole double.");
}
}
return new ValidationResult(true, null);
}
}
这很好,如果用户输入的值不是双精度值,则单元格周围会显示一个红色边框。现在,如果任何单元格都存在验证错误,我想禁用按钮。
在关于该主题的其他文章之后,我使用MultiDataTriggers
实现了这一目标:
<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="False" />
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=XColumn}" Value="False" />
<Condition Binding="{Binding Path=(Validation.HasError), ElementName=YColumn}" Value="False" />
</MultiDataTrigger.Conditions>
<Setter Property="IsEnabled" Value="True" />
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
这不起作用。即使出现验证错误,该按钮也不会禁用。我究竟做错了什么?
编辑:这是我的模型以及视图模型中的相关代码:
public class CustomVertex
{
public double X { get; set; }
public double Y { get; set; }
public CustomVertex()
{ }
}
public class CustomPolygonViewModel : ViewModelBase
{
public ObservableCollection<CustomVertex> Vertices { get; set; }
public CustomPolygonViewModel()
{
Vertices = new ObservableCollection<CustomVertex>();
}
}
我的DataContext
设置正确,并且我验证了更改值时正在更新模型的x和y。验证规则被正确击中。
答案 0 :(得分:1)
您必须让您的视图模型实现INotifyDataErrorInfo
MSDN。 Example。 Example from MSDN (Silverlight)。
从.Net 4.5开始,这是将验证引入视图模型的推荐方法,将帮助您解决问题。
实现此接口时,必须提供一个可以绑定到的HasErrors
属性。 INotifyDataErrorInfo
取代了过时的IDataErrorInfo
。
就像在触发器中一样,直接绑定到Validation.HasError
不会起作用,因为Validation.HasError
是只读的附加属性,因此不支持绑定。为了证明这一点,我在MSDN上找到了这个声明:
...只读依赖项属性不适用于通常需要依赖项属性提供解决方案的许多方案(即:数据绑定,可直接样式化为值,验证,动画,继承)。
INotifyDataErrorInfo
的工作方式当ValidatesOnNotifyDataErrors
的{{1}}属性设置为Binding
时,绑定引擎将在绑定源上搜索true
实现以订阅{{1 }}事件。
如果引发INotifyDataErrorInfo
事件并且ErrorsChanged
的值为ErrorsChanged
,则绑定将为实际属性调用HasErrors
方法,以检索特定的错误消息并应用可定制的验证错误模板以可视化该错误。默认情况下,已验证的元素周围会绘制红色边框。
true
GetErrors()
类实际上是INotifyDataErrorInfo
列的ViewModel,因为您要绑定到其属性。因此,它必须实现CustomVertex
。看起来可能像这样:
DataGrid
视图:
INotifyDataErrorInfo
以下是验证错误模板,以防您要自定义外观(可选)。通过附加的属性public class CustomVertex : INotifyPropertyChanged, INotifyDataErrorInfo
{
public CustomVertex()
{
this.errors = new Dictionary<string, List<string>>();
this.validationRules = new Dictionary<string, List<ValidationRule>>();
this.validationRules.Add(nameof(this.X), new List<ValidationRule>() {new DoubleValidationRule()});
this.validationRules.Add(nameof(this.Y), new List<ValidationRule>() {new DoubleValidationRule()});
}
public bool ValidateProperty(object value, [CallerMemberName] string propertyName = null)
{
lock (this.syncLock)
{
if (!this.validationRules.TryGetValue(propertyName, out List<ValidationRule> propertyValidationRules))
{
return;
}
// Clear previous errors from tested property
if (this.errors.ContainsKey(propertyName))
{
this.errors.Remove(propertyName);
OnErrorsChanged(propertyName);
}
propertyValidationRules.ForEach(
(validationRule) =>
{
ValidationResult result = validationRule.Validate(value, CultuteInfo.CurrentCulture);
if (!result.IsValid)
{
AddError(propertyName, result.ErrorContent, false);
}
}
}
}
// Adds the specified error to the errors collection if it is not
// already present, inserting it in the first position if isWarning is
// false. Raises the ErrorsChanged event if the collection changes.
public void AddError(string propertyName, string error, bool isWarning)
{
if (!this.errors.ContainsKey(propertyName))
{
this.errors[propertyName] = new List<string>();
}
if (!this.errors[propertyName].Contains(error))
{
if (isWarning)
{
this.errors[propertyName].Add(error);
}
else
{
this.errors[propertyName].Insert(0, error);
}
RaiseErrorsChanged(propertyName);
}
}
// Removes the specified error from the errors collection if it is
// present. Raises the ErrorsChanged event if the collection changes.
public void RemoveError(string propertyName, string error)
{
if (this.errors.ContainsKey(propertyName) &&
this.errors[propertyName].Contains(error))
{
this.errors[propertyName].Remove(error);
if (this.errors[propertyName].Count == 0)
{
this.errors.Remove(propertyName);
}
RaiseErrorsChanged(propertyName);
}
}
#region INotifyDataErrorInfo Members
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public System.Collections.IEnumerable GetErrors(string propertyName)
{
if (String.IsNullOrEmpty(propertyName) ||
!this.errors.ContainsKey(propertyName)) return null;
return this.errors[propertyName];
}
public bool HasErrors
{
get { return errors.Count > 0; }
}
#endregion
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private double x;
public double X
{
get => x;
set
{
if (ValidateProperty(value))
{
this.x = value;
OnPropertyChanged();
}
}
}
private double y;
public double Y
{
get => this.y;
set
{
if (ValidateProperty(value))
{
this.y = value;
OnPropertyChanged();
}
}
}
private Dictionary<String, List<String>> errors;
// The ValidationRules for each property
private Dictionary<String, List<ValidationRule>> validationRules;
private object syncLock = new object();
}
(在上文中),在已验证元素(在本例中为<DataGrid CanUserSortColumns="False" CanUserAddRows="True" ItemsSource="{Binding Vertices}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn x:Name="YColumn"
Width="*"
Header="Latitude"
Binding="{Binding Y, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
<DataGridTextColumn x:Name="XColumn"
Width="*"
Header="Longitude"
Binding="{Binding X, ValidatesOnNotifyDataErrors=True}"
Validation.ErrorTemplate="{DynamicResource ValidationErrorTemplate}" />
</DataGrid.Columns>
</DataGrid>
)上进行了设置:
DataGridTextColumn
当验证失败时将被禁用的按钮(由于我不知道该按钮在视觉树中的位置,因此我假设它共享Validation.ErrorTemplate
列的<ControlTemplate x:Key=ValidationErrorTemplate>
<StackPanel>
<!-- Placeholder for the DataGridTextColumn itself -->
<AdornedElementPlaceholder x:Name="textBox"/>
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding ErrorContent}" Foreground="Red"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
, DataContext
数据模型):
DataGrid
网络上有很多示例。我更新了链接以提供一些内容。
我建议将CustomVertex
的实现与<Button>
<Button.Style>
<Style TargetType="Button">
<Setter Property="IsEnabled" Value="True" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=HasErrors}" Value="True">
<Setter Property="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
一起移入基类,并让您的所有视图模型都继承它。这样可以使验证逻辑可重用,并使您的视图模型类保持整洁。
您可以更改INotifyDataErrorInfo
的实现细节以符合要求。
备注:该代码未经测试。这些代码片段应该可以使用,但是旨在提供一个示例,说明如何实现INotifyPropertyChanged
接口。