Подтвердить что ты не робот

Как подавить проверку, когда ничего не введено

Я использую привязку данных WPF к объектам, которые реализуют интерфейс IDataErrorInfo. В общем, мой код выглядит следующим образом:

Бизнес-объект:

public class Person : IDataErrorInfo 
{
  public string Name { get; set;}

  string IDataErrorInfo.this[string columnName]
  {
    if (columnName=="Name" && string.IsNullOrEmpty(Name))
      return "Name is not entered";
    return string.Empty;
  }  
}

Файл Xaml:

<TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=true}" />

Когда пользователь нажимает кнопку "Создать нового человека", выполняется следующий код:

DataContext = new Person();

Проблема заключается в том, что когда человек только что создан, его имя пустое, а WPF сразу рисует красную рамку и показывает сообщение об ошибке. Я хочу, чтобы он отображал ошибку только тогда, когда имя уже было отредактировано, а фокус потерян. Кто-нибудь знает, как это сделать?

4b9b3361

Ответ 1

Вы можете изменить свой класс для проверки ошибки проверки, только если свойство Name было изменено:

public class Person : IDataErrorInfo {

    private bool nameChanged = false;
    private string name;
    public string Name {
        get { return name; }
        set { 
            name = value;
            nameChanged = true;
        }
    }

//... skipped some code

    string IDataErrorInfo.this[string columnName] {
        get {
            if(nameChanged && columnName == "Name" && string.IsNullOrEmpty(Name)) 
                return "Name is not entered"; 
            return string.Empty;
        }
    }
}

Ответ 2

Есть другое решение, которое я нашел, но мне он не очень нравится. Вы должны очистить проверку при загрузке страницы.

Что я имею в виду, вы должны это сделать:

Validation.ClearInvalid(...) например, если у вас есть текстовое поле, которое вы не хотите проверять, следует вызвать

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty)) или что-то в этом роде.

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

Мне не понравилось решение, но это было лучшее, что я нашел. Я надеялся, что у wpf есть что-то "из коробки", которое сработало, но не нашло его.

Ответ 3

Я думаю, что подход @Станислав Князев правильный. Ваш комментарий о добавлении логики к бизнес-объекту также действителен. Чтобы иметь четкое разделение озабоченности, как насчет сохранения Лица в бизнес-слое (или слоя модели данных) и введения нового класса PersonVm с логикой представления. Для уровня VM мне нравится шаблон сдерживания больше, чем наследование, и на этом уровне я также реализую INotifyPropertyChanged, который также является свойством виртуальной машины, а не модели данных.

public class PersonVm : IDataErrorInfo, INotifyPropertyChanged
{
    private Person _person;

    public PersonVm( ) {
        // default constructor
        _person = new Person( );
        _dirty = false;
    }

    public PersonVm( Person p ) {
        // User this constructor when you get a Person from database or network
        _person = p;
        _dirty = false;
    }

    void fire( string prop ) {
        PropertyChanged( this, new PropertyChangedEventArgs( prop ) );
    }

    public string name {
        get { return _person.name; }
        set { _person.name = value; fire( "name" ); dirty = true; }
    }

    ...

    string IDataErrorInfo.this[string columnName] { 
        get {
            if( dirty ) return _person[columnName];
        }
    }

}

Идея состоит в том, чтобы поместить логику каждого слоя в соответствующий класс. На уровне модели данных вы выполняете проверку, которая касается только чистых данных. На уровне модели View вы добавляете логику, которая относится к модели View Model (а также к примечанию и другой логике View Model).

Ответ 4

Черт возьми, это заняло некоторое время, чтобы придумать, но, как всегда,... привязанные поведения к спасению.

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

Сначала удалите ValidatesOnDataErrors из привязки Xaml. Создайте поведение для элемента управления, который вы работаете (как показано ниже для TextBox), и в событии TextChanged (или любом другом событии) reset привязка к тому, что делает проверять ошибки данных. Простой действительно.

Таким образом, ваши сущности не должны меняться, ваш Xaml поддерживается достаточно чистым, и вы получаете свое поведение.

