Czy osoby dokonujące konwersji wartości są większym problemem niż są warte?

20

Pracuję nad aplikacją WPF z widokami, które wymagają wielu konwersji wartości. Początkowo moją filozofią (zainspirowaną częściowo tą ożywioną debatą na temat XAML Disciples ) było to, że powinienem stworzyć model widoku ściśle uwzględniający wymagania dotyczące danych w widoku. Oznaczało to, że wszelkie konwersje wartości wymagane do przekształcenia danych w rzeczy takie jak widoczności, pędzle, rozmiary itp. Będą obsługiwane przez konwertery wartości i konwertery wielu wartości. Pod względem koncepcyjnym wydawało się to dość eleganckie. Model widoku i widok miałyby odrębny cel i byłyby ładnie oddzielone. Należałoby wytyczyć wyraźną linię między „danymi” a „wyglądem”.

Cóż, po wypróbowaniu tej strategii jako „starej próby na studiach”, mam wątpliwości, czy chcę nadal rozwijać się w ten sposób. Naprawdę mocno zastanawiam się nad odrzuceniem konwerterów wartości i przekazaniem odpowiedzialności za (prawie) całą konwersję wartości wprost w ręce modelu widoku.

Rzeczywistość używania konwerterów wartości po prostu nie wydaje się mierzyć do wartości pozornie wyodrębnionych problemów. Moim największym problemem z konwerterami wartości jest to, że są uciążliwe w użyciu. Musisz stworzyć nową klasę, zaimplementować IValueConverterlub IMultiValueConverterodrzucić wartość lub wartości z objectwłaściwego typu, przetestować DependencyProperty.Unset(przynajmniej dla konwerterów wielowartościowych), napisać logikę konwersji, zarejestrować konwerter w słowniku zasobów [patrz aktualizacja poniżej ] i na koniec podłącz konwerter przy użyciu dość pełnego XAML (co wymaga użycia magicznych ciągów zarówno dla powiązań, jak i nazwy konwertera[patrz aktualizacja poniżej]). Proces debugowania nie jest też piknikiem, ponieważ komunikaty o błędach są często tajemnicze, szczególnie w trybie projektowania Visual Studio / Expression Blend.

Nie oznacza to, że alternatywą - uczynienie modelu widoku odpowiedzialnym za całą konwersję wartości - jest ulepszenie. Mogłoby to być kwestią zieleni po drugiej stronie. Oprócz utraty eleganckiego rozdzielenia problemów, musisz napisać kilka pochodnych właściwości i upewnić się, że sumiennie dzwonisz RaisePropertyChanged(() => DerivedProperty)przy ustawianiu podstawowych właściwości, co może okazać się nieprzyjemnym problemem konserwacji.

Poniżej znajduje się wstępna lista zalet i wad umożliwiania modelom widoku obsługi logiki konwersji i rezygnacji z konwerterów wartości:

  • Plusy:
    • Mniej łącznych wiązań, ponieważ wyeliminowano wiele konwerterów
    • Mniej magicznych ciągów (ścieżki wiązania + nazwy zasobów konwertera )
    • Koniec rejestrowania każdego konwertera (plus utrzymanie tej listy)
    • Mniej pracy do napisania każdego konwertera (nie wymaga interfejsów implementacyjnych ani rzutowania)
    • Może łatwo wstrzykiwać zależności, aby pomóc w konwersji (np. Tabele kolorów)
    • Znaczniki XAML są mniej szczegółowe i łatwiejsze do odczytania
    • Ponowne użycie konwertera jest nadal możliwe (choć wymagane jest pewne planowanie)
    • Żadnych tajemniczych problemów z DependencyProperty.Unset (problem zauważyłem w przypadku konwerterów wielowartościowych)

* Przekreślenia wskazują korzyści, które znikają, jeśli używasz rozszerzeń znaczników (patrz aktualizacja poniżej)

  • Cons:
    • Silniejsze sprzężenie między modelem widoku a widokiem (np. Właściwości muszą uwzględniać pojęcia takie jak widoczność i pędzle)
    • Więcej całkowitych właściwości, aby umożliwić bezpośrednie mapowanie dla każdego powiązanego widoku
    • RaisePropertyChangednależy wywołać dla każdej właściwości pochodnej (patrz Aktualizacja 2 poniżej)
    • Musi nadal polegać na konwerterach, jeśli konwersja jest oparta na właściwości elementu interfejsu użytkownika

