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