Wpf ValidationRule与ValidationStep=";“更新值”;使用BindingExpression而不是更新的值调用
我刚开始在WPF应用程序中使用ValidationRules,但相当混乱 我有以下简单的规则:Wpf ValidationRule与ValidationStep=";“更新值”;使用BindingExpression而不是更新的值调用,wpf,validation,data-binding,Wpf,Validation,Data Binding,我刚开始在WPF应用程序中使用ValidationRules,但相当混乱 我有以下简单的规则: class RequiredRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { if (String.IsNullOrWhiteSpace(value as strin
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
if (String.IsNullOrWhiteSpace(value as string))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
}
在XAML中使用,如下所示:
<TextBox>
<TextBox.Text>
<Binding Path="Identity.Name">
<Binding.ValidationRules>
<validation:RequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
这就是我觉得奇怪的地方。我得到了一个System.Windows.Data.BindingExpression
,而不是使用对象值作为设置的属性值(即字符串)调用Validate()!我在微软的文档中没有看到任何描述这种行为的内容
在调试器中,我可以看到源对象(文本框的的DataContext
),导航到属性的路径,并查看值是否已设置。但是,我看不到在验证规则中找到正确属性的任何好方法
注意:使用ValidationStep
作为ConvertedProposedValue
,我会得到输入的字符串(我没有使用转换器),但它也会在验证失败时阻止源属性更新,正如预期的那样。使用CommittedValue
,我得到的是BindingExpression
,而不是字符串
这里有几个问题:
为什么根据ValidationStep设置将不一致的参数类型传递给Validate()
如何从BindingExpression获取实际值
或者,是否有一种好方法允许用户将文本框恢复到以前的(有效)状态?(正如我提到的,我自己的撤销功能从未看到变化。)
为了回答您的两个问题:
字符串strVal=(字符串)((BindingExpression)值).DataItem
我已经解决了从BindingExpression
中提取值的问题,但有一个小小的限制
首先,一些更完整的XAML:
<Window x:Class="ValidationRuleTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:ValidationRuleTest"
Title="MainWindow" Height="100" Width="525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Text="String 1"/>
<TextBox Grid.Column="1">
<TextBox.Text>
<Binding Path="String1" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="RawProposedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock Text="String 2" Grid.Row="1"/>
<TextBox Grid.Column="1" Grid.Row="1">
<TextBox.Text>
<Binding Path="String2" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:RequiredRule ValidationStep="UpdatedValue"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</Window>
最后,新的RequiredRule:
class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value,
System.Globalization.CultureInfo cultureInfo)
{
// Get and convert the value
string stringValue = GetBoundValue(value) as string;
// Specific ValidationRule implementation...
if (String.IsNullOrWhiteSpace(stringValue))
{
return new ValidationResult(false, "Must not be empty");
}
else
{
return new ValidationResult(true, null);
}
}
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (Validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
object dataItem = binding.DataItem;
string propertyName = binding.ParentBinding.Path.Path;
// Extract the value of the property.
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
// This is what we want.
return propertyValue;
}
else
{
// ValidationStep was RawProposedValue or ConvertedProposedValue
// The argument is already what we want!
return value;
}
}
}
如果得到BindingExpression,则GetBoundValue()
方法将挖掘出我关心的值,如果没有,则简单地回退参数。真正的关键是找到“路径”,然后使用它来获取属性及其值
限制:在我最初的问题中,我的绑定有Path=“Identity.Name”
,因为我正在深入研究ViewModel的子对象。这将不起作用,因为上面的代码希望路径直接指向绑定对象上的属性。幸运的是,我已经展平了ViewModel,因此情况不再如此,但一个解决方法可能是首先将控件的datacontext设置为子对象
我想给爱德华多·布里茨一些荣誉,因为他的回答和讨论让我重新开始挖掘这个问题,并为他的谜题提供了一个线索。另外,当我打算完全抛弃ValidationRules而改用IDataErrorInfo时,我喜欢他的建议,即在不同类型和复杂的验证中同时使用它们。这是mbmcavoy的扩展
我修改了GetBoundValue
方法,以消除绑定路径的限制。BindingExpression方便地具有ResolvedSource和ResolvedSourcePropertyName属性,这些属性在调试器中可见,但无法通过普通代码访问。不过,通过反射来获取它们并没有问题,这个解决方案应该适用于任何绑定路径
private object GetBoundValue(object value)
{
if (value is BindingExpression)
{
// ValidationStep was UpdatedValue or CommittedValue (validate after setting)
// Need to pull the value out of the BindingExpression.
BindingExpression binding = (BindingExpression)value;
// Get the bound object and name of the property
string resolvedPropertyName = binding.GetType().GetProperty("ResolvedSourcePropertyName", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null).ToString();
object resolvedSource = binding.GetType().GetProperty("ResolvedSource", BindingFlags.Public | BindingFlags.DeclaredOnly | BindingFlags.Instance).GetValue(binding, null);
// Extract the value of the property
object propertyValue = resolvedSource.GetType().GetProperty(resolvedPropertyName).GetValue(resolvedSource, null);
return propertyValue;
}
else
{
return value;
}
}
这是对mbmcavoy和adabyron答案的另一种扩展
为了消除绑定路径的限制,我使用以下方法获取属性值:
public static object GetPropertyValue(object obj, string propertyName)
{
foreach (String part in propertyName.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
现在只需改变
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
到
相关帖子:我很惊讶没有看到任何反馈,特别是因为这已经被提升了投票率。对我来说,这似乎是使用ValidationRule方法的一个障碍,否则它看起来非常优秀和直观。使用IDataErrorInfo是否更好,尽管相比之下它看起来很笨重?这几乎是可行的,但不是。DataItem不包含属性值,而是包含DataContext(是的,属性在其中)。不幸的是,我不知道这个验证规则实例应该检查哪个属性。在BindingExpression中,我没有看到任何东西可以让我确定绑定了哪个属性(即路径)。我认为“RequiredRule”的目的是一种泛型的,验证哪个属性并不重要,只要它是字符串……是的,RequiredRule类应该能够验证任何字符串属性。对于上面的XAML,文本框用Path=“Identity.Name”绑定。我还有一个用Path=“Identity.Description”绑定的文本框,并应用了一个RequiredRule。由于传递的BindingExpression的DataItem是数据上下文,因此我可以看到Identity.Name和Identity.Description(以及ViewModel中的所有其他内容)。虽然我可以在调试器中看到我关心的值,但我看不到ValidationRule确定应该验证哪个属性的方法。在我的应用程序中,如果两个或多个控件使用相同的验证规则,则此规则必须是通用的,例如:Agrule、PositiveEnumberRule、,等等。所有需要特定业务验证的字段都继承IDataErrorInfo并在那里执行验证。这似乎是一种明智的方法,但我仍然不知道如何执行。(这个问题目前是我使用ValidationRules的一个障碍,我可能会用IDataErrorInfo做所有事情,我有一个可行的解决方案。不过,我真的很想了解这个问题。)您是否能够编辑您的答案,以包括一个可应用于多个控件的示例通用规则,并使用ValidationStep=“UpdatedValue”?在某些情况下,IDataErrorInfo是不够的,例如,当您想要验证datagrid中的唯一字段时,您必须比较行。我很高兴你找到了你想要的东西。干杯请参阅我的答案以删除limi
public static object GetPropertyValue(object obj, string propertyName)
{
foreach (String part in propertyName.Split('.'))
{
if (obj == null) { return null; }
Type type = obj.GetType();
PropertyInfo info = type.GetProperty(part);
if (info == null) { return null; }
obj = info.GetValue(obj, null);
}
return obj;
}
object propertyValue = dataItem.GetType().GetProperty(propertyName).GetValue(dataItem, null);
object propertyValue = GetPropertyValue(dataItem, propertyName);