Как подавить проверку, когда ничего не введено - PullRequest
31 голосов
/ 01 октября 2009

Я использую привязку данных 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 немедленно рисует красную рамку и показывает сообщение об ошибке. Я хочу, чтобы он показывал ошибку только тогда, когда имя уже отредактировано и фокус потерян. Кто-нибудь знает способ сделать это?

Ответы [ 8 ]

16 голосов
/ 01 октября 2009

Вы можете изменить свой класс персонажа на ошибку проверки достоверности, только если свойство 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;
        }
    }
}
4 голосов
/ 06 февраля 2012

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

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

}

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

4 голосов
/ 15 сентября 2010

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

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

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

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

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

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

3 голосов
/ 06 февраля 2012

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

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

3 голосов
/ 16 ноября 2010

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

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

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

Таким образом, ваши сущности не должны меняться, ваш 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.

2 голосов
/ 18 июля 2013

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

 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>
1 голос
/ 16 сентября 2014

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

В моем классе validationresult я создал конструктор без параметров для возврата правильного результата validation.

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>

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

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

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

0 голосов
/ 23 марта 2019

Я считаю, что такое поведение также является хорошим решением. При необходимости он удаляет ErrorTemplate в TextBox, а также поддерживает несколько «допустимых» недопустимых значений (вы также можете улучшить его, сделав ValidInputs свойством зависимости).

public class NotValidateWhenSpecified : Behavior<TextBox>
{
    private ControlTemplate _errorTemplate;

    public string[] ValidInputs { get; set; } = { string.Empty };

    protected override void OnAttached()
    {
        AssociatedObject.TextChanged += HideValiationIfNecessary;

        _errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
        Validation.SetErrorTemplate(AssociatedObject, null);
    }        

    protected override void OnDetaching()
    {
        AssociatedObject.TextChanged -= HideValiationIfNecessary;
    }

    private void HideValiationIfNecessary(object sender, TextChangedEventArgs e)
    {
        if (ValidInputs.Contains(AssociatedObject.Text))
        {                
            if (_errorTemplate != null)
            {
                _errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
                Validation.SetErrorTemplate(AssociatedObject, null);
            }                
        }
        else
        {
            if (Validation.GetErrorTemplate(AssociatedObject) != _errorTemplate)
            {
                Validation.SetErrorTemplate(AssociatedObject, _errorTemplate);
            }                
        }
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...