가치 변환기가 가치보다 더 문제가 있습니까?


20

수많은 값 변환이 필요한 뷰로 WPF 응용 프로그램을 만들고 있습니다. 처음에, XAML 제자에 대한이 활발한 토론 에서 부분적으로 영감을 얻은 나의 철학 은 뷰의 데이터 요구 사항을 지원하는 것에 대해 뷰 모델을 엄격하게 만들어야한다는 것이 었습니다 . 즉, 데이터를 가시성, 브러시, 크기 등으로 변환하는 데 필요한 값 변환은 값 변환기 및 다중 값 변환기로 처리됩니다. 개념적으로 이것은 매우 우아해 보였습니다. 뷰 모델과 뷰는 별개의 목적을 가지고 있으며 잘 분리되어 있습니다. "data"와 "look"사이에 명확한 선이 그려집니다.

글쎄,이 전략에 "오래된 대학 시도"를 한 후에, 나는 이런 식으로 계속 발전하고 싶은지 의심 스럽다. 실제로 가치 변환기를 덤프하고 (거의) 모든 가치 변환에 대한 책임을 뷰 모델의 손에 맡기는 것을 강력히 고려하고 있습니다.

가치 변환기를 사용하는 현실은 완전히 분리 된 우려의 명백한 가치까지 측정하지 않는 것 같습니다. 가치 변환기의 가장 큰 문제는 사용하기가 지루하다는 것입니다. 새 클래스를 작성 IValueConverter하거나 구현 하거나 IMultiValueConverter에서 값을 캐스트해야합니다.object 올바른 유형으로DependencyProperty.Unset (적어도 다중 값 변환기의 경우)를 테스트 하고 변환 논리를 작성 하고 변환기를 자원 사전에 등록해야합니다 ( 아래 업데이트 참조). ] 그리고 마지막으로 다소 장황한 XAML을 사용하여 변환기를 연결합니다 (이것은 바인딩 과 변환기 이름 모두에 마법 문자열을 사용해야 함)[아래 업데이트 참조]). 특히 Visual Studio의 디자인 모드 / Expression Blend에서 오류 메시지가 암호화되어 있기 때문에 디버깅 프로세스도 소용이 없습니다.

이것은 뷰 모델이 모든 가치 변환을 책임지게하는 대안이 개선되었다고 말하는 것은 아닙니다. 이것은 반대편의 잔디가 더 푸르는 문제 일 수 있습니다. 우아한 분리 문제를 잃는 것 외에도 파생 속성을 많이 작성하고 양심적으로 전화해야합니다.RaisePropertyChanged(() => DerivedProperty) 기본 속성을 설정할 때 해야합니다. 이는 불쾌한 유지 관리 문제가 될 수 있습니다.

다음은 뷰 모델이 변환 논리를 처리하고 값 변환기를 제거하는 장단점을 정리 한 초기 목록입니다.

  • 장점 :
    • 다중 변환기가 제거되므로 총 바인딩 수가 적습니다.
    • 마술 줄이 적다 (바인딩 경로 + 변환기 리소스 이름 )
    • 더 이상 각 변환기를 등록하지 않아도됩니다 (이 목록을 유지 관리함)
    • 각 변환기를 작성하는 작업이 줄어 듭니다 (인터페이스 구현 또는 캐스팅 필요 없음)
    • 변환을 돕기 위해 종속성을 쉽게 주입 할 수 있습니다 (예 : 색상 표)
    • XAML 마크 업이 덜 장황하고 읽기 쉽다
    • 변환기 재사용은 여전히 ​​가능합니다 (일부 계획이 필요하지만)
    • DependencyProperty.Unset에 대한 신비한 문제가 없습니다 (다중 값 변환기에서 발견 된 문제)

