수많은 값 변환이 필요한 뷰로 WPF 응용 프로그램을 만들고 있습니다. 처음에, XAML 제자에 대한이 활발한 토론 에서 부분적으로 영감을 얻은 나의 철학 은 뷰의 데이터 요구 사항을 지원하는 것에 대해 뷰 모델을 엄격하게 만들어야한다는 것이 었습니다 . 즉, 데이터를 가시성, 브러시, 크기 등으로 변환하는 데 필요한 값 변환은 값 변환기 및 다중 값 변환기로 처리됩니다. 개념적으로 이것은 매우 우아해 보였습니다. 뷰 모델과 뷰는 별개의 목적을 가지고 있으며 잘 분리되어 있습니다. "data"와 "look"사이에 명확한 선이 그려집니다.
글쎄,이 전략에 "오래된 대학 시도"를 한 후에, 나는 이런 식으로 계속 발전하고 싶은지 의심 스럽다. 실제로 가치 변환기를 덤프하고 (거의) 모든 가치 변환에 대한 책임을 뷰 모델의 손에 맡기는 것을 강력히 고려하고 있습니다.
가치 변환기를 사용하는 현실은 완전히 분리 된 우려의 명백한 가치까지 측정하지 않는 것 같습니다. 가치 변환기의 가장 큰 문제는 사용하기가 지루하다는 것입니다. 새 클래스를 작성 IValueConverter
하거나 구현 하거나 IMultiValueConverter
에서 값을 캐스트해야합니다.object
올바른 유형으로DependencyProperty.Unset
(적어도 다중 값 변환기의 경우)를 테스트 하고 변환 논리를 작성 하고 변환기를 자원 사전에 등록해야합니다 ( 아래 업데이트 참조). ] 그리고 마지막으로 다소 장황한 XAML을 사용하여 변환기를 연결합니다 (이것은 바인딩 과 변환기 이름 모두에 마법 문자열을 사용해야 함)[아래 업데이트 참조]). 특히 Visual Studio의 디자인 모드 / Expression Blend에서 오류 메시지가 암호화되어 있기 때문에 디버깅 프로세스도 소용이 없습니다.
이것은 뷰 모델이 모든 가치 변환을 책임지게하는 대안이 개선되었다고 말하는 것은 아닙니다. 이것은 반대편의 잔디가 더 푸르는 문제 일 수 있습니다. 우아한 분리 문제를 잃는 것 외에도 파생 속성을 많이 작성하고 양심적으로 전화해야합니다.RaisePropertyChanged(() => DerivedProperty)
기본 속성을 설정할 때 해야합니다. 이는 불쾌한 유지 관리 문제가 될 수 있습니다.
다음은 뷰 모델이 변환 논리를 처리하고 값 변환기를 제거하는 장단점을 정리 한 초기 목록입니다.
- 장점 :
- 다중 변환기가 제거되므로 총 바인딩 수가 적습니다.
- 마술 줄이 적다 (바인딩 경로
+ 변환기 리소스 이름) 더 이상 각 변환기를 등록하지 않아도됩니다 (이 목록을 유지 관리함)- 각 변환기를 작성하는 작업이 줄어 듭니다 (인터페이스 구현 또는 캐스팅 필요 없음)
- 변환을 돕기 위해 종속성을 쉽게 주입 할 수 있습니다 (예 : 색상 표)
- XAML 마크 업이 덜 장황하고 읽기 쉽다
- 변환기 재사용은 여전히 가능합니다 (일부 계획이 필요하지만)
- DependencyProperty.Unset에 대한 신비한 문제가 없습니다 (다중 값 변환기에서 발견 된 문제)
* 취소 선은 태그 확장을 사용하면 사라지는 이점을 나타냅니다 (아래 업데이트 참조).
- 단점 :
- 뷰 모델과 뷰 사이의 강력한 연결 (예 : 속성은 가시성 및 브러시와 같은 개념을 처리해야 함)
- 뷰의 모든 바인딩에 직접 매핑 할 수있는 더 많은 총 속성
(아래 업데이트 2 참조).RaisePropertyChanged
파생 된 각 속성에 대해 호출해야합니다- 변환이 UI 요소의 속성을 기반으로하는 경우 여전히 변환기를 사용해야합니다
아마 당신이 알 수 있듯이, 나는이 문제에 대해 가슴 앓이가 있습니다. 리팩토링의 길을 걸어가는 것이 매우 주저합니다. 코딩 프로세스가 값 변환기를 사용하거나 뷰 모델에 수많은 값 변환 속성을 노출하는지 여부와 마찬가지로 비효율적이며 지루합니다.
찬반 양론이 누락 되었습니까? 두 가지 가치 전환 수단을 모두 사용해 본 사람들에게 어떤 것이 더 효과적이며 왜 도움이 되었습니까? 다른 대안이 있습니까? (제자들은 형식 설명자 공급자에 대해 언급했지만, 그들이 말하는 내용을 처리 할 수 없었습니다. 이에 대한 통찰력이 있으면 감사하겠습니다.)
최신 정보
나는 오늘 "값 확장"이라는 것을 사용하여 값 변환기를 등록 할 필요가 없다는 것을 알았습니다. 실제로, 그것은 그것들을 등록 할 필요를 제거 할뿐만 아니라, 실제로 입력 할 때 변환기를 선택하기위한 지능을 제공합니다 Converter=
. 다음은 제가 시작한 기사입니다. http://www.wpftutorial.net/ValueConverters.html .
태그 확장을 사용하면 위의 장단점 목록과 토론에서 균형이 다소 변경됩니다 (파업 설명 참조).
이 계시의 결과로, 나는 변환기를 사용 BoolToVisibility
하고 내가 부르는 것을 사용하는 하이브리드 시스템을 실험하고 있습니다.MatchToVisibility
및 다른 모든 변환에 대한 뷰 모델을 하고 있습니다. MatchToVisibility는 기본적으로 바인딩 된 값 (일반적으로 열거 형)이 XAML에 지정된 하나 이상의 값과 일치하는지 확인할 수있는 변환기입니다.
예:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
기본적으로 상태가 완료 또는 취소인지 확인합니다. 이 경우 가시성은 "표시"로 설정됩니다. 그렇지 않으면 "숨김"으로 설정됩니다. 이것은 매우 일반적인 시나리오로 판명 되었으며이 변환기를 사용하면 뷰 모델에서 약 15 개의 속성과 관련 RaisePropertyChanged 문을 저장했습니다. 을 입력 Converter={vc:
하면 인텔리전스 메뉴에 "MatchToVisibility"가 나타납니다. 이렇게하면 오류 가능성이 눈에 띄게 줄어들고 값 변환기 사용이 지루해집니다 (원하는 값 변환기의 이름을 기억하거나 찾을 필요가 없습니다).
궁금한 점이 있으면 아래 코드를 붙여 넣습니다. 이 구현의 한 가지 중요한 기능은 MatchToVisibility
바운드 값이 enum
인지 확인하고 Value1
, Value2
이면 등이 같은 유형의 열거 형 인지 확인하는 것 입니다. 열거 형 값이 잘못 입력되었는지 디자인 타임 및 런타임 검사를 제공합니다. 이것을 컴파일 타임 검사로 향상시키기 위해 대신 다음을 사용할 수 있습니다 (손으로 직접 입력 했으므로 실수가 있으면 용서해주십시오).
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
이것이 더 안전하지만 나에게 가치가있는 것은 너무 장황하다. 이 작업을 수행하려면 뷰 모델에서 속성을 사용할 수도 있습니다. 어쨌든 디자인 타임 검사는 지금까지 시도한 시나리오에 완벽하게 적합하다는 것을 알았습니다.
코드는 다음과 같습니다. MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
코드는 다음과 같습니다. BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
ToEnum 확장 방법은 다음과 같습니다.
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
업데이트 2
이 질문을 게시 한 후 "IL weaving"을 사용하여 속성 및 종속 속성에 대한 NotifyPropertyChanged 코드를 삽입하는 오픈 소스 프로젝트를 발견했습니다. 이것은 뷰 모델에 대한 Josh Smith의 비전을 "스테로이드의 값 변환기"로 구현하는 것이 절대적인 바람입니다. "자동 구현 속성"을 사용하면 위버가 나머지 작업을 수행합니다.
예:
이 코드를 입력하면
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... 이것은 컴파일되는 것입니다 :
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
입력, 읽기, 스크롤 등의 코드를 크게 절약 할 수 있습니다. 더 중요한 것은 의존성이 무엇인지 파악하지 않아도되는 것입니다. 새로운 "속성 가져 오기"를 추가 할 수 있습니다FullName
RaisePropertyChanged()
호출 에 추가하기 위해 종속성 체인을 힘들게 올라갈 필요없이 .
이 오픈 소스 프로젝트는 무엇입니까? 원래 버전은 "NotifyPropertyWeaver"라고하지만 소유자 (Simon Potter)는 전체 일련의 IL 위버를 호스팅하기 위해 "Fody"라는 플랫폼을 만들었습니다. 이 새 플랫폼에서 NotifyPropertyWeaver에 해당하는 것은 PropertyChanged.Fody입니다.
- Fody 설정 지침 : http://code.google.com/p/fody/wiki/SampleUsage("Virtuosity "를"PropertyChanged "로 대체)
- PropertyChanged.Fody 프로젝트 사이트 : http://code.google.com/p/propertychanged/
NotifyPropertyWeaver (설치하기가 조금 더 간단하지만 향후 버그 수정 이후에 반드시 업데이트되지는 않음)를 선호하는 경우 프로젝트 사이트는 다음과 같습니다. http://code.google.com/p/ notifypropertyweaver /
어느 쪽이든, 이러한 IL 위버 솔루션은 스테로이드 대 가치 변환기에 대한 뷰 모델 사이의 토론에서 미적분학을 완전히 바꿉니다.
MatchToVisibility
일부 간단한 모드 스위치를 활성화하는 편리한 방법 인 것 같습니다 (특히 스위치를 켜거나 끌 수있는 많은 부품으로 하나의 뷰가 있습니다. 대부분의 경우 뷰의 섹션 x:Name
에는 모드와 일치하도록 레이블이 붙어 있음 ) 이것이 "비즈니스 로직"이라는 것은 실제로 일어나지 않았지만, 나는 여러분의 의견을 몇 가지 생각할 것입니다.
BooleanToVisibility
가시성 (true / false)과 관련된 하나의 값을 사용하여 다른 값으로 변환합니다. 이것은의 이상적인 사용처럼 보입니다ValueConverter
. 반면에, 어떤 유형의 항목을 표시해야하는지MatchToVisibility
에 비즈니스 로직을 인코딩하는View
것입니다. 제 생각에는이 논리는 아래로 밀어해야ViewModel
내가 부르는로 더욱 나EditModel
. 사용자가 볼 수있는 것은 테스트 대상이어야합니다.