Здесь код поведения -

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

    namespace IDataErrorInfoSample
    {
        public static class DirtyStateBehaviours
        {


            public static string GetDirtyBindingProperty(DependencyObject obj)
            {
                return (string)obj.GetValue(DirtyBindingPropertyProperty);
            }

            public static void SetDirtyBindingProperty(DependencyObject obj, string value)
            {
                obj.SetValue(DirtyBindingPropertyProperty, value);
            }

            // Using a DependencyProperty as the backing store for DirtyBindingProperty.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DirtyBindingPropertyProperty =
                DependencyProperty.RegisterAttached("DirtyBindingProperty", typeof(string), typeof(DirtyStateBehaviours),
                new PropertyMetadata(new PropertyChangedCallback(Callback)));


            public static void Callback(DependencyObject obj,
                DependencyPropertyChangedEventArgs args)
            {
                var textbox = obj as TextBox;
                textbox.TextChanged += (o, s) =>
                {
                    Binding b = new Binding(GetDirtyBindingProperty(textbox));
                    b.ValidatesOnDataErrors = true;
                    textbox.SetBinding(TextBox.TextProperty, b);
                };

            }
        }
    }

И Xaml тоже довольно прямолинейный.

<Window x:Class="IDataErrorInfoSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:IDataErrorInfoSample"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow"
    Height="350"
    Width="525">

<Window.DataContext>
    <local:Person />
</Window.DataContext>
<StackPanel Margin="20">
    <TextBox Height="20"
             Margin="0,0,0,10"
             local:DirtyStateBehaviours.DirtyBindingProperty="Name"
             Text="{Binding Path=Name}">
    </TextBox>
    <Button Content="Go" />
</StackPanel>

HTH, Stimul8d.

Ответ 5

Возможно, это вариант для вас, чтобы перевести вашу проверку на вид: Вместо реализации IDataErrorInfo вы можете включить NotifyOnValidationError в своей привязке и добавить ValidationRule, который выполняет проверку. Для ValidationRules существует стандартный способ если это правило должно применяться, когда объект изменяется (а не значение свойства напрямую)

Это уже обеспечит визуальную обратную связь с пользователем (применяется ErrorTemplate). Если вам нужно больше, например, отключите некоторые кнопки и т.д., вы можете подключить событие Validation.Error-Event вашего вида к вашей ViewModel или BusinessEntity, с. вы можете определить там, если какая-либо ошибка присутствует.

Ответ 6

Я реализовал следующее решение:

 public class SkipValidationOnFirstLoadBehavior : Behavior<TextBox>
 {
        protected override void OnAttached()
        {
            AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
        }

        private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
        {
            //Execute only once
            AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;

            //Get the current binding
            BindingExpression  expression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
            if (expression == null) return;
            Binding parentBinding = expression.ParentBinding;

            //Create a new one and trigger the validation 
            Binding updated = new Binding(parentBinding.Path.Path);
            updated.ValidatesOnDataErrors = true;
            updated.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;  
            AssociatedObject.SetBinding(TextBox.TextProperty, updated);
        }
 }

Пример использования:

    <TextBox Text="{Binding Email}">
        <i:Interaction.Behaviors>
            <local:SkipValidationOnFirstLoadBehavior/>
        </i:Interaction.Behaviors>
    </TextBox>

Ответ 7

Я просто младший разработчик, у которого мало знаний, но я исправил его таким образом.

В моем классе validationresult я создал конструктор без параметров, чтобы вернуть действительный результат validationresult.

public  class NotEmptyValidation : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
      if (string.IsNullOrEmpty(value as string))
      {
          return new ValidationResult(false,"Veld kan niet leeg zijn");
      }

      return new ValidationResult(true,null);

 }
  public NotEmptyValidation() : base()
  {
      Validate();
  }


  public ValidationResult Validate()
  {
      return new ValidationResult(true,null);
  }
}

Мой код xaml выглядит следующим образом

<!--TEXTBOXES-->
                <TextBox Grid.Column="1" Grid.ColumnSpan="2" Margin="5">
                    <TextBox.Text>
                        <Binding Path="SelectedEntity.Code" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <val:NotEmptyValidation  />
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Margin="5">
                    <TextBox.Text>
                        <Binding Path="SelectedEntity.Name" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <val:NotEmptyValidation />
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>

Когда моя форма загружается, проверка не срабатывает, когда загружается окно, но если я очищаю текстовое поле, оно срабатывает.

Есть недостаток в этом, если я загружаю недопустимый Entity, у которого есть имя или код emty, проверка не срабатывает при загрузке окна, однако, когда вы заполняете текстовое поле и очищаете его. Но это не происходит, поскольку я проверяю все свои поля, когда создаю объект.

Это не идеальное решение, но оно работает для меня.