* 취소 선은 태그 확장을 사용하면 사라지는 이점을 나타냅니다 (아래 업데이트 참조).

  • 단점 :
    • 뷰 모델과 뷰 사이의 강력한 연결 (예 : 속성은 가시성 및 브러시와 같은 개념을 처리해야 함)
    • 뷰의 모든 바인딩에 직접 매핑 할 수있는 더 많은 총 속성
    • RaisePropertyChanged파생 된 각 속성에 대해 호출해야합니다 (아래 업데이트 2 참조).
    • 변환이 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);
    }
}

입력, 읽기, 스크롤 등의 코드를 크게 절약 할 수 있습니다. 더 중요한 것은 의존성이 무엇인지 파악하지 않아도되는 것입니다. 새로운 "속성 가져 오기"를 추가 할 수 있습니다FullNameRaisePropertyChanged()호출 에 추가하기 위해 종속성 체인을 힘들게 올라갈 필요없이 .

이 오픈 소스 프로젝트는 무엇입니까? 원래 버전은 "NotifyPropertyWeaver"라고하지만 소유자 (Simon Potter)는 전체 일련의 IL 위버를 호스팅하기 위해 "Fody"라는 플랫폼을 만들었습니다. 이 새 플랫폼에서 NotifyPropertyWeaver에 해당하는 것은 PropertyChanged.Fody입니다.

NotifyPropertyWeaver (설치하기가 조금 더 간단하지만 향후 버그 수정 이후에 반드시 업데이트되지는 않음)를 선호하는 경우 프로젝트 사이트는 다음과 같습니다. http://code.google.com/p/ notifypropertyweaver /

어느 쪽이든, 이러한 IL 위버 솔루션은 스테로이드 대 가치 변환기에 대한 뷰 모델 사이의 토론에서 미적분학을 완전히 바꿉니다.


참고 : BooleanToVisibility가시성 (true / false)과 관련된 하나의 값을 사용하여 다른 값으로 변환합니다. 이것은의 이상적인 사용처럼 보입니다 ValueConverter. 반면에, 어떤 유형의 항목을 표시해야하는지 MatchToVisibility에 비즈니스 로직을 인코딩하는 View것입니다. 제 생각에는이 논리는 아래로 밀어해야 ViewModel내가 부르는로 더욱 나 EditModel. 사용자가 볼 수있는 것은 테스트 대상이어야합니다.
Scott Whitlock

@Scott, 좋은 지적입니다. 내가 지금 작업하고있는 앱은 실제로는 "비즈니스"앱이 아니며, 사용자에 대한 권한 수준이 다르기 때문에 이러한 방식에 대해서는 생각하지 않았습니다. MatchToVisibility일부 간단한 모드 스위치를 활성화하는 편리한 방법 인 것 같습니다 (특히 스위치를 켜거나 끌 수있는 많은 부품으로 하나의 뷰가 있습니다. 대부분의 경우 뷰의 섹션 x:Name에는 모드와 일치하도록 레이블이 붙어 있음 ) 이것이 "비즈니스 로직"이라는 것은 실제로 일어나지 않았지만, 나는 여러분의 의견을 몇 가지 생각할 것입니다.
devuxer

예 : 라디오, CD 또는 MP3 모드 일 수있는 스테레오 시스템이 있다고 가정하십시오. UI의 다른 부분에 각 모드에 해당하는 비주얼이 있다고 가정합니다. (1) 뷰가 어떤 그래픽에 어떤 그래픽이 대응하는지 결정하도록하고 그에 따라 그래픽을 켜거나 끌 수 있습니다. (2) 각 모드 값 (예 : IsModeRadio, IsModeCD)에 대해 뷰 모델에서 속성을 노출하거나 (3) 노출 각 그래픽 요소 / 그룹에 대한 뷰 모델의 속성 (예 : IsRadioLightOn, IsCDButtonGroupOn). (1) 이미 모드 인식을 가지고 있기 때문에 내 견해에 자연스럽게 적합했습니다. 이 경우에 어떻게 생각하십니까?
devuxer

