当HasError为false时,验证错误装饰不会清除

时间:2012-02-23 09:14:01

标签: c# wpf validation xaml adorner

引言

我创建了一个DecimalTextBox用户控件,它附加了ValidationRule以防止空值,具有最小和最大范围,并且它具有事件处理程序以防止非十进制值。我用过

ValidatesOnTargetUpdated="True"

关于绑定,因为我希望立即激活验证(在最小值和最大值发生变化之前我遇到了问题,但验证没有被重新评估)。

我所做的空验证取决于“AllowNull”依赖项属性的值:如果控件指定为true,则即使值为null,控件也是有效的。如果为false,则不允许为null。此属性的默认值为False


问题

在某个UserControl中使用它时,我将AllowNull设置为true。不幸的是,因为ValidatesOnTargetUpdated设置为true,所以在xaml将AllowNull设置为true之前验证控件,同时它仍然处于默认false设置。

这会在加载前导致错误,因为对TextBox文本的绑定尚未解析,因此在加载之前它不允许为null,并且文本的值为null。

这一切都很好,花花公子,因为在加载后,使用新的AllowNull值(true)重新评估验证,并删除错误。

然而红色验证装饰仍然存在。不完全确定如何摆脱它。


代码 textbox usercontrol的xaml:

<UserControl x:Class="WPFTest.DecimalTextBox"
         x:Name="DecimalBox"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:v="clr-namespace:ValidationRules"
         mc:Ignorable="d" 
         d:DesignHeight="25" d:DesignWidth="100" Initialized="DecimalBox_Initialized" >

    <TextBox x:Name="textbox">
        <TextBox.Text>
            <Binding ElementName="DecimalBox" TargetNullValue="" Path="Text" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" ValidatesOnDataErrors="True" ValidatesOnExceptions="True" NotifyOnValidationError="True">
                <Binding.ValidationRules>
                    <v:DecimalRangeRule  ValidatesOnTargetUpdated="True">
                        <v:DecimalRangeRule.MinMaxRange>
                            <v:MinMaxValidationBindings x:Name="minMaxValidationBindings"/>
                        </v:DecimalRangeRule.MinMaxRange> 
                    </v:DecimalRangeRule>
                    <v:NotEmptyRule  ValidatesOnTargetUpdated="True">
                        <v:NotEmptyRule.AllowNull>
                            <v:AllowNullValidationBinding x:Name="allowNullValidationBindings"></v:AllowNullValidationBinding>
                        </v:NotEmptyRule.AllowNull>
                    </v:NotEmptyRule>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
</UserControl>

控件背后的代码:

    public static DependencyProperty TextProperty = DependencyProperty.Register("Text", typeof(string), typeof(textboxcontrol), new PropertyMetadata());
    public static DependencyProperty MinimumProperty = DependencyProperty.Register("Minimum", typeof(decimal), typeof(DecimalTextBox), new PropertyMetadata(0M));
    public static DependencyProperty MaximumProperty = DependencyProperty.Register("Maximum", typeof(decimal), typeof(DecimalTextBox), new PropertyMetadata(0M));
    public static DependencyProperty AllowNullProperty = DependencyProperty.Register("AllowNull", typeof(bool), typeof(DecimalTextBox), new UIPropertyMetadata(false));

    public bool AllowNull
    {
        get { return (bool)GetValue(AllowNullProperty); }
        set { SetValue(AllowNullProperty, value); }
    }
    public decimal Minimum
    {
        get { return (decimal)GetValue(MinimumProperty); }
        set { SetValue(MinimumProperty, value); }
    }
    public decimal Maximum
    {
        get { return (decimal)GetValue(MaximumProperty); }
        set { SetValue(MaximumProperty, value); }
    }
    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }



    private void DecimalBox_Initialized(object sender, EventArgs e)
    {
        Binding minBinding = new Binding("Minimum");
        minBinding.Source = this;
        Binding maxBinding = new Binding("Maximum");
        maxBinding.Source = this;
        Binding allownullBinding = new Binding("AllowNull");
        allownullBinding.Source = this;

        minMaxValidationBindings.SetBinding(ValidationRules.MinMaxValidationBindings.minProperty, minBinding);
        BindingOperations.SetBinding(minMaxValidationBindings, ValidationRules.MinMaxValidationBindings.maxProperty, maxBinding);
        BindingOperations.SetBinding(allowNullValidationBindings, ValidationRules.AllowNullValidationBinding.allowNullProperty, allownullBinding);
    }