Tak więc, jak zapewne możesz powiedzieć, mam zgagę dotyczącą tego problemu. Bardzo waham się pójść drogą refaktoryzacji tylko po to, by zdać sobie sprawę, że proces kodowania jest równie nieefektywny i żmudny, niezależnie od tego, czy używam konwerterów wartości, czy ujawniam wiele właściwości konwersji wartości w moim modelu widoku.

Czy brakuje mi zalet / wad? Dla tych, którzy wypróbowali oba sposoby przeliczania wartości, które według ciebie działały dla Ciebie lepiej i dlaczego? Czy są jakieś inne alternatywy? (Uczniowie wspominali coś o dostawcach deskryptorów typów, ale nie mogłem zrozumieć, o czym rozmawiali. Doceniłbym każdy wgląd w to).


Aktualizacja

Dowiedziałem się dzisiaj, że można użyć czegoś, co nazywa się „rozszerzeniem znaczników”, aby wyeliminować potrzebę rejestrowania konwerterów wartości. W rzeczywistości nie tylko eliminuje to konieczność ich rejestrowania, ale w rzeczywistości zapewnia inteligencję do wybierania konwertera podczas pisania Converter=. Oto artykuł, który mnie zaczął: http://www.wpftutorial.net/ValueConverters.html .

Możliwość użycia rozszerzenia znaczników nieco zmienia równowagę w mojej liście zalet i wad oraz powyższej dyskusji (patrz przekreślenia).

W wyniku tego odkrycia eksperymentuję z systemem hybrydowym, w którym używam konwerterów BoolToVisibilityi tego, co nazywam, MatchToVisibilityoraz modelu widoku dla wszystkich innych konwersji. MatchToVisibility to w zasadzie konwerter, który pozwala mi sprawdzić, czy wartość powiązana (zwykle wyliczenie) odpowiada jednej lub większej liczbie wartości określonych w XAML.

Przykład:

Visibility="{Binding Status, Converter={vc:MatchToVisibility
            IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"

Zasadniczo polega to na sprawdzeniu, czy status ma status Zakończony lub Anulowany. Jeśli tak, to widoczność zostaje ustawiona na „Widoczne”. W przeciwnym razie ustawi się na „Ukryty”. Okazało się to bardzo częstym scenariuszem i posiadanie tego konwertera zapisało mi około 15 właściwości w moim modelu widoku (plus powiązane instrukcje RaisePropertyChanged). Pamiętaj, że po wpisaniu Converter={vc:„MatchToVisibility” pojawia się w menu inteligencji. To znacznie zmniejsza ryzyko wystąpienia błędów i sprawia, że ​​korzystanie z konwerterów wartości jest mniej uciążliwe (nie musisz zapamiętywać ani szukać nazwy żądanego konwertera wartości).

Jeśli jesteś ciekawy, wkleję poniższy kod. Jedną ważną cechą tej realizacji MatchToVisibilityjest to, że sprawdza, czy wartość związana jest enum, a jeśli jest, to kontrole, aby upewnić się Value1, Value2itp są także teksty stałe tego samego typu. Zapewnia to sprawdzenie w czasie projektowania i w czasie wykonywania, czy któraś z wartości wyliczenia jest błędnie wpisana. Aby poprawić to do sprawdzania czasu kompilacji, możesz zamiast tego użyć następującego (napisałem to ręcznie, więc proszę wybacz mi, jeśli popełniłem jakieś błędy):

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}}"

Chociaż jest to bezpieczniejsze, jest po prostu zbyt gadatliwe, aby było dla mnie tego warte. Równie dobrze mogę po prostu użyć właściwości modelu widoku, jeśli mam to zrobić. W każdym razie stwierdzam, że kontrola czasu projektowania jest całkowicie adekwatna do scenariuszy, które próbowałem do tej pory.

Oto kod dla 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;
    }
}

Oto kod dla 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();
    }
}

Oto metoda rozszerzenia ToEnum

public static TEnum ToEnum<TEnum>(this string text)
{
    return (TEnum)Enum.Parse(typeof(TEnum), text);
}

Aktualizacja 2

Od kiedy opublikowałem to pytanie, natknąłem się na projekt open source, który używa „tkania IL” do wstrzykiwania kodu NotifyPropertyChanged dla właściwości i właściwości zależnych. To sprawia, że ​​wdrożenie wizji modelu widoku Josha Smitha jako „konwertera wartości na sterydach” jest absolutną bryzą. Możesz po prostu użyć „Właściwości automatycznie zaimplementowane”, a tkacz zrobi resztę.

