使用 BindingExpression 调用具有 ValidationStep="UpdatedValue" 的 ValidationRule,而不是更新值

2024-04-09

我开始在 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>

This mostly正如我所期望的那样工作。我很惊讶地发现我的源属性(Identity.Name) 没有被设置;我有一个撤消函数,它永远看不到更改,并且除了重新输入值之外没有其他方法可以恢复该值(不好)。

微软的数据绑定概述 https://msdn.microsoft.com/en-us/library/vstudio/ms752347(v=vs.100).aspx底部描述了验证过程,很好地解释了这种行为。基于此,我想拥有我的ValidationStep set to UpdatedValue.

<validation:RequiredRule ValidationStep="UpdatedValue"/>

这就是事情对我来说变得奇怪的地方。我没有使用作为设置的属性值(即字符串)的对象值来调用 Validate() ,而是得到一个System.Windows.Data.BindingExpression!我在 Microsoft 的文档中没有看到任何描述此行为的内容。

在调试器中,我可以看到源对象(DataContext of the TextBox),导航到该属性的路径,并查看该值已设置。但是,我没有看到任何在验证规则中获取正确属性的好方法。

注:与ValidationStep as ConvertedProposedValue,我得到输入的字符串(我没有使用转换器),但当验证失败时,它也会阻止源属性更新,如预期的那样。和CommittedValue,我得到BindingExpression而不是字符串。

这里有几个问题:

  1. 为什么根据 ValidationStep 设置传递给 Validate() 的参数类型不一致?

  2. 如何从 BindingExpression 获取实际值?

  3. 或者,是否有一种好方法允许用户将 TextBox 恢复到以前的(有效)状态? (正如我提到的,我自己的撤消函数永远不会看到变化。)


我已经解决了从中提取价值的问题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;
        }
    }
}

The GetBoundValue()如果它获得 BindingExpression,方法将挖掘出我关心的值,或者如果不是,则简单地回退参数。真正的关键是找到“路径”,然后使用它来获取属性及其值。

限制:在我原来的问题中,我的绑定有Path="Identity.Name",当我深入研究 ViewModel 的子对象时。这会not有效,因为上面的代码期望路径直接指向绑定对象上的属性。幸运的是,我已经展平了 ViewModel,因此情况不再如此,但解决方法可能是首先将控件的数据上下文设置为子对象。

我想对 Eduardo Brites 给予一些信任,因为他的回答和讨论让我重新开始深入研究这个问题,并且确实为他的难题提供了一部分。另外,虽然我打算完全放弃 ValidationRules 并使用 IDataErrorInfo 代替,但我喜欢他关于将它们一起用于不同类型和复杂性验证的建议。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 BindingExpression 调用具有 ValidationStep="UpdatedValue" 的 ValidationRule,而不是更新值 的相关文章

随机推荐