MVVM을 통한 적절한 검증
경고:매우 길고 상세한 투고입니다.
MVVM을 사용할 때 WPF에서 검증합니다.많은 것을 읽고, SO에 관한 질문도 많이 보고, 여러가지 어프로치도 해봤지만, 어느 시점에서는 모든 것이 좀 허술한 느낌이 들어, 어떻게 하면 좋을지 정말 모르겠어™
이상적으로는 를 사용하여 뷰 모델에서 모든 검증을 수행하고자 합니다. 그래서 그렇게 했습니다.그러나 이 솔루션이 전체 검증 토픽에 대한 완전한 솔루션이 될 수 없는 다양한 측면이 있습니다.
상황
하다시시시시시 츠2개의 , 2개의 텍스트박스는 2개의 박스에는 2개의 박스가 .string
★★★★★★★★★★★★★★★★★」int
뷰 모델에 각각 속성이 있습니다., 단추가 , 단추가 있어요.ICommand
.
검증에는, 다음의 2개의 선택지가 있습니다.
- 텍스트 상자의 값이 변경될 때마다 검증을 자동으로 실행할 수 있습니다.따라서 사용자는 잘못된 내용을 입력했을 때 즉각적인 응답을 받습니다.
- 한 걸음 더 나아가 오류가 발생했을 때 버튼을 비활성화할 수 있습니다.
- 또는 버튼을 눌렀을 때만 명시적으로 검증을 실행하고 해당되는 경우 모든 오류를 표시할 수 있습니다.여기서 오류에 대한 버튼을 비활성화할 수 없습니다.
이상적으로는 선택지 1을 구현하고 싶습니다.활성화 된 일반 데이터 바인딩의 경우 이것이 기본 동작입니다.따라서 텍스트가 변경되면 바인딩에 의해 소스가 업데이트되고 이 바인딩에 의해IDataErrorInfo
★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★ 오류는 뷰로 보고됩니다.직직아아
뷰 모델의 유효성 검사 상태
흥미로운 점은 뷰 모델(이 경우 버튼)에 오류가 있는지 확인하는 것입니다. ★★★IDataErrorInfo
이 기능은 주로 뷰에 오류를 보고하기 위해 사용됩니다.따라서 뷰는 에러가 있는지 여부를 쉽게 확인하고 표시할 수 있으며 를 사용하여 주석을 표시할 수도 있습니다.또한 검증은 항상 단일 속성을 보고 이루어집니다.
따라서 뷰 모델에 오류가 있거나 검증에 성공했는지 확인하는 것은 어렵습니다. '일부러'를 입니다.IDataErrorInfo
뷰 모델 자체의 모든 속성에 대한 유효성 검사를 수행합니다.요.IsValid
을 사용하면 비활성화에도 할 수 .이 기능을 사용하면 명령어를 비활성화할 때도 쉽게 사용할 수 있습니다.단점은 모든 속성에 대해 검증을 너무 자주 실행할 수 있지만 대부분의 검증은 단순히 성능을 저하시키지 않기 위해 충분해야 한다는 것입니다.또 다른 해결책은 검증을 사용하여 어떤 속성이 오류를 발생시켰는지 기억하고 그것만 확인하는 것이지만, 이는 대부분의 경우 다소 복잡하며 불필요한 것으로 보입니다.
을 사용하다 IDataErrorInfo
는 모든 속성에 대한 검증을 제공하며 뷰 모델 자체의 인터페이스를 사용하여 개체 전체에 대한 검증을 실행할 수 있습니다.다음 중 하나:
바인딩 예외
뷰 모델은 속성에 실제 유형을 사용합니다.따라서 이 예에서 정수 속성은 실제 값입니다.int
. 보기에서 사용되는 텍스트 상자는 기본적으로 텍스트만 지원합니다.따라서 에 바인딩할 때int
뷰 모델에서 데이터 바인딩 엔진은 자동으로 유형 변환을 수행하거나 최소한 시도합니다.숫자를 의미하는 텍스트 상자에 텍스트를 입력할 수 있는 경우 유효한 숫자가 항상 포함되어 있지 않을 수 있습니다.하여 를 .FormatException
.
전망 측면에서는 그것을 쉽게 볼 수 있습니다.바인딩 엔진으로부터의 예외는 자동적으로 WPF 에 의해서 검출되어 에러로서 표시됩니다.세터로 발생하는 예외에 대해서는, 이네이블로 할 필요도 없습니다.에러 메시지에는 범용 텍스트가 포함되어 있기 때문에 문제가 있을 수 있습니다.이 문제는 핸들러를 사용하여 발생되는 예외를 검사하고 소스 속성을 확인한 후 일반적이지 않은 오류 메시지를 생성함으로써 해결되었습니다.그 모든 것이 내 자신의 Binding 마크업 확장에 반영되어 내가 필요한 모든 디폴트를 가질 수 있게 되었다.
그래서 경치가 좋아요.사용자가 오류를 발생시키고 일부 오류 피드백을 확인하여 수정할 수 있습니다.그러나 뷰 모델은 손실됩니다.바인딩 엔진에서 예외가 발생했기 때문에 소스는 업데이트되지 않았습니다.따라서 뷰 모델은 사용자에게 표시되는 값이 아닌 이전 값을 그대로 유지합니다.IDataErrorInfo
검증은 분명히 적용되지 않습니다.
더욱이 뷰 모델이 이를 알 수 있는 좋은 방법이 없습니다.적어도, 나는 아직 이것에 대한 좋은 해결책을 찾지 못했다.뷰가 뷰 모델에 오류가 발생했음을 보고하도록 하는 것이 가능합니다.뷰 모델은 뷰의 상태를 먼저 확인할 수 있도록 속성을 뷰 모델에 다시 바인딩하여 이 작업을 수행할 수 있습니다(직접 가능하지 않음).
하나의 은 에서 입니다.Binding.UpdateSourceExceptionFilter
뷰 모델에도 통지할 수 있도록 하겠습니다.뷰 모델에서는 바인딩이 이러한 내용을 보고하기 위한 인터페이스를 제공할 수도 있으므로 일반적인 유형별 오류 메시지 대신 사용자 지정 오류 메시지를 사용할 수 있습니다.그러나 이렇게 하면 뷰에서 뷰 모델로의 결합이 강화됩니다.이러한 결합은 일반적으로 피하고 싶습니다.
하나의은 입력된, 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋한 밋밋밋한 밋밋밋밋밋한 .string
대신 뷰 모델에서 변환을 수행합니다.이렇게 하면 모든 검증이 뷰 모델로 이행될 뿐만 아니라 데이터 바인딩 엔진이 일반적으로 처리하는 작업의 엄청난 중복을 의미하기도 합니다.게다가 뷰 모델의 의미론도 변화할 것이다.뷰는 뷰 모델이 아닌 뷰 모델을 위해 구축됩니다.물론 뷰 모델의 설계는 뷰가 무엇을 할 수 있는지에 따라 다르지만 뷰가 무엇을 할 수 있는지에 대한 일반적인 자유는 여전히 있습니다. 뷰 되어 있습니다.int
숫자가 있기 때문에 속성. 이제 텍스트 상자(이러한 모든 문제를 표시)를 사용하거나 기본적으로 숫자와 함께 작동하는 항목을 사용할 수 있습니다.아니요, 속성 유형을 다음으로 변경합니다.string
나한테는 선택권이 없어
결국, 이것은 견해상의 문제입니다.뷰(및 해당 데이터 바인딩 엔진)는 뷰 모델에 작업할 적절한 값을 제공하는 역할을 합니다.그러나 이 경우 뷰 모델에 오래된 부동산 가치를 무효화하도록 지시할 수 있는 좋은 방법은 없는 것 같습니다.
바인딩 그룹
결속 그룹은 내가 이것을 해결하려고 했던 한 가지 방법이다.바인딩 그룹에는 다음을 포함한 모든 검증을 그룹화할 수 있는 기능이 있습니다.IDataErrorInfo
예외를 두었습니다.뷰 모델에서 사용할 수 있는 경우 이러한 모든 검증 소스의 검증 상태를 확인하는 수단도 있습니다(예: 를 사용).
디폴트로는 바인딩 그룹은 위의 선택지2 를 실장합니다.바인딩을 명시적으로 갱신하고 기본적으로 커밋되지 않은 상태를 추가합니다.따라서 버튼을 클릭하면 이러한 변경을 커밋하고 소스 업데이트 및 모든 검증을 트리거하여 성공 시 단일 결과를 얻을 수 있습니다.따라서 명령어의 동작은 다음과 같습니다.
if (bindingGroup.CommitEdit())
SaveEverything();
CommitEdit
는 모든 검증이 성공한 경우에만 true를 반환합니다.시간이 걸릴 것이다IDataErrorInfo
또한 바인딩 예외도 확인합니다.이것이 2번 선택지에 대한 완벽한 해결책인 것 같습니다.조금 귀찮은 것은 바인딩으로 바인딩 그룹을 관리하는 것뿐이지만, 이것을 주로 담당하는 것을 스스로 구축했습니다.
바인딩 그룹이 존재하는 경우 바인딩은 기본적으로 명시적입니다.바인딩 그룹을 사용하여 위의 선택 항목1을 구현하려면 기본적으로 트리거를 변경해야 합니다.어차피 커스텀바인딩 확장이 있기 때문에, 이것은 비교적 심플합니다.그냥으로 설정했습니다.LostFocus
모두를 위해.
따라서 텍스트 필드가 변경될 때마다 바인딩이 계속 업데이트됩니다.할 수 은 예외를).IDataErrorInfo
정상적으로 실행됩니다.갱신할 수 없는 경우, 뷰는 계속 표시할 수 있습니다.기본 됩니다.CommitEdit
(단, 커밋할 필요는 없습니다만), 계속 할 수 있는지를 확인하기 위해서, 합계 검증 결과를 취득합니다.
이 방법으로는 버튼을 쉽게 비활성화할 수 없을 수도 있습니다.적어도 뷰 모델에서는 사용할 수 없습니다.검증을 반복해서 확인하는 것은 명령어 상태를 업데이트하는 것만으로는 그다지 좋은 방법이 아닙니다.또한 바인딩 엔진 예외가 발생해도(버튼을 비활성화해야 함), 버튼을 다시 활성화하기 위해 사라졌을 때에도 뷰 모델에 알림이 표시되지 않습니다.를 사용하여 뷰의 버튼을 비활성화하는 트리거를 추가할 수 있으므로 불가능하지 않습니다.
해결책?
따라서 전반적으로 이것이 완벽한 솔루션인 것 같습니다.근데 그게 뭐가 문제죠?솔직히 잘 모르겠어요.바인딩 그룹은 일반적으로 작은 그룹에서 사용되는 것처럼 보이는 복잡한 것으로, 단일 뷰에 여러 바인딩 그룹이 있을 수 있습니다.뷰 전체에 대해 하나의 큰 바인딩 그룹을 사용함으로써 내 검증이 확실하게 이루어지도록 함으로써 마치 내가 그것을 남용하는 것처럼 느껴집니다.이 모든 상황을 해결할 수 있는 더 좋은 방법이 있을 거라고 생각합니다.저만 이런 문제를 안고 있을 수는 없으니까요그리고 지금까지 MVVM 검증에 바인딩 그룹을 사용하는 사람은 거의 없었기 때문에 이상할 뿐입니다.
그렇다면 바인딩 엔진의 예외를 체크하면서 MVVM을 사용하여 WPF에서 검증을 수행하는 올바른 방법은 무엇일까요?
나의 솔루션(/hack)
우선 의견 감사합니다!, ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★를 사용하고 있습니다.IDataErrorInfo
이치노개인적으로는 이것이 검증 작업에 가장 편리한 유틸리티라고 생각합니다.아래 답변에서 Sheridan이 제시한 것과 유사한 유틸리티를 사용하고 있기 때문에 유지 보수도 문제 없습니다.
결국 문제는 뷰 모델이 언제 발생했는지 알 수 없는 구속력 있는 예외 문제로 귀결되었습니다.위에서 설명한 바와 같이 결속 그룹에서는 대응할 수 있었지만, 그다지 마음이 편하지 않았기 때문에 반대했습니다.그래서 내가 뭘 했을까?
와 같이,의 '바인딩'의 '의 '바인딩의 '바인딩'을 .UpdateSourceExceptionFilter
여기서 바인딩 식에서 뷰 모델에 대한 참조를 얻을 수 있습니다.그러면 인터페이스가 생성됩니다.IReceivesBindingErrorInformation
뷰 모델을 바인딩 오류에 대한 가능한 수신기로 등록합니다.그런 다음 이를 사용하여 뷰 모델에 바인딩 경로와 예외를 전달합니다.
object OnUpdateSourceExceptionFilter(object bindExpression, Exception exception)
{
BindingExpression expr = (bindExpression as BindingExpression);
if (expr.DataItem is IReceivesBindingErrorInformation)
{
((IReceivesBindingErrorInformation)expr.DataItem).ReceiveBindingErrorInformation(expr.ParentBinding.Path.Path, exception);
}
// check for FormatException and produce a nicer error
// ...
}
뷰 모델에서는 경로의 바인딩 표현식에 대해 알림을 받을 때마다 다음과 같이 기억합니다.
HashSet<string> bindingErrors = new HashSet<string>();
void IReceivesBindingErrorInformation.ReceiveBindingErrorInformation(string path, Exception exception)
{
bindingErrors.Add(path);
}
★★★★★★★★★★★★★★★★★★★★.IDataErrorInfo
속성을 재검증합니다.바인딩이 기능하고 해시 세트에서 속성을 클리어할 수 있습니다.
뷰 모델에서 해시 세트에 항목이 포함되어 있는지 확인하고 데이터를 완전히 검증해야 하는 작업을 중단할 수 있습니다.뷰에서 뷰 모델로의 결합으로 인해 최적의 솔루션은 아닐 수 있지만, 이 인터페이스를 사용하면 적어도 문제가 다소 줄어듭니다.
경고: 긴 답변도 있습니다.
용 the the the를 .IDataErrorInfo
인터페이스 검증은 실시하지만, 필요에 따라서 커스터마이즈 하고 있습니다.당신의 문제도 몇 가지 해결된다는 것을 알게 될 것입니다.질문의 한 가지 차이점은 기본 데이터 유형 클래스에서 구현한다는 것입니다.
지적하신 바와 같이 이 인터페이스는 한 번에 하나의 속성만 취급하지만, 오늘날과 같은 시대에는 그다지 좋지 않습니다.그래서 대신 사용할 컬렉션 속성을 추가했습니다.
protected ObservableCollection<string> errors = new ObservableCollection<string>();
public virtual ObservableCollection<string> Errors
{
get { return errors; }
}
외부 오류를 표시할 수 없는 문제에 대처하기 위해(뷰에서는 표시하지만 내 뷰 모델에서는 표시) 다른 컬렉션 속성을 추가했습니다.
protected ObservableCollection<string> externalErrors = new ObservableCollection<string>();
public ObservableCollection<string> ExternalErrors
{
get { return externalErrors; }
}
는 i i나 an an an i i i i i i i 。HasError
내 컬렉션을 보는 속성:
public virtual bool HasError
{
get { return Errors != null && Errors.Count > 0; }
}
에 의해, 을 「」에 할 수 .Grid.Visibility
「」의 BoolToVisibilityConverter
a를 :Grid
수집 컨트롤이 내장되어 있는 경우 오류를 표시합니다., 이 하면, 한한을 변경할 수 있습니다.Brush
로로 합니다.Red
강조하다Converter
★★★★★★★★★★★★★★★★?
으로 각 유형 모델 에서 '하다'를 .Errors
및 에 대해 설명합니다.Item
인이이이이이이이 ( ) 。
public override ObservableCollection<string> Errors
{
get
{
errors = new ObservableCollection<string>();
errors.AddUniqueIfNotEmpty(this["Name"]);
errors.AddUniqueIfNotEmpty(this["EmailAddresses"]);
errors.AddUniqueIfNotEmpty(this["SomeOtherProperty"]);
errors.AddRange(ExternalErrors);
return errors;
}
}
public override string this[string propertyName]
{
get
{
string error = string.Empty;
if (propertyName == "Name" && Name.IsNullOrEmpty()) error = "You must enter the Name field.";
else if (propertyName == "EmailAddresses" && EmailAddresses.Count == 0) error = "You must enter at least one e-mail address into the Email address(es) field.";
else if (propertyName == "SomeOtherProperty" && SomeOtherProperty.IsNullOrEmpty()) error = "You must enter the SomeOtherProperty field.";
return error;
}
}
AddUniqueIfNotEmpty
은 관습적인 입니다.extension
한다.'는 것을 말합니다.검증할 각 속성을 차례로 호출하고 중복 오류를 무시한 채 이들로부터 컬렉션을 컴파일합니다.
「 」의 ExternalErrors
데이터 클래스에서 검증할 수 없는 것을 검증할 수 있습니다.
private void ValidateUniqueName(Genre genre)
{
string errorMessage = "The genre name must be unique";
if (!IsGenreNameUnique(genre))
{
if (!genre.ExternalErrors.Contains(errorMessage)) genre.ExternalErrors.Add(errorMessage);
}
else genre.ExternalErrors.Remove(errorMessage);
}
가 int
필드, 나는 커스텀을 사용하는 경향이 있다.IsNumeric AttachedProperty
★★★★★★★★★★★★★★★★의 경우TextBox
예를 들어, 나는 그들이 이런 종류의 실수를 하도록 놔두지 않는다.나는 항상 그런 일이 일어나게 놔두고 그것을 고치는 것보다 그것을 멈추는 것이 낫다고 느낀다.
전반적으로 나는 WPF에서의 나의 검증 능력에 매우 만족하고 전혀 부족함이 없다.
마지막으로, 그리고 완성도를 위해, 저는 당신에게 경고해야 한다고 느꼈습니다.INotifyDataErrorInfo
이 추가 기능의 일부를 포함하는 인터페이스입니다.상세한 것에 대하여는, MSDN 의 「Interface」페이지를 참조해 주세요.
업데이트 >>>
ㅇㅇ, ㅇㅇ.ExternalErrors
하지 않아서 ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★」IsGenreNameUnique
을 알 수 .LinQ
의 모든 부분에서Genre
컬렉션의 데이터 항목을 사용하여 개체 이름이 고유한지 여부를 확인합니다.
private bool IsGenreNameUnique(Genre genre)
{
return Genres.Where(d => d.Name != string.Empty && d.Name == genre.Name).Count() == 1;
}
의 객의에 int
/string
문제, 데이터 클래스에서 이러한 오류가 발생하는 유일한 방법은 모든 속성을 다음과 같이 선언하는 것입니다.object
을 두 로 늘릴 수 .다음과 같이 자산을 두 배로 늘릴 수 있습니다.
public object FooObject { get; set; } // Implement INotifyPropertyChanged
public int Foo
{
get { return FooObject.GetType() == typeof(int) ? int.Parse(FooObject) : -1; }
}
'만약에'가Foo
와 코 and was was was was was was was에 사용되었습니다.FooObject
에서 사용되었습니다.Binding
이치노
public override string this[string propertyName]
{
get
{
string error = string.Empty;
if (propertyName == "FooObject" && FooObject.GetType() != typeof(int))
error = "Please enter a whole number for the Foo field.";
...
return error;
}
}
그렇게 하면 요구 사항을 충족할 수 있지만 추가할 코드가 많아집니다.
내 생각에 문제는 너무 많은 장소에서 일어나는 검증에 있다., 모든 을 또, 든, 든, 고, 고에 기입하고 하고 있었습니다.ViewModel
하지만 그 모든 숫자의 바인딩이 내 마음을ViewModel
crazycrazy. crazy 。
저는 이 문제를 실패하지 않는 바인딩을 만들어서 해결했습니다.바인딩이 항상 성공적일 경우 유형 자체가 오류 조건을 정상적으로 처리해야 합니다.
실패 가능한 값 유형
우선 실패한 변환을 정상적으로 지원하는 범용 유형을 만듭니다.
public struct Failable<T>
{
public T Value { get; private set; }
public string Text { get; private set; }
public bool IsValid { get; private set; }
public Failable(T value)
{
Value = value;
try
{
var converter = TypeDescriptor.GetConverter(typeof(T));
Text = converter.ConvertToString(value);
IsValid = true;
}
catch
{
Text = String.Empty;
IsValid = false;
}
}
public Failable(string text)
{
Text = text;
try
{
var converter = TypeDescriptor.GetConverter(typeof(T));
Value = (T)converter.ConvertFromString(text);
IsValid = true;
}
catch
{
Value = default(T);
IsValid = false;
}
}
}
유효하지 않은 입력 문자열(두 번째 생성자)로 인해 유형이 초기화에 실패하더라도 비활성 상태는 비활성 텍스트와 함께 조용히 저장됩니다.이는 입력이 잘못된 경우에도 바인딩 라운드 트립을 지원하기 위해 필요합니다.
범용 값 변환기
범용 값 변환기는 위의 유형을 사용하여 쓸 수 있습니다.
public class StringToFailableConverter<T> : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(Failable<T>))
throw new InvalidOperationException("Invalid value type.");
if (targetType != typeof(string))
throw new InvalidOperationException("Invalid target type.");
var rawValue = (Failable<T>)value;
return rawValue.Text;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value.GetType() != typeof(string))
throw new InvalidOperationException("Invalid value type.");
if (targetType != typeof(Failable<T>))
throw new InvalidOperationException("Invalid target type.");
return new Failable<T>(value as string);
}
}
XAML 핸디 컨버터
범용 인스턴스의 작성과 사용은 XAML에서는 번거로우므로 공통 컨버터의 스태틱인스턴스를 만듭니다
public static class Failable
{
public static StringToFailableConverter<Int32> Int32Converter { get; private set; }
public static StringToFailableConverter<double> DoubleConverter { get; private set; }
static Failable()
{
Int32Converter = new StringToFailableConverter<Int32>();
DoubleConverter = new StringToFailableConverter<Double>();
}
}
다른 값 유형은 쉽게 확장할 수 있습니다.
사용.
은 아주 .유형만 변경하면 됩니다.int
로로 합니다.Failable<int>
: : :
뷰 모델
public Failable<int> NumberValue
{
//Custom logic along with validation
//using IsValid property
}
XAML
<TextBox Text="{Binding NumberValue,Converter={x:Static local:Failable.Int32Converter}}"/>
이 방법은 동일한 유효한 메커니즘을 사용할 수 있습니다. 하 동 커 용 습 있 할 this way 니 수 사,니( validation you can the다메 use검즘을렇IDataErrorInfo
★★★★★★★★★★★★★★★★★」INotifyDataErrorInfo
or anything else) in 또는 다른 것)에ViewModel
by checking the 체크함으로써IsValid
소유물. If 한다면IsValid
is true, you can directly use the 네, 직접 사용할 수 있습니다.Value
.
네, 당신이 찾고 있는 답을 알아냈다고 생각합니다.좋아, 네가 찾던 답을 찾은 것 같은데...
명하기 츠요시
...
MVVM에 대해 "표준"으로 간주하거나 최소한 시도된 표준으로 간주하는 것이 가장 정확하거나 "인증"되었다고 생각합니다.
하지만 시작하기 전에..MVVM에 대해 익숙한 개념을 변경해야 합니다.
게다가 뷰 모델의 의미도 바뀝니다.뷰는 뷰 모델이 아닌 뷰 모델을 위해 구축됩니다.물론 뷰 모델의 설계는 뷰가 무엇을 할지에 따라 다르지만 뷰가 어떻게 그렇게 할지는 여전히 자유롭습니다."
그 단락이 당신 문제의 근원입니다.- 왜? - 왜?
View-Model(보기 모델) View(보기)
그것은 여러모로 잘못되었다 - 내가 너에게 아주 간단하게 증명해 보이겠지만..
다음과 같은 속성이 있는 경우:
public Visibility MyPresenter { get...
죠?Visibility
에에도 면면 면면 면면 면면 면면?
유형 자체와 속성에 지정될 이름은 보기에 대해 확실하게 구성됩니다.
내 경험에 따르면 MVVM에는 두 가지 구별 가능한 View-Model 카테고리가 있습니다.
- 발표자 뷰 모델 - 버튼, 메뉴, 탭 항목 등에 연결됨...
- 엔티티 뷰 모델 - 엔티티 데이터를 화면에 표시하는 컨트롤에 고정됩니다.
이 두 가지는 완전히 다른 관심사입니다.
이제 솔루션에 대해 설명하겠습니다.
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class VmSomeEntity : ViewModelBase, INotifyDataErrorInfo
{
//This one is part of INotifyDataErrorInfo interface which I will not use,
//perhaps in more complicated scenarios it could be used to let some other VM know validation changed.
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
//will hold the errors found in validation.
public Dictionary<string, string> ValidationErrors = new Dictionary<string, string>();
//the actual value - notice it is 'int' and not 'string'..
private int storageCapacityInBytes;
//this is just to keep things sane - otherwise the view will not be able to send whatever the user throw at it.
//we want to consume what the user throw at us and validate it - right? :)
private string storageCapacityInBytesWrapper;
//This is a property to be served by the View.. important to understand the tactic used inside!
public string StorageCapacityInBytes
{
get { return storageCapacityInBytesWrapper ?? storageCapacityInBytes.ToString(); }
set
{
int result;
var isValid = int.TryParse(value, out result);
if (isValid)
{
storageCapacityInBytes = result;
storageCapacityInBytesWrapper = null;
RaisePropertyChanged();
}
else
storageCapacityInBytesWrapper = value;
HandleValidationError(isValid, "StorageCapacityInBytes", "Not a number.");
}
}
//Manager for the dictionary
private void HandleValidationError(bool isValid, string propertyName, string validationErrorDescription)
{
if (!string.IsNullOrEmpty(propertyName))
{
if (isValid)
{
if (ValidationErrors.ContainsKey(propertyName))
ValidationErrors.Remove(propertyName);
}
else
{
if (!ValidationErrors.ContainsKey(propertyName))
ValidationErrors.Add(propertyName, validationErrorDescription);
else
ValidationErrors[propertyName] = validationErrorDescription;
}
}
}
// this is another part of the interface - will be called automatically
public IEnumerable GetErrors(string propertyName)
{
return ValidationErrors.ContainsKey(propertyName)
? ValidationErrors[propertyName]
: null;
}
// same here, another part of the interface - will be called automatically
public bool HasErrors
{
get
{
return ValidationErrors.Count > 0;
}
}
}
이제 코드 어딘가에 있습니다. 버튼 명령어 'CanExecute' 메서드를 사용하여 VmEntity에 대한 호출을 구현에 추가할 수 있습니다.HasErrors(하스 에러)
그리고, 향후 검증에 관해서, 당신의 코드에 평화가 깃들기를 바랍니다.
단점은 모든 속성에 대해 검증을 너무 자주 실행할 수 있지만 대부분의 검증은 단순히 성능을 저하시키지 않기 위해 충분해야 한다는 것입니다.또 다른 해결책은 검증을 사용하여 어떤 속성이 오류를 발생시켰는지 기억하고 그것만 확인하는 것이지만, 대부분의 경우 이는 다소 복잡하며 불필요한 것으로 보입니다.
오류가 있는 속성을 추적할 필요가 없습니다. 오류가 존재하는지만 알면 됩니다.이 됩니다)와 「」( 「」)를 할 수 .IsValid
속성은 단순히 목록에 무엇이 있는지 여부를 반영하는 것일 수 있습니다.IsValid
이며, 「」, 「」, 「」, 「」가 되어 있는 한, 가 됩니다.IsValid
는 변경될 때마다 갱신됩니다.
결국, 이것은 견해상의 문제입니다.뷰(및 해당 데이터 바인딩 엔진)는 뷰 모델에 작업할 적절한 값을 제공하는 역할을 합니다.그러나 이 경우 뷰 모델에 오래된 부동산 가치를 무효화하도록 지시할 수 있는 좋은 방법은 없는 것 같습니다.
뷰 모델에 바인딩된 컨테이너 내의 오류를 청취할 수 있습니다.
container.AddHandler(Validation.ErrorEvent, Container_Error);
...
void Container_Error(object sender, ValidationErrorEventArgs e) {
...
}
또는 삭제되었을 때또, 예외는, 에러에 의해서 됩니다.e.Error.Exception
이 경우 뷰는 바인딩 예외 목록을 유지하고 뷰 모델에 이를 알릴 수 있습니다.
그러나 이 문제에 대한 해결책은 항상 해킹입니다. 뷰가 역할을 제대로 수행하지 못하기 때문입니다. 뷰 모델 구조를 읽고 업데이트할 수 있는 수단이 사용자에게 주어지기 때문입니다.텍스트 상자가 아닌 일종의 "정수 상자"를 사용자에게 올바르게 제공할 때까지 이는 일시적인 해결책으로 간주해야 합니다.
수많은 추가 코드를 구현하고 싶지 않은 경우 이 작업을 단순화하기 위한 노력이 있습니다.
이 시나리오에서는 뷰 모델에 int 속성이 있고(10진수 또는 문자열 이외의 유형일 수 있음) 텍스트박스를 뷰에 바인드합니다.
뷰 모델에 속성 설정기에서 실행되는 검증이 있습니다.
뷰에서 사용자가 123abc를 입력하면 뷰 로직이 뷰에서 오류를 강조 표시하지만 값이 잘못된 유형이기 때문에 속성을 설정할 수 없습니다.셋터는 절대 호출을 받지 않는다.
가장 간단한 해결책은 뷰 모델의 int 속성을 문자열 속성으로 변경하고 모델에 값을 캐스트하는 것입니다.이렇게 하면 잘못된 텍스트가 속성 설정자에 도달하고 검증 코드가 데이터를 검사하여 적절하게 거부할 수 있습니다.
WPF에서의 IMHO 검증은, 지금까지 주어진 문제를 회피하기 위한 정교한(그리고 기발한) 방법으로부터 알 수 있듯이, 깨져 있습니다.저는 텍스트 박스가 검증할 수 있도록 하기 위해 많은 코드를 추가하거나 나만의 타입 클래스를 구현하고 싶지 않습니다.따라서 문자열에 따라 이러한 속성을 사용하는 것은 다소 엉터리처럼 느껴지더라도 감수할 수 있습니다.
Microsoft는 int 속성 또는 10진수 속성에 바인딩된 텍스트 상자에 잘못된 사용자가 입력되는 시나리오가 뷰 모델에 이 사실을 우아하게 전달할 수 있도록 이 문제를 수정해야 합니다.예를 들어 뷰 로직 검증 오류를 뷰 모델의 속성에 전달하기 위해 XAML 컨트롤에 대한 새로운 바인딩 속성을 생성할 수 있습니다.
이 주제에 대한 자세한 답변을 제공해 주신 다른 분들께 감사드립니다.
언급URL : https://stackoverflow.com/questions/19498485/proper-validation-with-mvvm
'programing' 카테고리의 다른 글
Tree View 전체를 강조 표시WPF의 항목 행 (0) | 2023.04.19 |
---|---|
Bash 스크립트에서 프로그램이 존재하는지 확인하려면 어떻게 해야 합니까? (0) | 2023.04.19 |
사용자 선택 범위 가져오기 (0) | 2023.04.19 |
Excel의 .xlsx XML 형식에 대한 명확한 설명을 찾고 있습니다. (0) | 2023.04.19 |
Excel VBA에서 Absolute 경로가 아닌 상대 경로 (0) | 2023.04.19 |