Przykład:

Jeśli wprowadzę ten kod:

public string GivenName { get; set; }
public string FamilyName { get; set; }

public string FullName
{
    get
    {
        return string.Format("{0} {1}", GivenName, FamilyName);
    }
}

... to się kompiluje:

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

To ogromna oszczędność w ilości kodu, który musisz wpisać, odczytać, przewinąć, itp. Co ważniejsze, jednak oszczędza ci to konieczności zastanawiania się, jakie są twoje zależności. Możesz dodawać nowe „właściwości” jak FullNamebez starannego wchodzenia w łańcuch zależności w celu dodawania RaisePropertyChanged()wywołań.

Jak nazywa się ten projekt typu open source? Oryginalna wersja nosi nazwę „NotifyPropertyWeaver”, ale właściciel (Simon Potter) stworzył platformę o nazwie „Fody” do obsługi całej serii tkaczy IL. Odpowiednik NotifyPropertyWeaver na tej nowej platformie nazywa się PropertyChanged.Fody.

Jeśli wolisz korzystać z NotifyPropertyWeaver (który jest nieco prostszy w instalacji, ale niekoniecznie będzie aktualizowany w przyszłości poza poprawkami błędów), oto strona projektu: http://code.google.com/p/ powiadomienie

Tak czy inaczej, te rozwiązania tkacza IL całkowicie zmieniają rachunek w debacie między modelem widoku na sterydach a przetwornikami wartości.

devuxer
źródło
Tylko uwaga: BooleanToVisibilitypobiera jedną wartość związaną z widocznością (prawda / fałsz) i tłumaczy ją na inną. To wydaje się idealnym zastosowaniem ValueConverter. Z drugiej strony MatchToVisibilitykoduje logikę biznesową w View(jakie rodzaje elementów powinny być widoczne). Moim zdaniem logikę tę należy sprowadzić do ViewModel, a nawet dalej, do tego, co nazywam EditModel. To, co użytkownik może zobaczyć, powinno być testowane.
Scott Whitlock
@ Scott, to dobry punkt. Aplikacja, nad którą teraz pracuję, nie jest tak naprawdę aplikacją „biznesową”, w której użytkownicy mają różne poziomy uprawnień, więc nie myślałem w ten sposób. MatchToVisibilitywydaje się być wygodnym sposobem na włączenie niektórych prostych przełączników trybu (mam jeden widok, w szczególności z toną części, które można włączać i wyłączać. W większości przypadków sekcje widoku są nawet oznaczone (za pomocą x:Name), aby pasowały do ​​trybu odpowiadają.) Tak naprawdę nie przyszło mi do głowy, że jest to „logika biznesowa”, ale dam wam komentarz.
devuxer
Przykład: powiedzmy, że masz system stereo, który może być w trybie radia, CD lub MP3. Załóżmy, że istnieją elementy wizualne odpowiadające każdemu trybowi w różnych częściach interfejsu użytkownika. Możesz albo (1) pozwolić widokowi zdecydować, która grafika odpowiada danemu trybowi i odpowiednio je włączyć / wyłączyć, (2) ujawnić właściwości modelu widoku dla każdej wartości trybu (np. IsModeRadio, IsModeCD) lub (3) odsłonić właściwości w modelu widoku dla każdego elementu / grupy graficznej (np. IsRadioLightOn, IsCDButtonGroupOn). (1) wydawało mi się naturalne dla mojego poglądu, ponieważ ma już świadomość trybu. Co myślisz w tym przypadku?
devuxer
To najdłuższe pytanie, jakie kiedykolwiek widziałem w całej SE! :]
trejder

Odpowiedzi:

10

ValueConvertersW niektórych przypadkach używałem logiki, ViewModelaw innych logikę . Mam wrażenie, że ValueConverterstaje się częścią Viewwarstwy, więc jeśli logika jest naprawdę częścią, Viewto umieść ją tam, w przeciwnym razie umieść ją w ViewModel.

Osobiście nie widzę problemu z ViewModelradzeniem sobie ze Viewspecyficznymi pojęciami, takimi jak Brushes, ponieważ w moich aplikacjach ViewModelistnieje tylko jako testowalna i wiążąca powierzchnia dla View. Jednak niektórzy ludzie wkładają dużo logiki biznesowej w ViewModel(ja nie) i w tym przypadku ViewModeljest to bardziej część ich warstwy biznesowej, więc w tym przypadku nie chciałbym tam rzeczy specyficznych dla WPF.