验证规则(#Note:它们在ValidationRules命名空间内):

public class NotEmptyRule : ValidationRule
{

    public NotEmptyRule()
    {
    }
    private AllowNullValidationBinding _allowNullBinding;

    public AllowNullValidationBinding AllowNull
    {
        get { return _allowNullBinding; }
        set { _allowNullBinding = value; }
    }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        if (!_allowNullBinding.AllowNull)
            if (string.IsNullOrEmpty((string)value))
                return new ValidationResult(false,
                  "Value cannot be null or empty.");
            else
                return new ValidationResult(true, null);

        else
           return new ValidationResult(true, null);

    }
}

public class DecimalRangeRule : ValidationRule
{
    private MinMaxValidationBindings _bindableMinMax;
    public MinMaxValidationBindings MinMaxRange
    {
        get { return _bindableMinMax; }
        set
        {
            _bindableMinMax = value;

        }
    }


    public DecimalRangeRule()
    {

    }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {

        decimal number = 0;

        if(decimal.TryParse((string)value,out number))
            if (_bindableMinMax.Min != _bindableMinMax.Max || _bindableMinMax.Min != 0)
            {
                if ((number < _bindableMinMax.Min) || (number > _bindableMinMax.Max))
                {
                    return new ValidationResult(false,
                      "Please enter an decimal in the range: " + _bindableMinMax.Min + " - " + _bindableMinMax.Max + ".");
                }
                else
                {
                    return new ValidationResult(true, null);
                }
            }
            else
                return new ValidationResult(true, null);
        else
            return new ValidationResult(true, null);
    }
}

public class AllowNullValidationBinding:FrameworkElement
{
     public static readonly DependencyProperty allowNullProperty = DependencyProperty.Register(
        "AllowNull", typeof(bool), typeof(AllowNullValidationBinding), new UIPropertyMetadata(false));

    public bool AllowNull
    {
        get{return (bool)GetValue(allowNullProperty);}
        set{SetValue(allowNullProperty,value);}
    }
    public AllowNullValidationBinding()
    {}
}

public class MinMaxValidationBindings : FrameworkElement
{
    public static readonly DependencyProperty minProperty = DependencyProperty.Register(
        "Min", typeof(decimal), typeof(MinMaxValidationBindings), new UIPropertyMetadata(0.0m));

    public static readonly DependencyProperty maxProperty = DependencyProperty.Register(
        "Max", typeof(decimal), typeof(MinMaxValidationBindings), new UIPropertyMetadata(0.0m));

    public decimal Min
    {
        get { return (decimal)GetValue(minProperty); }
        set { SetValue(minProperty, value); }
    }

    public decimal Max
    {
        get { return (decimal)GetValue(maxProperty); }
        set { SetValue(maxProperty, value); }
    }

    public MinMaxValidationBindings() { }

}

使用FrameworkElement绑定,因此我的ValidationRules可以具有要绑定的依赖项属性。这允许我指定控件之外的最小值和最大值。


概要

我在加载后使用HasError(对于两者控件本身以及它的内部Validation.GetHasError(DecimalBox))检查TextBox并生成假的。

我知道如果我删除ValidatesOnTargetUpdated="True"红色不会出现,但我需要它。那么为什么重新评估验证,但红色边框装饰没有消失?

我对Validation类或其静态方法知之甚少,但是有什么东西要删除装饰。 ClearInvalid方法不会有帮助,因为我没有提供错误。

有什么想法吗?

u_u


修改

