mbm*_*voy 22 data-binding validation wpf
我开始在我的WPF应用程序中使用ValidationRules,但很困惑.
我有以下简单的规则:
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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
在XAML中使用如下:
<TextBox>
<TextBox.Text>
<Binding Path="Identity.Name">
<Binding.ValidationRules>
<validation:RequiredRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Run Code Online (Sandbox Code Playgroud)
这大多数工作正如我所料.我很惊讶地看到我的源属性(Identity.Name)没有被设置; 我有一个永远不会看到变化的撤销功能,除了重新输入它之外没有办法恢复该值(不好).
Microsoft的数据绑定概述描述了底部附近的验证过程,这很好地解释了这种行为.基于此,我想让我的ValidationStep设置UpdatedValue.
<validation:RequiredRule ValidationStep="UpdatedValue"/>
Run Code Online (Sandbox Code Playgroud)
这就是我的事情变得奇怪的地方.而不是使用对象值调用Validate()作为设置的属性值(即字符串),我得到了System.Windows.Data.BindingExpression!我没有在Microsoft的文档中看到任何描述此行为的内容.
在调试器,我可以看到源对象(DataContext的TextBox),导航路径的特性,并看到该值已被设定.但是,我没有看到在验证规则中找到正确属性的任何好方法.
注意:使用ValidationStepas ConvertedProposedValue,我得到输入的字符串(我没有使用转换器),但它也会在验证失败时阻止源属性更新,如预期的那样.有了CommittedValue,我得到了BindingExpression而不是字符串.
这里有几个问题:
为什么我会根据ValidationStep设置将不一致的参数类型传递给Validate()?
如何从BindingExpression获取实际值?
或者,是否有一种很好的方法可以让用户将TextBox恢复到之前的(有效)状态?(正如我所提到的,我自己的撤销功能永远不会看到变化.)
mbm*_*voy 15
我已经解决了从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>
Run Code Online (Sandbox Code Playgroud)
请注意,第一个TextBox使用ValidationStep="RawProposedValue"(默认值),而第二个使用ValidationStep="UpdatedValue",但两者都使用相同的验证规则.
一个简单的ViewModel(忽略INPC和其他有用的东西):
class MainWindowViewModel
{
public string String1
{ get; set; }
public string String2
{ get; set; }
}
Run Code Online (Sandbox Code Playgroud)
最后,新的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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
GetBoundValue()如果它获得BindingExpression,该方法将挖掘我关心的值,或者如果不是,则简单地反驳参数.真正的关键是找到"路径",然后用它来获取属性及其值.
限制:在我的原始问题中,我的绑定有Path="Identity.Name",因为我正在挖掘我的ViewModel的子对象.这将不工作,与上述代码期望的路径直接向装订物上的性质.幸运的是,我已经扁平化了我的ViewModel,所以不再是这种情况,但是解决方法可能是首先将控件的datacontext设置为子对象.
我想对Eduardo Brites表示赞赏,因为他的回答和讨论使我重新开始研究这个问题,并确实为他的谜题提供了一些内容.此外,当我即将完全放弃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;
}
}
Run Code Online (Sandbox Code Playgroud)