Украшение ошибки валидации не очищается, когда HasError имеет значение false - PullRequest
1 голос
/ 23 февраля 2012

Введение

Я создал DecimalTextBox пользовательский элемент управления, к которому прикреплены ValidationRule s для предотвращения пустых значений, имеют минимальный и максимальный диапазон и в нем есть обработчики событий для предотвращения недесятичных значений. Я использовал

ValidatesOnTargetUpdated="True"

на привязках, потому что я хочу, чтобы проверка активировалась немедленно (и у меня была проблема раньше, когда значения min и max менялись, но проверка не переоценивалась).

Нулевая проверка, которую я сделал, зависит от значения свойства зависимости «AllowNull»: если элемент управления указывает значение «истина», тогда элемент управления действителен, даже если значение равно нулю. Если ложь, то ноль не допускается. Значение по умолчанию для этого свойства False


ПРОБЛЕМА

Я устанавливаю AllowNull на true при использовании его в определенном пользовательском контроле. К сожалению, поскольку для ValidatesOnTargetUpdated установлено значение true, элемент управления проверяется до того, как xaml установит для AllowNull значение true, в то время как для него по умолчанию установлено значение false.

Это приводит к ошибке перед загрузкой, поскольку привязка к тексту TextBox также еще не разрешена, поэтому перед загрузкой не допускается нулевое значение, а значение текста равно нулю.

Это все нормально, потому что после загрузки проверка переоценивается с новым значением AllowNull (of true) и ошибка устраняется.

Однако красный знак подтверждения остается. Не совсем уверен, как от него избавиться.


КОД Xaml для текстового поля usercontrol:

<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) после загрузки, и он выдает false.

Я знаю, что если я уберу ValidatesOnTargetUpdated="True", красный цвет не появится, но мне это нужно. Так почему же валидация переоценивается, а украшение красной границы не исчезает?

Я мало что знаю о классе Validation или его статических методах, но есть ли что-то, что могло бы убрать украшение. Метод ClearInvalid не поможет, потому что у меня нет ошибки при его предоставлении.

Есть идеи?

u_u


EDIT

Я провел еще несколько расследований и обнаружил следующие вещи:

  1. Если я изменю текст после загрузки на величину, превышающую максимум, а затем вернусь обратно , ошибка исчезнет с номера
  2. Если я изменю значение свойства зависимости текста внутри элементов управления, программно загружу событие на величину, превышающую максимум и верну его обратно, рекламодатель все еще там .
  3. Если я изменил текст после загрузки на нулевое значение, а затем изменил его обратно, то рекламодатель все еще там .
  4. Если я изменю значение свойства viewmodel, привязанного к тексту внутри конструктора модели представления, рекламодатель все еще там
  5. Если я изменю значение свойства viewmodel, привязанного к тексту внутри конструктора модели представления, на значение, превышающее максимум, а затем вернусь обратно, рекламодатель все еще там .
  6. Если я изменю значение свойства viewmodel, привязанного к тексту с помощью кнопки, на другое значение, а затем вернусь обратно, , исчезнет
  7. Если я изменю значение свойства viewmodel, привязанного к тексту с помощью кнопки, на значение , превышающее максимальное значение , а затем вернусь обратно, , исчезнет

Я все еще довольно тупой.Я пробовал такие методы, как UpdateLayout(), и пытался переместить украшение на разные элементы управления и вернуть его обратно, используя Validation.SetValidationAdornerSite.Има продолжает пытаться, но я не знаю, что делать на самом деле.

u_u


2-й РЕДАКТИРОВАТЬ

Хорошо, что я сделал за это времяпоместил AdornerDecorator вокруг TextBox, а затем в событии загрузки текстовых полей измените максимум на 1 и значение на 2, а затем измените его обратно, чтобы обновить текстовое поле.

Это работало, но яНенавистная идея вызывает ее ужасный код.

Однако это решение больше не жизнеспособно.У меня был некоторый код, который выполнял работу над измененным свойством одного из свойств, привязанных к одному из этих DecimalTextBoxes.Затем, поскольку свойство изменялось и изменялось обратно в событии загрузки, другой код также выполнялся и вызывал ошибки.Я должен найти лучшее решение, чем это.

Кто-нибудь знает, как обновить валидатора?

u_u

Ответы [ 2 ]

2 голосов
/ 06 мая 2014

Вот несколько способов решения этой проблемы, поскольку я столкнулся с ней и не смог ничего найти на ней, я подозреваю, что это ошибка где-то в структуре, вызванная состоянием гонки, но не смог найти что-либо для поддержкичто вверх.

Отражение на приватных полях (гадость)

Поскольку вы знаете, что в вашем поле нет ошибок, вы можете сделать это, чтобы перебрать элементы управления и убить украшателей

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);
    }
0 голосов
/ 19 июня 2012

Попытайтесь удалить ошибку по событию LayoutUpdated вашего элемента управления (установите флаг для события, чтобы сделать это только один раз)

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

, а затем пересмотрите правила валидации (обновляя привязки).

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