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

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

问题描述

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



我有以下简单的规则:

  class RequiredRule:ValidationRule 
{
public override ValidationResult验证(对象值,System.Globalization.CultureInfo cultureInfo)
{
if(String.IsNullOrWhiteSpace(value as string))
{
返回新的ValidationResult(false,不能为空);
}
else
{
返回新的ValidationResult(true,null);
}

}
}

XAML如下:

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

这个主要是正如我所料。我很惊讶地看到我的源属性( Identity.Name )未被设置;我有一个撤消功能,从来没有看到这个变化,没有办法恢复值,而不是重新输入(不好)。



微软的数据绑定概述介绍了验证过程附近底部,这很好地解释了这个行为。基于此,我想要将我的 ValidationStep 设置为 UpdatedValue

 < validation:RequiredRule ValidationStep =UpdatedValue/> 

这是对我来说奇怪的地方。而不是使用Validate()被调用的对象值是设置的属性值(即字符串),我得到一个 System.Windows.Data.BindingExpression !我在Microsoft的文档中没有看到描述这种行为的东西。



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



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



这里有几个问题:


  1. 为什么我得到一个不一致的参数类型传递给Validate()在验证步骤设置?


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


  3. 另外,还有一个很好的方法是允许用户将TextBox还原到上一个(有效)状态? (正如我所说,我自己的撤消功能从来没有看到改变。)



解决方案

p>我已经解决了从 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 =MainWindowHeight =100Width =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 =String1UpdateSourceTrigger =PropertyChanged>
< Binding.ValidationRules>
< local:RequiredRule ValidationStep =RawProposedValue/>
< /Binding.ValidationRules>
< / Binding>
< /TextBox.Text>
< / TextBox>
< TextBlock Text =String 2Grid.Row =1/>
< TextBox Grid.Column =1Grid.Row =1>
< TextBox.Text>
< Binding Path =String2UpdateSourceTrigger =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;组; }

public string String2
{get;组;
}

最后,新的RequiredRule:

  class RequiredRule:ValidationRule 
{
public override ValidationResult验证(对象值,
System.Globalization.CultureInfo cultureInfo)
{
//获取并转换值
string stringValue = GetBoundValue(value)作为字符串;

//具体的ValidationRule实现...
if(String.IsNullOrWhiteSpace(stringValue))
{
返回新的ValidationResult(false,不能为空 );
}
else
{
返回新的ValidationResult(true,null);



私有对象GetBoundValue(对象值)
{
if(value is BindingExpression)
{
/ / ValidationStep已更新的值或CommittedValue(设置后验证)
//需要将值从BindingExpression中拉出。
BindingExpression binding =(BindingExpression)value;

//获取绑定对象和属性名称
对象dataItem = binding.DataItem;
string propertyName = binding.ParentBinding.Path.Path;

//提取属性的值。
object propertyValue = dataItem.GetType()。GetProperty(propertyName).GetValue(dataItem,null);

//这就是我们想要的。
return propertyValue;
}
else
{
//验证步骤为RawProposedValue或ConvertedProposedValue
//参数已经是我们想要的!
返回值;
}
}
}

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



限制:在我的原始问题中,我的绑定有 Path =Identity.Name,因为我正在挖掘我的ViewModel的子对象。这将工作,因为上面的代码希望路径直接到绑定对象上的属性。幸运的是,我已经展平了我的ViewModel,所以现在已经不再这样了,但是一个解决方法可能就是将控件的数据文本设置为子对象,首先。



我想给Eduardo Brites一些信用,因为他的回答和讨论让我回到这个挖掘,并提供了一个他的难题。另外,当我要完全使用ValidationRules并使用IDataErrorInfo时,我喜欢他的建议,将它们一起使用在不同类型和复杂的验证中。


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

I have the following simple rule:

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);
        }

    }
}

Used in XAML as follows:

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

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'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"/>

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.

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.

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.

There are several questions in here:

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

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

  3. 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.)

解决方案

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

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>

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

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

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

    public string String2
    { get; set; }
}

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;
        }
    }
}

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.

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.

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与ValidationStep =“UpdatedValue”使用BindingExpression而不是更新的值调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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