首页 文章

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

提问于
浏览
1

INTRODUCTION

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

ValidatesOnTargetUpdated="True"

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

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


THE PROBLEM

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

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

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

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


THE CODE 文本框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可以具有要绑定的依赖项属性 . 这允许我在控件外指定最小值和最大值 .


SUMMARY

我在加载后使用 Validation.GetHasError(DecimalBox) (对于控件本身以及它的内部 TextBox )检查了 HasError 并且它产生了错误 .

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

我不太了解Validation类或它的静态方法,但是有什么东西可以删除装饰 . ClearInvalid方法不会有帮助,因为我没有提供错误 .

有任何想法吗?

u_u


EDIT

我做了一些调查,发现了以下几点:

  • 如果我在加载后将文本更改为大于最大值的数量,然后将其更改回 the error adorner dissapears

  • 如果我将控件加载事件中的Text依赖项属性的值以编程方式更改为大于最大值的值并将其更改回来, the adorner is still there .

  • 如果我在加载后将文本更改为空值,然后将其更改回 adorner is still there .

  • 如果我更改了viewmodel属性的值绑定到视图模型的构造函数内的文本, the adorner is still there

  • 如果我将视图模型构造函数中绑定的viewmodel属性的值更改为大于最大值的值,然后将其更改回来, the adorner is still there .

  • 如果我将使用按钮绑定到文本的viewmodel属性的值更改为其他值,然后将其更改回来, the adorner dissapears

  • 如果我将使用按钮绑定到文本的viewmodel属性的值更改为大于最大值的值,然后将其更改回来, the adorner dissapears

我还是很难过 . 我尝试了像 UpdateLayout() 这样的方法,并尝试将装饰器移动到不同的控件并使用 Validation.SetValidationAdornerSite 将其移回 . 我继续尝试,但我不知道该怎么做 .

u_u


2ND EDIT

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

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

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

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

u_u

2 回答

  • 0

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

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

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

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

    以下是这个问题的几个解决方法,因为我遇到了它并且无法在其上找到任何内容,我怀疑它是由于竞争条件而在框架中某处出现的错误但是找不到任何可以支持它的东西 .

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

    因为您知道您的字段没有错误,所以您可以执行此操作来迭代控件并终止装饰器

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

相关问题