2009-10-01 4 views
30

IDataErrorInfo 인터페이스를 구현하는 엔티티와 WPF 데이터 바인딩을 사용합니다. 일반적으로 내 코드는 다음과 같습니다아무 것도 입력하지 않으면 유효성 검사를 억제하는 방법

비즈니스 엔티티 :

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 즉시은 빨간색 프레임을 그리고 오류 메시지를 표시합니다. 이름이 이미 편집되고 포커스가 손실 된 경우에만 오류가 표시되기를 원합니다. 아무도이 일을하는 방법을 알고 있습니까?

+3

저는 해킹이없는 해결책을 원할 경우이 질문에 현상금을드립니다. –

+0

InitializeComponent()가 호출되기 전에 Person을 생성 할 수 없습니까? – markmnl

+1

좋은 해킹없는 솔루션을 얻으려면 bounty를 추가했습니다. –

답변

15

당신은 이름 속성이 지금까지 변경 한 경우에만 유효성 검사 오류를 발생하기 위해 사람 클래스를 변경할 수 있습니다

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; 
     } 
    } 
} 
+5

예, 가능하지만 WPF 바인딩을 조정하고 비즈니스 엔티티를 변경하고 싶습니다.WPF 전문가가 아니기 때문에 그러한 문제에 대한 상대적으로 쉬운 해결책이 있기를 바랍니다. 양식이 방금 열려있을 때 모든 필드에 경고를 표시하지 않는 것이 전형적인 동작 인 것 같습니다. –

+2

XAML 또는 바인딩 설정 수준에서 제어 할 수 없다고 생각합니다. 데이터가 정확하거나 그렇지 않습니다. IDataErrorInfo가 유효성을 검사합니다. 또는 "if (columnName =="Name "&& Name ==" ")"를 검사하여 편집시 유효하지 않은 빈 문자열로 바뀌는 초기 "null"을 처리 할 수 ​​있습니다. 다른 어떤 생각도 할 수 없다. –

+0

@AlexKofman 나는 그것이 오래 된 포스트라는 것을 알고있다. 그러나 이것은 내가 지금 막 달려 드는 것이다. 이미 IDataErrorInfo 인터페이스를 구현해야하는 경우 비즈니스 엔티티를 변경하는 것과 관련이 없습니다. Uri이 제안한 것처럼 ViewModel 뒤에 객체를 배치하는 것이 좋습니다. 그리고 프리젠 테이션 로직을 배치하십시오. – Bronumski

4

이 내가 찾은 또 다른 솔루션입니다하지만 난 그것을 많이 좋아하지 않습니다. 페이지로드시 유효성 검사를 지워야합니다. 당신이 그나마 텍스트 상자 그런

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty)) 또는 뭔가를 호출 해 검증 할 경우 예를 들어

Validation.ClearInvalid(...) : 무슨 뜻인지

는이 작업을 수행해야합니다.

유효성 검사를 지우고 싶은 모든 컨트롤에 대해 이렇게해야합니다.

나는 해결책을 좋아하지 않았지만 그것이 내가 찾은 최고였다. 나는 wpf가 작동하지만 "찾지 못했던"무엇인가를 가지고 있기를 바랐다.

3

젠장, 생각만큼 시간이 좀 걸렸지 만 언제나처럼 ... 구조에 대한 행동을 취했습니다.

본질적으로보고있는 것은 더티 상태 추적입니다. ViewModel을 사용하여이 작업을 수행하는 방법은 여러 가지가 있지만 엔티티를 변경하지 않으려는 경우 가장 좋은 방법은 비헤이비어를 사용하는 것입니다.

우선 Xaml 바인딩에서 ValidatesOnDataErrors를 제거하십시오. 작업중인 컨트롤 (아래) 및 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

바인딩 대상의 이름을 복제해야하고 TextChanged 이벤트에 대한 이벤트 핸들러를 제거하는 것을 잊지 않으면 텍스트 상자에 입력 할 때마다 새 바인딩이있게됩니다. –

3

유효성 검사를보기로 이동하는 옵션이 있습니다. IDataErrorInfo를 구현하는 대신 바인딩에서 NotifyOnValidationError를 활성화하고 검사를 수행하는 ValidationRule을 추가 할 수 있습니다. ValidationRules의 경우 standard way to control, if the rule should be applied when the object changes (직접 속성 값이 아님)

이것은 시각적 피드백을 사용자에게 제공합니다 (ErrorTemplate이 적용됩니다). 더 필요하면 예 : 몇몇 버튼을 비활성화 시키면 View의 Validation.Error-Event를 ViewModel 또는 BusinessEntity, s.th에 연결할 수 있습니다. 오류가 있으면 그곳을 확인할 수 있습니다.

4

@ Stanislav Kniazev 방식이 올바른 것 같습니다. 비즈니스 오브젝트에 논리를 추가하지 않는다는 것에 대한 귀하의 의견도 유효합니다. 우려를 깨끗하게 분리하려면 비즈니스 계층 (또는 데이터 모델 계층)에 Person을 유지하고 뷰 논리를 사용하여 PersonVm 클래스를 새로 도입하는 방법. VM 레이어의 경우 상속보다 견제 패턴이 더 좋으며이 레이어에서는 데이터 모델이 아닌 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]; 
     } 
    } 

} 

아이디어는 각 계층의 논리를 적절한 클래스에 배치하는 것입니다. 데이터 모델 계층에서는 순수 데이터에만 관련된 유효성 검사를 수행합니다. 모델보기 계층에서는보기 모델과 관련된 논리를 추가하고 (예 : 공지 및 다른보기 모델 논리) 추가합니다. 사용의

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

예 :

+0

동의합니다. 엔티티를 뷰에 직접 노출하지 않아야합니다. 비즈니스 개체 변경에 대한 요점은 IDataErrorInfo 인터페이스로 이미 더러워야 할 때입니다. – Bronumski

2

나는 다음과 같은 솔루션을 구현했습니다

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

나는 많은 지식이없는 단지 주니어 개발자입니다을하지만 난 이런 식으로 해결 .

유효한 유효성 검사 결과를 반환하는 매개 변수가없는 생성자를 만들었습니다.

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> 

과 같은 경우 내 양식로드, 검증 나던 화재 때 창이로드,하지만 난 텍스트 상자를 취소하는 경우, 그것은 불을한다.

emty 이름이나 코드가있는 유효하지 않은 엔터티를로드하면 창을로드 할 때 유효성 검사가 실행되지 않지만 텍스트 상자에 내용을 채우면 지워집니다. 엔티티를 만들 때 모든 필드의 유효성을 검사하므로 실제로 발생하지는 않습니다.

완벽한 솔루션은 아니지만 잘 작동합니다.

관련 문제