Wolę inną separację:

  • View- Rzeczy WPF, czasem nie do przetestowania (jak XAML i kodowanie), ale także ValueConverters
  • ViewModel - testowalna i dająca się powiązać klasa, która jest również specyficzna dla WPF
  • EditModel - część warstwy biznesowej, która reprezentuje mój model podczas manipulacji
  • EntityModel - część warstwy biznesowej, która reprezentuje mój model jako utrwalony
  • Repository- odpowiedzialny za trwałość EntityModelbazy danych

Więc sposób, w jaki to robię, nie ma większego sensu dla ValueConverters

Uciekłem od niektórych twoich „oszustów”, czyniąc moje ViewModelbardzo ogólnym. Na przykład jeden, ViewModelktóry mam, o nazwie, ChangeValueViewModelimplementuje właściwość Label i właściwość Value. Na stronie Viewznajduje się Labelpowiązanie z właściwością Label oraz TextBoxpowiązanie z właściwością Value.

Następnie mam klucz, ChangeValueViewktóry jest DataTemplatewyłączony z tego ChangeValueViewModeltypu. Ilekroć WPF widzi, że ViewModelto stosuje View. Mój konstruktor ChangeValueViewModelprzyjmuje logikę interakcji, której potrzebuje, aby odświeżyć swój stan od EditModel(zwykle po prostu przekazując a Func<string>) i działania, które musi podjąć, gdy użytkownik edytuje wartość (tylko ten, Actionktóry wykonuje pewną logikę w EditModel).

Element nadrzędny ViewModel(dla ekranu) pobiera EditModelkonstruktor i tworzy po prostu odpowiednie elementy elementarne, ViewModeltakie jak ChangeValueViewModel. Ponieważ rodzic ViewModelwstrzykuje akcję, która ma zostać wykonana, gdy użytkownik dokona jakiejkolwiek zmiany, może przechwycić wszystkie te akcje i wykonać inne. Dlatego wstrzyknięta akcja edycji ChangeValueViewModelmoże wyglądać następująco:

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

Oczywiście foreachpętlę można zrefaktoryzować gdzie indziej, ale wystarczy wykonać działanie, zastosować ją do modelu, a następnie (zakładając, że model zaktualizował swój stan w jakiś nieznany sposób), mówi wszystkim dzieciom, ViewModelaby poszły i odzyskały swój stan model ponownie. Jeśli stan się zmienił, są oni odpowiedzialni za wykonanie swoich PropertyChangedzdarzeń, jeśli to konieczne.

To całkiem ładnie radzi sobie z interakcją między, na przykład, listą i panelem szczegółów. Gdy użytkownik wybierze nowy wybór, aktualizuje ten EditModelwybór i EditModelzmienia wartości właściwości ujawnionych dla panelu szczegółów. Te ViewModeldzieci, które są odpowiedzialne za wyświetlanie informacji szczegół panel automatycznie otrzymywać powiadomienia, że muszą sprawdzić nowe wartości, a jeśli już zmieniony, ogień oni swoje PropertyChangedwydarzenia.

Scott Whitlock
źródło
/ukłon. To bardzo podobne do mojego wyglądu.
Ian
+1. Dzięki za odpowiedź, Scott, mam te same warstwy, co ty, a także nie umieszczam logiki biznesowej w modelu widoku. (Dla przypomnienia, najpierw używam kodu EntityFramework i mam warstwę usługi, która tłumaczy modele widoku i modele jednostek i odwrotnie.) Biorąc to pod uwagę, wydaje mi się, że mówisz, że nie ma dużych kosztów do umieszczenia całej / większości logiki konwersji w warstwie modelu widoku.
devuxer 13.10.11
@DanM - Tak, zgadzam się. Wolałbym konwersję w ViewModelwarstwie. Nie wszyscy się ze mną zgadzają, ale zależy to od tego, jak działa twoja architektura.
Scott Whitlock,
2
Chciałem powiedzieć +1 po przeczytaniu pierwszego akapitu, ale potem przeczytałem twój drugi i zdecydowanie nie zgadzam się z umieszczeniem kodu ViewModels. Jedyny wyjątek dotyczy sytuacji, gdy ViewModel jest tworzony specjalnie z myślą o pominięciu widoku ogólnego (na przykład CalendarViewModeldla CalendarViewUserControl lub a DialogViewModeldla a DialogView). To tylko moja opinia :)
Rachel
@Rachel - cóż, gdybyś dalej czytał mój drugi akapit, zobaczyłbyś, że właśnie to robiłem. :) W moim systemie nie ma logiki biznesowej ViewModel.
Scott Whitlock
8