我做了更多调查,发现了以下内容:

  1. 如果我在加载后将文本更改为大于最大值的数量,然后将其更改为错误装配器消失
  2. 如果我将控件加载事件中的文本依赖项属性的值以编程方式更改为大于最大值并将其更改回来,装饰器仍然存在
  3. 如果我在加载后将文本更改为空值然后将其更改回装饰器仍然存在
  4. 如果我更改了viewmodel属性的值绑定到视图模型的构造函数内的文本,装饰器仍然存在
  5. 如果我将视图模型构造函数中绑定的viewmodel属性的值更改为大于最大值的值,然后将其更改回来,装饰器仍然存在
  6. 如果我将使用按钮绑定到文本的viewmodel属性的值更改为其他值,然后将其更改回来, adorner dissapears
  7. 如果我将使用按钮绑定到文本的viewmodel属性的值更改为值大于最大值,然后将其更改回来,装饰者消失
  8. 我还是很难过。我尝试了UpdateLayout()之类的方法,并尝试将装饰器移动到不同的控件并使用Validation.SetValidationAdornerSite将其移回。我一直在努力,但我不知道该怎么做。

    u_u


    2ND EDIT

    好的,我在此期间所做的是在TextBox周围放置一个AdornerDecorator,然后在文本框中加载事件将最大值更改为1,将值更改为2,然后将其更改回来以使文本框刷新。 / p>

    这是有效的,但我讨厌这个想法导致其可怕的代码。

    然而,这种解决方案已不再适用。我有一些代码正在对属性的一些属性进行更改,这些属性绑定到这些DecimalTextBox之一。然后因为属性在load事件中被更改并更改回来,其他代码也在运行并导致错误。我必须找到一个更好的解决方案。

    有谁知道如何刷新验证装饰器?

    u_u

3 个答案:

答案 0 :(得分:2)

以下是我遇到问题并且无法在其上找到任何内容的问题的几种解决方法,我怀疑它是由于竞争条件导致的框架中的某个地方的错误但是不能#&# 39;找不到任何东西支持它。

对私人领域的反思(哎呀)

因为您知道您的字段没有错误,所以可以执行此操作来迭代控件并杀死广告

var depPropGetter = typeof (Validation).GetField("ValidationAdornerProperty", BindingFlags.Static | BindingFlags.NonPublic);
var validationAdornerProperty = (DependencyProperty)depPropGetter.GetValue(null);
var adorner = (Adorner)DateActionDone.GetValue(validationAdornerProperty);

if (adorner != null && Validation.GetHasError(MyControl))
{
    var adorners = AdornerLayer.GetAdornerLayer(MyControl).GetAdorners(MyControl);
    if (adorners.Contains(adorner))
        AdornerLayer.GetAdornerLayer(MyControl).Remove(adorner);
}

或者你可以通过反射来调用Validation.ShowAdornerHelper方法,我没有直接尝试过,所以无法为编写代码而烦恼。

强行刷新所有绑定

我们可以利用您的发现,即使绑定无效然后再次生效将为我们清除装饰。

这是我决定采用的解决方案,并且恰好非常有效。

因为我在我的基本视图模型中使用了IDataErrorInfo,所以我可以根据你的验证处理方式做一些事情,你可能会更难以重新验证它们。

    string IDataErrorInfo.this[string columnName]
    {
        get
        {
            if (_refreshing) return "Refreshing";
            return ValidationEngine.For(this.GetType()).GetError(this, columnName);
        }
    }

    bool _refreshing = false;
    public void RefreshValidation()
    {
        _refreshing = true;
        this.NotifyOfPropertyChange(string.Empty);
        _refreshing = false;
        this.NotifyOfPropertyChange(string.Empty);
    }

答案 1 :(得分:0)

尝试删除控件的事件LayoutUpdated上的错误(在事件上放置一个标志只执行一次)

Validation.ClearInvalid(SystemCode.GetBindingExpression(TextBox.TextProperty));

然后重新评估您的验证规则(刷新绑定)。

var dp = SystemCode.GetBindingExpression(TextBox.TextProperty);
dp.UpdateSource();

答案 2 :(得分:0)

我有一个类似的问题,即使根本的错误发生,装饰器也不会消失。

我发现的最终解决方法是通过调用

来强制更新布局
Control.UpdateLayout()

以某种方式迫使WPF同步备份。我在Control_Loaded事件处理程序上进行了更新,但它有时也可以解决此问题。

相关问题