이것은 전체 SE에서 본 가장 긴 질문입니다! :]
trejder

답변:


10

나는 ValueConverters어떤 경우에는 논리를 사용 했고 다른 경우에는 논리를 넣었 ViewModel습니다. 내 느낌은 a ValueConverterView레이어의 일부 가된다는 것입니다 . 따라서 논리가 실제로 일부인 View경우에는 거기에 넣고 그렇지 않으면ViewModel .

개인적으로 es 와 같은 특정 개념을 ViewModel다루는 데 문제가 없습니다 . 내 응용 프로그램 에서는 테스트 가능하고 바인딩 가능한 표면으로 만 존재 하기 때문입니다 . 그러나 일부 사람들은 많은 비즈니스 로직을 (그렇지 않습니다 .)ViewBrushViewModelViewViewModelViewModel

나는 다른 분리를 선호합니다.

  • View- WPF 물건, 때때로 (XAML과 코드 숨김 등)을 검증 할 수 있지만, 또한 ValueConverter이야
  • ViewModel -WPF에 특정한 테스트 가능하고 바인딩 가능한 클래스
  • EditModel -조작 중 내 모델을 나타내는 비즈니스 계층의 일부
  • EntityModel -내 모델이 지속됨을 나타내는 비즈니스 계층의 일부
  • Repository- EntityModel데이터베이스의 지속성을 책임진다

그래서, 내가 할 방법, 나는 거의 사용이 ValueConverter

내가 당신의 "Con ViewModel's 중 일부에서 벗어난 방법은 내를 매우 일반적인 것으로 만드는 것 입니다. 예를 들어, ViewModel내가 가지고 ChangeValueViewModel있는 것은 Label 속성과 Value 속성을 구현합니다. 온 View거기에 Label그 레이블 속성에 바인딩하고 TextBox그 값 속성에 바인딩합니다.

그런 다음 유형 ChangeValueViewDataTemplate키가 있습니다 ChangeValueViewModel. WPF가 ViewModel그것을 볼 때마다 그것은 적용됩니다 View. 내 생성자는 ChangeValueViewModel상호 작용 논리를 가져 와서 EditModel(보통 그냥 a 전달) 상태를 새로 고쳐야하며 Func<string>사용자가 값을 편집 할 때 수행 해야하는 작업 (의 Action일부 논리를 실행 하는 것)을 취 합니다 EditModel.

부모 ViewModel(화면의 경우)는 EditModel생성자를 사용하고와 ViewModel같은 적절한 기초를 인스턴스화합니다 ChangeValueViewModel. 부모 ViewModel는 사용자가 변경할 때 수행 할 조치를 주입하므로 이러한 모든 조치를 가로 채고 다른 조치를 취할 수 있습니다. 따라서에 대한 삽입 된 편집 조치 ChangeValueViewModel는 다음과 같습니다.

(string newValue) =>
{
    editModel.SomeField = newValue;
    foreach(var childViewModel in this.childViewModels)
    {
        childViewModel.RefreshStateFromEditModel();
    }
}

분명히 foreach루프는 다른 곳에서 리팩토링 될 수 있지만, 이것이하는 일은 조치를 취하여 모델에 적용 한 다음 (모델이 알 수없는 방식으로 상태를 업데이트했다고 가정) 모든 어린이 ViewModel에게 가서 상태를 가져 오는 것입니다. 다시 모델. 상태가 변경되면PropertyChanged 필요에 이벤트 합니다.

목록 상자와 세부 정보 패널 간의 상호 작용을 아주 잘 처리합니다. 사용자가 새로운 선택을 선택하면, 상기 업데이트를 EditModel검색하여, 상기는 EditModel상세한 패널의 노출 된 특성 값을 변경한다. ViewModel자동으로 새로운 가치를 확인해야하고, 그들이 변경 한 경우, 그들의 발사 것을 통보받을 상세 패널 정보 표시에 대한 책임이 있습니다 어린이 PropertyChanged이벤트.