Jeśli konwersja ma coś wspólnego z widokiem, na przykład decyduje o widoczności obiektu, określa obraz do wyświetlenia lub zastanawia się, jakiego koloru pędzla użyć, zawsze umieszczam konwertery w widoku.

Jeśli jest to związane z biznesem, takie jak ustalenie, czy pole powinno być zamaskowane, lub jeśli użytkownik ma uprawnienia do wykonania akcji, konwersja nastąpi w moim ViewModel.

Z twoich przykładów, myślę, że tracisz duży kawałek WPF: DataTriggers. Wygląda na to, że używasz konwerterów do określania wartości warunkowych, ale konwertery powinny naprawdę służyć do konwersji jednego typu danych na inny.

W twoim przykładzie powyżej

Przykład: powiedzmy, że masz system stereo, który może być w trybie radia, CD lub MP3. Załóżmy, że istnieją elementy wizualne odpowiadające każdemu trybowi w różnych częściach interfejsu użytkownika. Możesz albo (1) pozwolić widokowi zdecydować, która grafika odpowiada danemu trybowi i odpowiednio je włączyć / wyłączyć, (2) wyświetlić właściwości modelu widoku dla każdej wartości trybu (np. IsModeRadio, IsModeCD) lub (3) odsłonić właściwości w modelu widoku dla każdego elementu / grupy graficznej (np. IsRadioLightOn, IsCDButtonGroupOn). (1) wydawało mi się naturalne dla mojego poglądu, ponieważ ma już świadomość trybu. Co myślisz w tym przypadku?

Chciałbym użyć a, DataTriggeraby określić, który obraz ma zostać wyświetlony, a nie Converter. Konwerter służy do konwersji jednego typu danych na inny, a wyzwalacz służy do określania niektórych właściwości na podstawie wartości.

<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>

Jedynym przypadkiem, w którym rozważę użycie Konwertera, jest to, czy powiązana wartość faktycznie zawierała dane obrazu, a ja musiałem przekonwertować ją na typ danych zrozumiały dla interfejsu użytkownika. Na przykład, jeśli źródło danych zawiera właściwość o nazwie ImageFilePath, to rozważę użycie Konwertera do konwersji ciągu zawierającego lokalizację pliku obrazu na taki, BitmapImagektóry mógłby być użyty jako źródło mojego obrazu

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

W rezultacie mam jedną przestrzeń nazw bibliotek pełną ogólnych konwerterów, które konwertują jeden typ danych na inny, i rzadko muszę kodować nowy konwerter. Są chwile, kiedy będę chciał konwerterów dla określonych konwersji, ale są one na tyle rzadkie, że nie mam nic przeciwko ich pisaniu.

