ValidationRule with ValidationStep="UpdatedValue";使用 BindingExpression 而不是更新值调用 [英] ValidationRule with ValidationStep="UpdatedValue" is called with BindingExpression instead of updated value

查看:28
本文介绍了ValidationRule with ValidationStep="UpdatedValue";使用 BindingExpression 而不是更新值调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开始在我的 WPF 应用程序中使用 ValidationRules,但很困惑.

I am getting started with using ValidationRules in my WPF application, but quite confused.

我有以下简单的规则:

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 中使用如下:

Used in XAML as follows:

<TextBox>
    <TextBox.Text>
        <Binding Path="Identity.Name">
            <Binding.ValidationRules>
                <validation:RequiredRule/>
            </Binding.ValidationRules>
         </Binding>
     </TextBox.Text>
</TextBox>

大部分按我的预期工作.我很惊讶地看到我的源属性 (Identity.Name) 没有被设置;我有一个永远不会看到更改的撤消功能,除了重新键入它之外,没有办法恢复该值(不好).

This mostly works as I would expect. I was surprised to see that my source property (Identity.Name) was not being set; I have an undo function that never sees the change, and there is no way to revert the value other than re-type it (not good).

Microsoft 的数据绑定概述 描述了靠近底部的验证过程,它很好地解释了这种行为.基于此,我希望将 ValidationStep 设置为 UpdatedValue.

Microsoft's Data Binding Overview describes the validation process near the bottom, which explains this behavior very well. Based on this, I would want to have my ValidationStep set to UpdatedValue.

<validation:RequiredRule ValidationStep="UpdatedValue"/>

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

This is where things get weird for me. Instead of Validate() being called with object value being the property value that was set (i.e., a string), I get a System.Windows.Data.BindingExpression! I don't see anything in Microsoft's documentation that describes this behavior.

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

In the debugger, I can see the source object (the DataContext of the TextBox), navigate the path to the property, and see that the value has been set. However, I don't see any good way to get to the right property within the validation rule.

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

Note: With ValidationStep as ConvertedProposedValue, I get the entered string (I don't have a converter in use), but it also blocks the source property update when validation fails, as expected. With CommittedValue, I get the BindingExpression instead of the string.

这里有几个问题:

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

  1. Why do I get an inconsistent argument type being passed to Validate() based on the ValidationStep setting?

如何从 BindingExpression 获取实际值?

How can I get to the actual value from the BindingExpression?

或者,是否有一种好方法可以让用户将 TextBox 恢复到之前的(有效)状态?(正如我所提到的,我自己的撤消功能永远不会看到变化.)

Alternately, is there a good way to allow the user to revert the TextBox to the previous (valid) state? (As I mentioned, my own undo function never sees the change.)

推荐答案

我已经解决了从 BindingExpression 中提取值的问题,有一个小限制.

I have solved the problem of extracting the value from the BindingExpression, with a minor limitation.

首先,一些更完整的 XAML:

First, some more complete 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",但两者都使用相同的验证规则.

Note that the first TextBox uses ValidationStep="RawProposedValue" (the default), while the second one uses ValidationStep="UpdatedValue", but both use the same validation rule.

一个简单的 ViewModel(忽略 INPC 和其他有用的东西):

A simple ViewModel (neglecting INPC and other useful stuff):

class MainWindowViewModel
{
    public string String1
    { get; set; }

    public string String2
    { get; set; }
}

最后,新的RequiredRule:

And finally, the new 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,或者如果它不是,则简单地踢回参数.真正的关键是找到路径",然后使用它来获取属性及其值.

The GetBoundValue() method will dig out the value I care about if it gets a BindingExpression, or simply kick back the argument if it's not. The real key was finding the "Path", and then using that to get the property and its value.

限制:在我最初的问题中,我的绑定有 Path="Identity.Name",因为我正在挖掘我的 ViewModel 的子对象.这将不起作用,因为上面的代码期望路径直接指向绑定对象上的属性.幸运的是,我已经扁平化了我的 ViewModel,所以情况不再如此,但一种解决方法是首先将控件的数据上下文设置为子对象.

The limitation: In my original question, my binding had Path="Identity.Name", as I was digging into sub-objects of my ViewModel. This will not work, as the code above expects the path to be directly to a property on the bound object. Fortunately, I have already flattened my ViewModel so this is no longer the case, but a workaround could be to set the control's datacontext to be the sub-object, first.

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

I'd like to give some credit to Eduardo Brites, as his answer and discussion got me back to digging on this, and did provide a piece to his puzzle. Also, while I was about to ditch the ValidationRules entirely and use IDataErrorInfo instead, I like his suggestion on using them together for different types and complexities of validation.

这篇关于ValidationRule with ValidationStep="UpdatedValue";使用 BindingExpression 而不是更新值调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