/목례. 그것은 내 모습과 매우 비슷합니다.
Ian

+1. 대답 해 주셔서 감사합니다, Scott, 나는 당신과 거의 같은 계층을 가지고 있으며 비즈니스 모델을 뷰 모델에 넣지 않습니다. (기록을 위해 먼저 EntityFramework Code를 사용하고 있으며 뷰 모델과 엔터티 모델 간을 변환하는 서비스 계층이 있으며 그 반대도 마찬가지입니다.) 따라서이 비용이 많이 들지 않는다고 생각합니다. 모든 / 대부분의 변환 로직을 뷰 모델 레이어에 넣습니다.
devuxer

@ DanM-예, 동의합니다. ViewModel레이어 에서 변환을 선호합니다 . 모두가 저에게 동의하는 것은 아니지만 아키텍처가 어떻게 작동하는지에 달려 있습니다.
Scott Whitlock

2
첫 번째 단락을 읽은 후 +1을 말하려고했지만 두 번째 단락을 읽고 ViewModels에 뷰 특정 코드를 넣는 것에 크게 동의하지 않습니다. 뷰 모델은 (예 : 같은 일반적인보기 뒤에가는을 위해 특별히 만든 경우 한 가지 예외는 CalendarViewModelA의 CalendarViewUserControl을, 또는 DialogViewModelA에 대한 DialogView). 그건 내 의견이지만 :)
Rachel

@Rachel-글쎄, 만약 당신이 나의 두 번째 단락을지나 계속 읽었다면 그것이 내가하고있는 것과 정확히 같을 것입니다. :) 내 비즈니스 로직이 없습니다 ViewModel.
Scott Whitlock 1

8

변환이 개체의 가시성 결정, 표시 할 이미지 결정 또는 사용할 브러시 색상 파악과 같은보기 관련 항목 인 경우 항상 변환기를보기에 배치합니다.

필드를 마스킹해야하는지 결정하는 것과 같은 비즈니스 관련이거나 사용자에게 작업을 수행 할 권한이있는 경우 변환이 내 ViewModel에서 발생합니다.

귀하의 예에서, WPF의 큰 부분이 빠져 있다고 생각합니다 DataTriggers. 조건부 값을 결정하기 위해 변환기를 사용하는 것처럼 보이지만 변환기는 실제로 한 데이터 유형을 다른 데이터 유형으로 변환해야합니다.

위의 예에서

예 : 라디오, CD 또는 MP3 모드 일 수있는 스테레오 시스템이 있다고 가정하십시오. UI의 다른 부분에 각 모드에 해당하는 비주얼이 있다고 가정합니다. (1) 뷰가 어떤 그래픽에 어떤 그래픽이 대응하는지 결정하도록하고 그에 따라 그래픽을 켜거나 끌 수 있습니다. (2) 각 모드 값 (예 : IsModeRadio, IsModeCD)에 대해 뷰 모델에서 속성을 노출하거나 (3) 노출 각 그래픽 요소 / 그룹에 대한 뷰 모델의 속성 (예 : IsRadioLightOn, IsCDButtonGroupOn). (1) 이미 모드 인식을 가지고 있기 때문에 내 견해에 자연스럽게 적합했습니다. 이 경우에 어떻게 생각하십니까?

를 사용 DataTrigger하여 표시 할 이미지를 결정 하는 데 사용 합니다 Converter. 변환기는 하나의 데이터 유형을 다른 데이터 유형으로 변환하는 데 사용되며 트리거는 값을 기반으로 일부 특성을 판별하는 데 사용됩니다.

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{StaticResource RadioImage}" />
    <Style.Triggers>
        <DataTrigger Binding="{Binding Mode}" Value="CD">
            <Setter Property="Source" Value="{StaticResource CDImage}" />
        </DataTrigger>
        <DataTrigger Binding="{Binding Mode}" Value="MP3">
            <Setter Property="Source" Value="{StaticResource MP3Image}" />
        </DataTrigger>
    </Style.Triggers>