Rachel
źródło
+1. Podnosisz dobre punkty. Używałem wcześniej wyzwalaczy, ale w moim przypadku nie wyłączam źródeł obrazów (które są właściwością), wyłączam całe Gridelementy. Próbuję również wykonywać takie czynności, jak ustawianie pędzli dla pierwszego planu / tła / obrysu w oparciu o dane w moim modelu widoku i określoną paletę kolorów zdefiniowaną w pliku konfiguracyjnym. Nie jestem pewien, czy to świetnie pasuje do wyzwalacza lub konwertera. Jedynym problemem, jaki mam do tej pory z umieszczeniem większości logiki widoku w modelu widoku, jest połączenie wszystkich RaisePropertyChanged()połączeń.
devuxer
@ DanM Zrobiłbym te wszystkie rzeczy w jednym DataTrigger, nawet wyłączając elementy Grid. Zazwyczaj umieszczam miejsce, w ContentControlktórym powinna znajdować się moja dynamiczna treść, i wymieniam ContentTemplateelement uruchamiający. Mam przykład pod poniższym linkiem, jeśli jesteś zainteresowany (przewiń w dół do sekcji z nagłówkiem Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel
Wcześniej używałem szablonów danych i kontroli treści, ale nigdy nie potrzebowałem wyzwalaczy, ponieważ zawsze miałem unikalny model widoku dla każdego widoku. W każdym razie twoja technika ma doskonały sens i jest dość elegancka, ale jest też bardzo gadatliwa. Z MatchToVisibility można to skrócić do: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"i<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer
1

Zależy to od tego, co testujesz.

Bez testów: zmieszaj Wyświetl kod z ViewModel do woli (zawsze możesz później refaktoryzować).
Testy na ViewModel i / lub niższym: użyj konwerterów.
Testy na warstwach modelu i / lub niższych: intermix Wyświetl kod z ViewModel do woli

ViewModel streszcza Model dla Widoku . Osobiście użyłbym ViewModel dla pędzli itp. I pominąłem konwertery. Testuj na warstwie (warstwach), w której dane są w „ najczystszej ” formie (tj. Warstwach modelu ).

Jake Berger
źródło
2
Ciekawe informacje na temat testowania, ale chyba nie widzę, jak logika konwertera w modelu widoku szkodzi testowalności modelu widoku? Na pewno nie sugeruję umieszczania rzeczywistych kontrolek interfejsu użytkownika w modelu widoku. Wystarczy zobaczyć specyficzne właściwości, takie jak Visibility, SolidColorBrushi Thickness.
devuxer 13.10.11
@DanM: Jeśli korzystasz z widoku Najpierw zobacz , nie ma problemu . Jednakże, niektóre używają ViewModel pierwszego podejścia gdzie ViewModel odwołuje się do widoku, to może być problematyczne .
Jake Berger
Cześć Jay, zdecydowanie pierwsze podejście. Widok nie wie nic o modelu widoku, z wyjątkiem nazw właściwości, z którymi musi się połączyć. Dzięki za kontynuację. +1.
devuxer 13.10.11
0

Prawdopodobnie nie rozwiąże to wszystkich problemów, o których wspomniałeś, ale należy wziąć pod uwagę dwie kwestie:

Najpierw musisz umieścić kod konwertera gdzieś w pierwszej strategii. Czy bierzesz pod uwagę tę część widoku lub model widoku? Jeśli jest to część widoku, dlaczego nie umieścić właściwości specyficznych dla widoku w widoku zamiast modelu widoku?

Po drugie, wygląda na to, że Twój projekt bez konwertera próbuje zmodyfikować właściwości istniejących obiektów, które już istnieją. Wygląda na to, że już zaimplementowali INotifyPropertyChanged, więc dlaczego nie skorzystać z utworzenia obiektu opakowania specyficznego dla widoku, z którym można się połączyć? Oto prosty przykład:

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");
        }
    }
}
John Fisher
źródło
Nie chciałem sugerować, że zmieniam właściwości mojego modelu encji bezpośrednio w widoku lub modelu widoku. Model widoku jest zdecydowanie inną warstwą niż warstwa mojego modelu encji. W rzeczywistości praca, którą wykonałem do tej pory, dotyczyła widoków tylko do odczytu. Nie oznacza to, że moja aplikacja nie wymaga żadnej edycji, ale nie widzę żadnych konwersji w kontrolkach używanych do edycji (więc załóżmy, że wszystkie wiązania są jednokierunkowe, z wyjątkiem zaznaczeń na listach). Dobra uwaga na temat „widoków danych”. To była koncepcja poruszona w poście o uczniach XAML, o którym wspomniałem na początku mojego pytania.
devuxer 13.10.11
0

Czasami dobrze jest użyć konwertera wartości, aby skorzystać z wirtualizacji.

Przykładem tego jest projekt, w którym musieliśmy wyświetlać dane w postaci maski bitowej dla setek tysięcy komórek w siatce. Kiedy dekodowaliśmy maski bitów w modelu widoku dla każdej pojedynczej komórki, ładowanie programu trwało zbyt długo.

Ale kiedy stworzyliśmy konwerter wartości, który dekodował pojedynczą komórkę, program ładował się w ułamku czasu i był tak samo responsywny, ponieważ konwerter jest wywoływany tylko wtedy, gdy użytkownik patrzy na określoną komórkę (i trzeba go było tylko wywołać maksymalnie trzydzieści razy za każdym razem, gdy użytkownik zmieni widok na siatce).

Nie wiem, jak MVVM skarżyło się na to rozwiązanie, ale skróciło czas ładowania o 95%.

kleineg
źródło