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);
        }
    }
}
在XAML中使用如下:
<TextBox>
    <TextBox.Text>
        <Binding Path="Identity.Name">
            <Binding.ValidationRules>
                <validation:RequiredRule/>
            </Binding.ValidationRules>
         </Binding>
     </TextBox.Text>
</TextBox>
这大多数工作正如我所料.我很惊讶地看到我的源属性(Identity.Name)没有被设置; 我有一个永远不会看到变化的撤销功能,除了重新输入它之外没有办法恢复该值(不好).
Microsoft的数据绑定概述描述了底部附近的验证过程,这很好地解释了这种行为.基于此,我想让我的ValidationStep设置UpdatedValue.
<validation:RequiredRule ValidationStep="UpdatedValue"/>
这就是我的事情变得奇怪的地方.而不是使用对象值调用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>
请注意,第一个TextBox使用ValidationStep="RawProposedValue"(默认值),而第二个使用ValidationStep="UpdatedValue",但两者都使用相同的验证规则.
一个简单的ViewModel(忽略INPC和其他有用的东西):
class MainWindowViewModel
{
    public string String1
    { get; set; }
    public string String2
    { get; set; }
}
最后,新的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;
        }
    }
}
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;
    }
}
| 归档时间: | 
 | 
| 查看次数: | 11205 次 | 
| 最近记录: |