</Style>

바운드 값에 실제로 이미지 데이터가 포함되어 있고 UI가 이해할 수있는 데이터 형식으로 변환해야하는 경우에만 변환기를 사용하는 것이 좋습니다. 예를 들어 데이터 소스에라는 속성이 포함되어 있으면 ImageFilePath변환기를 사용하여 이미지 파일 위치가 포함 된 문자열 BitmapImage을 내 이미지의 소스로 사용할 수있는 문자열로 변환하는 것을 고려하는 것보다

<Style x:Key="RadioImageStyle">
    <Setter Property="Source" Value="{Binding ImageFilePath, 
            Converter={StaticResource StringPathToBitmapConverter}}" />
</Style>

결과적으로 하나의 데이터 유형을 다른 데이터 유형으로 변환하는 일반 변환기로 가득 찬 하나의 라이브러리 네임 스페이스가 있으며 새 변환기를 코딩 할 필요가 거의 없습니다. 특정 전환을위한 변환기를 원할 때가 있지만 가끔씩 작성하지 않아도 될 수 있습니다.


+1. 당신은 좋은 점수를 올립니다. 전에 트리거를 사용했지만 내 경우에는 이미지 소스 (속성)를 전환하지 않고 전체 Grid요소를 전환합니다 . 또한 뷰 모델의 데이터와 구성 파일에 정의 된 특정 색상 팔레트를 기반으로 전경 / 배경 / 스트로크에 브러시 설정과 같은 작업을 시도하고 있습니다. 이것이 트리거 또는 변환기에 잘 맞는지 잘 모르겠습니다. 지금까지 뷰 모델에 대부분의 뷰 로직을 배치하는 데있어 유일한 문제는 모든 RaisePropertyChanged()호출을 연결하는 것 입니다.
devuxer

