Wpf ValidationRule与ValidationStep=";“更新值”;使用BindingExpression而不是更新的值调用

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

我刚开始在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>
这就是我觉得奇怪的地方。我得到了一个
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);