1

介绍

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

ValidatesOnTargetUpdated="True"

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

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


问题

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

这会在加载之前导致错误,因为与文本的绑定TextBox还没有解决,所以在加载之前它不允许为空,并且文本的值为空。

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

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


代码 文本框用户控件的 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);
    }

和验证规则(#注意:它们在 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. 如果我以编程方式将控件加载事件中的 Text 依赖属性的值更改为大于最大值并将其更改回来,则装饰器仍然存在
  3. 如果我在加载后将文本更改为空值,然后将其改回,则装饰器仍然存在
  4. 如果我在视图模型的构造函数中更改绑定到文本的视图模型属性的值,装饰器仍然存在
  5. 如果我将绑定到视图模型的构造函数内的文本的视图模型属性的值更改为大于最大值的值,然后将其更改回来,装饰器仍然存在
  6. 如果我使用按钮将绑定到文本的视图模型属性的值更改为不同的值,然后将其更改回来,装饰器就会消失
  7. 如果我使用按钮将绑定到文本的视图模型属性的值更改为大于最大值的值,然后将其更改回来,装饰器就会消失

我仍然相当难过。我尝试过类似的方法,UpdateLayout()并尝试将装饰器移动到不同的控件并使用Validation.SetValidationAdornerSite. 我一直在努力,但我真的不知道该怎么做。

u_u


第二次编辑

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

这是可行的,但我讨厌这个想法,因为它的代码很糟糕。

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

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

u_u

4

3 回答 3

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);
    }
于 2014-05-06T07:04:23.810 回答
0

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

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

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

var dp = SystemCode.GetBindingExpression(TextBox.TextProperty);
dp.UpdateSource();
于 2012-06-19T19:04:31.967 回答
0

我有一个与此类似的问题,即使潜在的错误确实存在,装饰者也不会消失。

我发现的最终解决方法是通过调用强制更新布局

Control.UpdateLayout()

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

于 2020-06-02T16:48:48.177 回答