@ DanM 실제로 DataTrigger그리드의 요소를 전환하기 위해 모든 것을 할 것입니다 . 일반적으로 ContentControl동적 콘텐츠가 있어야 할 곳에 배치 ContentTemplate하고 트리거를 교체 합니다. 관심있는 경우 다음 링크에 예가 있습니다 (헤더가있는 섹션으로 스크롤 Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel

이전에는 데이터 템플릿과 콘텐츠 컨트롤을 사용해 왔지만 항상 각 뷰마다 고유 한 뷰 모델이 있었기 때문에 트리거가 필요하지 않았습니다. 어쨌든, 당신의 기술은 완벽하게 이해되고 매우 우아하지만 매우 장황합니다. MatchToVisibility를 사용하면 다음 <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"과 같이 단축 될 수 있습니다.<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer

1

테스트 대상 에 따라 다릅니다 .

테스트 없음 : 원하는대로 ViewModel과 Viewmix를 혼합하십시오 (나중에 언제든지 리팩토링 할 수 있음).
ViewModel 및 / 또는 더 낮은 테스트 : 변환기를 사용하십시오.
모델 레이어 및 / 또는 더 낮은 테스트 : 원하는 경우 ViewModel을 사용한 혼합 혼합 코드보기

ViewModel은 View 모델을 추상화합니다 . 개인적으로 브러시 등을 위해 ViewModel을 사용하고 변환기를 건너 뜁니다. 데이터 가 " 가장 순수한 "형식 인 레이어 (예 : 모델 레이어 ) 에서 테스트 합니다 .


2
테스트에 대한 흥미로운 점이 있지만 뷰 모델에 변환기 논리가 뷰 모델의 테스트 가능성에 어떻게 영향을 미치는지 알지 못하는 것 같습니다. 실제 UI 컨트롤 을 뷰 모델에 넣을 것을 제안하지는 않습니다 . 그냥보기 - 특정 속성 좋아 Visibility, SolidColorBrush하고 Thickness.
devuxer

@ DanM : View-first 접근법을 사용하는 경우 아무런 문제가 없습니다 . 그러나 일부는 ViewModel 이 View를 참조하는 ViewModel 우선 접근 방식을 사용 하므로 문제 가 될 수 있습니다 .
Jake Berger

안녕 제이, 분명히보기 우선 접근 방식. 뷰는 바인딩해야하는 속성의 이름을 제외하고 뷰 모델에 대해 아무것도 모릅니다. 연락해 주셔서 감사합니다. +1.
devuxer

0

이것은 아마도 언급 한 모든 문제를 해결하지는 못하지만 고려해야 할 두 가지 사항이 있습니다.

먼저, 첫 번째 전략의 어딘가에 변환기 코드를 배치해야합니다. 뷰 또는 뷰 모델의 해당 부분을 고려하십니까? 뷰의 일부인 경우 뷰 모델 대신 뷰에 뷰 특정 속성을 배치하지 않겠습니까?

둘째, 비 변환기 디자인은 이미 존재하는 실제 객체 속성을 수정하려고하는 것처럼 들립니다. 이미 INotifyPropertyChanged를 구현하는 것처럼 들리므로 바인딩 할 뷰 특정 래퍼 객체를 사용하지 않는 이유는 무엇입니까? 다음은 간단한 예입니다.

public class RealData
{
    private bool mIsInteresting;
    public bool IsInteresting
    {
        get { return mIsInteresting; }
        set 
        {
            if (mIsInteresting != null) 
            {
                mIsInteresting = value;
                RaisePropertyChanged("IsInteresting");
            }
        }
    }
}

public class RealDataView
{
    private RealData mRealData;

    public RealDataView(RealData data)
    {
        mRealData = data;
        mRealData.PropertyChanged += OnRealDataPropertyChanged;
    }

    public Visibility IsVisiblyInteresting
    {
       get { return mRealData.IsInteresting ? Visibility.Visible : Visibility.Hidden; }
       set { mRealData.IsInteresting = (value == Visibility.Visible); }
    }

    private void OnRealDataPropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "IsInteresting") 
        {
            RaisePropertyChanged(this, "IsVisiblyInteresting");
        }
    }
}

뷰 또는 뷰 모델에서 엔터티 모델의 속성을 직접 변경한다는 것을 의미하지는 않았습니다. 뷰 모델은 분명히 엔터티 모델 레이어와 다른 레이어입니다. 사실, 내가 한 일은 읽기 전용보기에 관한 것입니다. 이것은 내 응용 프로그램에 편집이 필요하지 않지만 편집에 사용되는 컨트롤에서 변환이 수행되는 것을 보지 못합니다 (따라서 목록의 선택을 제외하고 모든 바인딩이 단방향이라고 가정). "데이터 뷰"에 대한 좋은 지적. 그것은 제가 질문의 맨 위에 언급 한 XAML 제자 게시물에서 제기 된 개념이었습니다.
devuxer

0

때로는 가상화를 활용하기 위해 값 변환기를 사용하는 것이 좋습니다.

예를 들어 그리드에서 수십만 셀에 대한 비트 마스크 데이터를 표시해야하는 프로젝트의 예입니다. 모든 단일 셀에 대해 뷰 모델에서 비트 마스크를 디코딩 할 때 프로그램을로드하는 데 너무 오래 걸렸습니다.

그러나 단일 셀을 디코딩하는 값 변환기를 만들면 프로그램이 짧은 시간에로드되고 사용자가 특정 셀을보고있을 때만 변환기가 호출되기 때문에 반응이 빨랐습니다 (호출 만하면됩니다) 사용자가 그리드에서보기를 이동할 때마다 최대 30 번).

솔루션이 MVVM에 어떻게 불만을 제기했는지는 모르지만로드 시간을 95 % 단축했습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.