Sind Wertkonverter mehr Mühe als sie wert sind?

20

Ich arbeite an einer WPF-Anwendung mit Ansichten, die zahlreiche Wertkonvertierungen erfordern. Zunächst meine Philosophie (inspiriert teilweise von dieser lebhaften Debatte über XAML Disciples war) , dass ich die Ansicht Modell streng über die Unterstützung der machen sollte Daten Anforderungen der Ansicht. Dies bedeutete, dass alle Wertkonvertierungen, die erforderlich waren, um Daten in Dinge wie Sichtbarkeiten, Pinsel, Größen usw. umzuwandeln, mit Wertkonvertern und Mehrfachwertkonvertern durchgeführt wurden. Konzeptionell schien dies ziemlich elegant. Das Ansichtsmodell und die Ansicht hätten beide einen bestimmten Zweck und wären gut entkoppelt. Eine klare Linie würde zwischen "Daten" und "Aussehen" gezogen.

Nun, nachdem ich dieser Strategie den "alten College-Versuch" gegeben habe, habe ich einige Zweifel, ob ich mich auf diese Weise weiterentwickeln möchte. Eigentlich denke ich stark darüber nach, die Wertkonverter zu entleeren und die Verantwortung für (fast) alle Wertkonvertierungen direkt in die Hände des Ansichtsmodells zu legen.

Die Realität der Verwendung von Wertkonvertern scheint einfach nicht dem scheinbaren Wert sauber getrennter Belange zu entsprechen. Mein größtes Problem bei Wertkonvertern ist, dass deren Verwendung mühsam ist. Sie müssen eine neue Klasse erstellen , den Wert oder die Werte implementieren IValueConverteroder in den richtigen Typ umwandeln, auf (zumindest für mehrwertige Konverter) testen , die Konvertierungslogik schreiben und den Konverter in einem Ressourcenwörterbuch registrieren [siehe Aktualisierung unten ] und schließlich schließen Sie den Konverter mit ziemlich ausführlichem XAML an (was die Verwendung von magischen Zeichenfolgen sowohl für die Bindung (en) als auch für den Namen des Konverters erfordert)IMultiValueConverterobjectDependencyProperty.Unset[siehe Update unten]). Der Debugging-Prozess ist auch kein Picknick, da Fehlermeldungen häufig kryptisch sind, insbesondere im Entwurfsmodus / Expression Blend von Visual Studio.

Das heißt nicht, dass die Alternative, das Ansichtsmodell für alle Wertkonvertierungen verantwortlich zu machen, eine Verbesserung ist. Dies könnte sehr gut darauf zurückzuführen sein, dass das Gras auf der anderen Seite grüner ist. Abgesehen davon, dass Sie die elegante Trennung von Bedenken verlieren, müssen Sie eine Reihe abgeleiteter Eigenschaften schreiben und sicherstellen, dass Sie die Basiseigenschaften gewissenhaft RaisePropertyChanged(() => DerivedProperty)festlegen, was sich als unangenehmes Wartungsproblem erweisen kann.

Das Folgende ist eine erste Liste von Vor- und Nachteilen, die ich zusammengestellt habe, um Ansichtsmodellen den Umgang mit der Konvertierungslogik und die Beseitigung von Wertkonvertern zu ermöglichen:

  • Vorteile:
    • Weniger Gesamtbindungen, da Multikonverter entfallen
    • Weniger magische Zeichenfolgen (Bindungspfade + Konverter-Ressourcennamen )
    • Keine Registrierung mehr für jeden Konverter (plus Pflege dieser Liste)
    • Weniger Arbeit zum Schreiben jedes Konverters (keine Implementierung von Schnittstellen oder Casting erforderlich)
    • Kann problemlos Abhängigkeiten einfügen, um die Konvertierung zu erleichtern (z. B. Farbtabellen)
    • XAML-Markups sind weniger ausführlich und einfacher zu lesen
    • Wiederverwendung des Konverters weiterhin möglich (obwohl eine gewisse Planung erforderlich ist)
    • Keine mysteriösen Probleme mit DependencyProperty.Unset (ein Problem, das ich bei mehrwertigen Konvertern festgestellt habe)

* Durchgestrichene Symbole kennzeichnen Vorteile, die bei Verwendung von Markup-Erweiterungen nicht mehr angezeigt werden (siehe Update unten).

  • Nachteile:
    • Stärkere Kopplung zwischen Ansichtsmodell und Ansicht (z. B. müssen Eigenschaften Konzepte wie Sichtbarkeit und Pinsel behandeln)
    • Weitere Gesamteigenschaften, um eine direkte Zuordnung für jede angezeigte Bindung zu ermöglichen
    • RaisePropertyChangedmuss für jede abgeleitete Eigenschaft aufgerufen werden (siehe Update 2 unten)
    • Muss sich weiterhin auf Konverter verlassen, wenn die Konvertierung auf einer Eigenschaft eines Oberflächenelements basiert

Wie Sie wahrscheinlich feststellen können, habe ich Sodbrennen in Bezug auf dieses Problem. Ich zögere sehr, mich dem Refactoring zu widmen, nur um festzustellen, dass der Codierungsprozess genauso ineffizient und mühsam ist, ob ich Wertkonverter verwende oder zahlreiche Wertkonvertierungseigenschaften in meinem Ansichtsmodell verfügbar mache.

Vermisse ich irgendwelche Vor- / Nachteile? Für diejenigen, die beide Mittel der Wertumwandlung ausprobiert haben, welche haben Sie für besser befunden und warum? Gibt es noch andere Alternativen? (Die Schüler erwähnten etwas über Typdeskriptor-Anbieter, aber ich konnte nicht verstehen, worüber sie sprachen. Einsichten darüber wären willkommen.)


Aktualisieren

Ich habe heute herausgefunden, dass es möglich ist, eine sogenannte "Markup-Erweiterung" zu verwenden, um die Notwendigkeit zu beseitigen, Wertkonverter zu registrieren. Tatsächlich müssen sie nicht nur nicht mehr registriert werden, sondern es wird auch eine intelligente Funktion zum Auswählen eines Konverters während der Eingabe bereitgestellt Converter=. Hier ist der Artikel, mit dem ich angefangen habe: http://www.wpftutorial.net/ValueConverters.html .

Die Möglichkeit, eine Markup-Erweiterung zu verwenden, ändert das Gleichgewicht in meiner Auflistung der Vor- und Nachteile sowie in der obigen Diskussion etwas (siehe durchgestrichene Punkte).

Als Ergebnis dieser Enthüllung experimentiere ich mit einem Hybridsystem, in dem ich Konverter verwende BoolToVisibilityund das, was ich nenne, MatchToVisibilityund das Ansichtsmodell für alle anderen Konvertierungen. MatchToVisibility ist im Grunde ein Konverter, mit dem ich überprüfen kann, ob der gebundene Wert (normalerweise eine Aufzählung) mit einem oder mehreren in XAML angegebenen Werten übereinstimmt.

Beispiel:

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

Grundsätzlich wird überprüft, ob der Status entweder "Fertig" oder "Abgebrochen" ist. Wenn dies der Fall ist, wird die Sichtbarkeit auf "Sichtbar" gesetzt. Andernfalls wird es auf "Versteckt" gesetzt. Es stellte sich heraus, dass dies ein sehr häufiges Szenario war und dass dieser Konverter mir ungefähr 15 Eigenschaften in meinem Ansichtsmodell (plus zugehörige RaisePropertyChanged-Anweisungen) ersparte. Beachten Sie Converter={vc:, dass bei der Eingabe "MatchToVisibility" in einem Intellisense-Menü angezeigt wird. Dies verringert die Wahrscheinlichkeit von Fehlern merklich und macht die Verwendung von Wertkonvertern weniger mühsam (Sie müssen sich den Namen des gewünschten Wertkonverters nicht merken oder nachschlagen).

Falls Sie neugierig sind, füge ich den folgenden Code ein. Ein wichtiges Merkmal dieser Implementierung MatchToVisibilityist , dass es überprüft , ob der gebundene Wert eine ist enum, und wenn es ist, es überprüft , um sicherzustellen Value1, Value2usw. sind auch Aufzählungen des gleichen Typs. Dies bietet eine Entwurfs- und Laufzeitprüfung, um festzustellen, ob die Enum-Werte falsch eingegeben wurden. Um dies zu einer Überprüfung während der Kompilierung zu verbessern, können Sie stattdessen Folgendes verwenden (ich habe dies von Hand eingegeben, bitte verzeihen Sie mir, wenn ich Fehler gemacht habe):

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

Das ist zwar sicherer, aber zu ausführlich, um es mir wert zu sein. Ich könnte genauso gut eine Eigenschaft für das Ansichtsmodell verwenden, wenn ich dies tun werde. Auf jeden Fall finde ich, dass die Entwurfszeitprüfung für die Szenarien, die ich bisher ausprobiert habe, vollkommen ausreichend ist.

Hier ist der Code für 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;
    }
}

Hier ist der Code für 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();
    }
}

Hier ist die ToEnum-Erweiterungsmethode

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

Update 2

Seitdem ich diese Frage gestellt habe, bin ich auf ein Open-Source-Projekt gestoßen, das "IL-Weben" verwendet, um NotifyPropertyChanged-Code für Eigenschaften und abhängige Eigenschaften einzufügen. Dies macht die Implementierung von Josh Smiths Vision des Ansichtsmodells als "Wertkonverter für Steroide" zum Kinderspiel. Sie können einfach "Automatisch implementierte Eigenschaften" verwenden, den Rest erledigt der Weber.

Beispiel:

Wenn ich diesen Code eingebe:

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

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

... das wird kompiliert:

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

Das spart enorm viel Code, den Sie eingeben, lesen, durchblättern usw. müssen. Noch wichtiger ist jedoch, dass Sie nicht herausfinden müssen, welche Abhängigkeiten Sie haben. Sie können neue Eigenschaften hinzufügen, FullNameohne die Abhängigkeitskette mühsam zu durchlaufen, um sie in RaisePropertyChanged()Aufrufe einzufügen.

Wie heißt dieses Open-Source-Projekt? Die ursprüngliche Version heißt "NotifyPropertyWeaver", aber der Besitzer (Simon Potter) hat seitdem eine Plattform namens "Fody" für das Hosting einer ganzen Reihe von IL-Webern erstellt. Das Äquivalent zu NotifyPropertyWeaver unter dieser neuen Plattform heißt PropertyChanged.Fody.

Wenn Sie sich für NotifyPropertyWeaver entscheiden möchten (das etwas einfacher zu installieren ist, aber in Zukunft nicht unbedingt über Fehlerkorrekturen hinaus aktualisiert werden muss), finden Sie hier die Projektsite: http://code.google.com/p/ notifypropertyweaver /

In beiden Fällen verändern diese IL-Weaver-Lösungen das Kalkül in der Debatte zwischen dem Ansichtsmodell für Steroide und den Wertkonvertern vollständig.

Devuxer
quelle
Nur eine Anmerkung: BooleanToVisibilityNimmt einen Wert, der mit der Sichtbarkeit zusammenhängt (wahr / falsch) und übersetzt ihn in einen anderen. Dies scheint eine ideale Verwendung von a zu sein ValueConverter. Auf der anderen Seite MatchToVisibilityist die Codierung der Geschäftslogik in View(welche Arten von Elementen sollten sichtbar sein). Meiner Meinung nach sollte diese Logik auf das heruntergedrückt werden ViewModel, oder sogar noch weiter in das, was ich das nenne EditModel. Was der Benutzer sehen kann, sollte sich im Test befinden.
Scott Whitlock
@ Scott, das ist ein guter Punkt. Bei der App, an der ich gerade arbeite, handelt es sich nicht wirklich um eine "geschäftliche" App, bei der es unterschiedliche Berechtigungsstufen für Benutzer gibt. Daher habe ich nicht in diese Richtung gedacht. MatchToVisibilityschien eine bequeme Möglichkeit zu sein, einige einfache Modusschalter zu aktivieren (ich habe eine Ansicht, insbesondere mit einer Tonne von Teilen, die ein- und ausgeschaltet werden können. In den meisten Fällen sind Abschnitte der Ansicht sogar mit (mit x:Name) beschriftet , um dem Modus zu entsprechen sie entsprechen.) Es ist mir nicht wirklich eingefallen, dass dies "Geschäftslogik" ist, aber ich werde Ihren Kommentar etwas überlegen.
Devuxer
Beispiel: Angenommen, Sie hatten eine Stereoanlage, die sich entweder im Radio-, CD- oder MP3-Modus befindet. Angenommen, es gibt für jeden Modus in verschiedenen Bereichen der Benutzeroberfläche eine entsprechende Grafik. Sie können entweder (1) die Ansicht entscheiden lassen, welche Grafiken welchem ​​Modus entsprechen, und sie entsprechend aktivieren / deaktivieren, (2) die Eigenschaften des Ansichtsmodells für jeden Moduswert (z. B. IsModeRadio, IsModeCD) verfügbar machen oder (3) verfügbar machen Eigenschaften im Ansichtsmodell für jedes grafische Element / jede grafische Gruppe (z. B. IsRadioLightOn, IsCDButtonGroupOn). (1) schien meiner Ansicht nach selbstverständlich zu sein, da es bereits Modenbewusstsein besitzt. Was denkst du in diesem Fall?
Devuxer
Dies ist die längste Frage, die ich je in der gesamten SE gesehen habe! :]
trejder

Antworten:

10

Ich habe ValueConvertersin einigen Fällen die Logik verwendet und ViewModelin anderen die Logik eingesetzt . Ich habe das Gefühl, dass a ValueConverterTeil der ViewEbene wird. Wenn die Logik also wirklich Teil der Ebene ist, platziere Viewsie dort, andernfalls platziere sie in der Ebene ViewModel.

Persönlich sehe ich kein Problem mit dem ViewModelUmgang mit Viewspezifischen Konzepten wie Brushes, weil in meinen Anwendungen eine ViewModelnur als testbare und bindbare Oberfläche für die View. Einige Leute ViewModelfügen jedoch eine Menge Geschäftslogik hinzu (ich nicht) und in diesem Fall ViewModelist das eher ein Teil ihrer Geschäftsebene, sodass ich in diesem Fall keine WPF-spezifischen Inhalte haben möchte.

Ich bevorzuge eine andere Trennung:

  • View- WPF-Zeug, manchmal nicht testbar (wie XAML und Code-Behind), aber auch ValueConverters
  • ViewModel - Testbare und bindbare Klasse, die auch WPF-spezifisch ist
  • EditModel - Teil der Business-Schicht, die mein Modell während der Manipulation darstellt
  • EntityModel - Teil der Business-Schicht, die mein Modell als dauerhaft darstellt
  • Repository- verantwortlich für die Persistenz EntityModelder Datenbank

So wie ich es mache, habe ich wenig Sinn für ValueConverters

Die Art und Weise, wie ich mich von einigen Ihrer "Con" verabschiedet habe, ist, meine ViewModelsehr generisch zu machen . Ein Beispiel, das ViewModelich genannt habe, ChangeValueViewModelimplementiert eine Label-Eigenschaft und eine Value-Eigenschaft. Auf der Viewgibt es eine Label, die an die Label-Eigenschaft und eine TextBox, die an die Value-Eigenschaft bindet.

Ich habe dann einen, ChangeValueViewder DataTemplatevon dem ChangeValueViewModelTyp abgetastet ist . Wann immer WPF sieht, dass ViewModeles das anwendet View. Der Konstruktor von my ChangeValueViewModelübernimmt die Interaktionslogik, die er zum Aktualisieren seines Status benötigt EditModel(normalerweise wird nur ein a übergeben Func<string>), und die Aktion Action, die er ausführen muss, wenn der Benutzer den Wert bearbeitet (nur eine , die eine Logik in der ausführt EditModel).

Das übergeordnete Element ViewModel(für den Bildschirm) nimmt ein EditModelin seinen Konstruktor und instanziiert nur die entsprechenden elementaren Elemente ViewModelwie z ChangeValueViewModel. Da das übergeordnete ViewModelElement die auszuführende Aktion einfügt, wenn der Benutzer Änderungen vornimmt, kann es alle diese Aktionen abfangen und andere Aktionen ausführen. Daher könnte die injizierte Bearbeitungsaktion für a ChangeValueViewModelwie folgt aussehen:

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

Natürlich kann die foreachSchleife an einer anderen Stelle umgestaltet werden, aber dies bewirkt, dass die Aktion ausgeführt und auf das Modell angewendet wird. Dann (vorausgesetzt, das Modell hat seinen Status auf unbekannte Weise aktualisiert) werden alle Kinder ViewModelaufgefordert, ihren Status abzurufen das Modell wieder. Wenn sich der Zustand geändert hat, sind sie bei PropertyChangedBedarf für die Ausführung ihrer Ereignisse verantwortlich .

Das regelt die Interaktion zwischen beispielsweise einem Listenfeld und einem Detailfenster recht gut. Wenn der Benutzer eine neue Auswahl auswählt, aktualisiert er die EditModelAuswahl mit der Auswahl und EditModeländert die Werte der Eigenschaften, die für den Detailbereich verfügbar gemacht werden. Die ViewModelKinder, die für die Anzeige der Detailfensterinformationen verantwortlich sind, werden automatisch benachrichtigt, dass sie nach neuen Werten suchen müssen. Wenn sie sich geändert haben, lösen sie ihre PropertyChangedEreignisse aus.

Scott Whitlock
quelle
/Nicken. Das ist ziemlich ähnlich wie meins aussehen.
Ian
+1. Vielen Dank für Ihre Antwort, Scott. Ich habe so ziemlich die gleichen Ebenen wie Sie. Außerdem füge ich keine Geschäftslogik in das Ansichtsmodell ein. (Im Grunde genommen verwende ich EntityFramework Code First und habe eine Service-Ebene, die zwischen Ansichtsmodellen und Entitätsmodellen übersetzt und umgekehrt.) Vor diesem Hintergrund gehen Sie vermutlich davon aus, dass keine hohen Kosten anfallen um die gesamte Konvertierungslogik in der Ansichtsmodellebene zu platzieren.
Devuxer
@ DanM - Ja, ich stimme zu. Ich würde die Konvertierung in der ViewModelEbene vorziehen . Nicht jeder stimmt mir zu, aber es hängt davon ab, wie Ihre Architektur funktioniert.
Scott Whitlock
2
Nachdem ich den ersten Absatz gelesen hatte, wollte ich +1 sagen, aber dann habe ich Ihren zweiten gelesen und bin absolut nicht einverstanden damit, ansichtsspezifischen Code in die ViewModels einzufügen. Die einzige Ausnahme besteht darin, dass das ViewModel speziell für eine generische Ansicht erstellt wurde (z. B. CalendarViewModelfür ein CalendarViewUserControl oder ein DialogViewModelfür ein DialogView). Das ist nur meine Meinung :)
Rachel
@ Rachel - nun, wenn du nach meinem zweiten Absatz weitergelesen hättest, würdest du sehen, dass das genau das ist, was ich tat. :) Es gibt keine Geschäftslogik in meinem ViewModels.
Scott Whitlock
8

Wenn die Konvertierung etwas mit der Ansicht zu tun hat, z. B. die Sichtbarkeit eines Objekts zu bestimmen, das anzuzeigende Bild zu bestimmen oder die zu verwendende Pinselfarbe zu ermitteln, lege ich meine Konverter immer in die Ansicht.

Wenn es geschäftsbezogen ist, z. B. wenn festgelegt wird, ob ein Feld maskiert werden soll, oder wenn ein Benutzer über die Berechtigung zum Ausführen einer Aktion verfügt, erfolgt die Konvertierung in meinem ViewModel.

Von Ihrem Beispiel, ich glaube , Sie ein großes Stück WPF sind vermisst: DataTriggers. Sie scheinen Konverter zum Bestimmen von Bedingungswerten zu verwenden, aber Konverter sollten wirklich zum Konvertieren eines Datentyps in einen anderen dienen.

In deinem obigen Beispiel

Beispiel: Angenommen, Sie hatten eine Stereoanlage, die sich entweder im Radio-, CD- oder MP3-Modus befindet. Angenommen, es gibt für jeden Modus in verschiedenen Bereichen der Benutzeroberfläche eine entsprechende Grafik. Sie können entweder (1) die Ansicht entscheiden lassen, welche Grafiken welchem ​​Modus entsprechen, und sie entsprechend aktivieren / deaktivieren, (2) die Eigenschaften des Ansichtsmodells für jeden Moduswert (z. B. IsModeRadio, IsModeCD) verfügbar machen oder (3) verfügbar machen Eigenschaften im Ansichtsmodell für jedes grafische Element / jede grafische Gruppe (z. B. IsRadioLightOn, IsCDButtonGroupOn). (1) schien meiner Ansicht nach selbstverständlich zu sein, da es bereits Modenbewusstsein besitzt. Was denkst du in diesem Fall?

Ich würde ein verwenden DataTrigger, um zu bestimmen, welches Bild angezeigt werden soll, nicht ein Converter. Ein Konverter dient zum Konvertieren eines Datentyps in einen anderen, während ein Trigger verwendet wird, um einige Eigenschaften basierend auf einem Wert zu bestimmen.

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

Ich würde nur in Betracht ziehen, einen Konverter zu verwenden, wenn der gebundene Wert tatsächlich die Bilddaten enthält und ich ihn in einen Datentyp konvertieren muss, den die Benutzeroberfläche verstehen kann. Wenn die Datenquelle beispielsweise eine aufgerufene Eigenschaft enthält ImageFilePath, würde ich in Betracht ziehen, die Zeichenfolge mit dem Speicherort der Bilddatei mit einem Konverter in eine zu konvertieren, die BitmapImageals Quelle für mein Bild verwendet werden kann

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

Das Endergebnis ist, dass ich einen Bibliotheksnamensraum voller generischer Konverter habe, die einen Datentyp in einen anderen konvertieren, und selten einen neuen Konverter codieren muss. Es gibt Fälle, in denen ich Konverter für bestimmte Konvertierungen haben möchte, aber sie sind selten genug, dass es mir nichts ausmacht, sie zu schreiben.

Rachel
quelle
+1. Sie sprechen einige gute Punkte an. Ich habe bereits Trigger verwendet, aber in meinem Fall tausche ich keine Bildquellen (die eine Eigenschaft sind) aus, sondern ganze GridElemente. Ich versuche auch, Pinsel für Vordergrund / Hintergrund / Kontur festzulegen, basierend auf den Daten in meinem Ansichtsmodell und einer bestimmten Farbpalette, die in der Konfigurationsdatei definiert ist. Ich bin mir nicht sicher, ob dies für einen Trigger oder einen Konverter geeignet ist. Das einzige Problem, das ich bisher habe, wenn ich die meiste Ansichtslogik in das Ansichtsmodell einbaue, ist die Verkabelung aller RaisePropertyChanged()Anrufe.
Devuxer
@DanM Ich würde all diese Dinge tatsächlich in einem DataTriggerGrid tun und sogar die Elemente des Grids austauschen . Normalerweise platziere ich einen, ContentControlwo sich mein dynamischer Inhalt befinden soll, und tausche den ContentTemplatein einem Trigger aus. Ich habe ein Beispiel unter folgendem Link , wenn Sie daran interessiert sind (navigieren Sie zu dem Abschnitt nach unten mit dem Header Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/...
Rachel
Ich habe zuvor Datenvorlagen und Inhaltssteuerelemente verwendet, aber ich habe nie Trigger benötigt, da ich immer ein eindeutiges Ansichtsmodell für jede Ansicht hatte. Wie auch immer, Ihre Technik macht vollkommen Sinn und ist ziemlich elegant, aber sie ist auch sehr ausführlich. Mit MatchToVisibility könnte dies verkürzt werden: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"und<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
Devuxer
1

Es hängt davon ab, was Sie testen , wenn überhaupt.

Keine Tests: Mischen Sie den Code mit ViewModel nach Belieben (Sie können ihn später jederzeit umgestalten).
Tests auf ViewModel und / oder niedriger: Konverter verwenden.
Tests auf Modellebenen und / oder niedriger: Code nach Belieben mit ViewModel mischen

ViewModel abstrahiert das Modell für die Ansicht . Persönlich würde ich ViewModel für Pinsel usw. verwenden und die Konverter überspringen. Testen Sie die Ebene (n), auf der sich die Daten in ihrer " reinsten " Form befinden (dh Modellebenen ).

Jake Berger
quelle
2
Interessante Punkte zum Testen, aber ich vermute, ich sehe nicht, wie die Konverterlogik im Ansichtsmodell die Testbarkeit des Ansichtsmodells beeinträchtigt. Ich schlage nicht vor, tatsächliche UI- Steuerelemente in das Ansichtsmodell einzufügen. Nur sieht spezifische Eigenschaften wie Visibility, SolidColorBrushund Thickness.
Devuxer
@DanM: Wenn Sie einen View-first- Ansatz verwenden, ist das kein Problem . Einige verwenden jedoch einen ViewModel-first- Ansatz, bei dem das ViewModel auf eine Ansicht verweist. Dies kann problematisch sein .
Jake Berger
Hallo Jay, definitiv ein Blickfang. Die Ansicht kennt das Ansichtsmodell nur mit den Namen der Eigenschaften, an die sie gebunden werden muss. Vielen Dank für das Follow-up. +1.
Devuxer
0

Dies wird wahrscheinlich nicht alle Probleme lösen, die Sie angesprochen haben, aber es gibt zwei Punkte zu beachten:

Zunächst müssen Sie den Konvertercode in Ihrer ersten Strategie platzieren. Betrachten Sie diesen Teil der Ansicht oder des Ansichtsmodells? Wenn es Teil der Ansicht ist, platzieren Sie die ansichtsspezifischen Eigenschaften in der Ansicht anstelle des Ansichtsmodells.

Zweitens scheint es, als würde Ihr Nicht-Konverter-Entwurf versuchen, die tatsächlichen Objekteigenschaften zu ändern, die bereits vorhanden sind. Es hört sich so an, als würden sie bereits INotifyPropertyChanged implementieren. Warum also nicht ein ansichtsspezifisches Wrapper-Objekt zum Binden verwenden? Hier ist ein einfaches Beispiel:

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
quelle
Ich wollte nicht implizieren, dass ich die Eigenschaften meines Entitätsmodells direkt in der Ansicht oder im Ansichtsmodell ändere. Das Ansichtsmodell ist definitiv eine andere Ebene als die Ebene meines Entitätsmodells. Die Arbeit, die ich bisher an schreibgeschützten Ansichten geleistet habe. Das soll nicht heißen, dass meine Anwendung keine Bearbeitung beinhaltet, aber ich sehe keine Konvertierungen für Steuerelemente, die zum Bearbeiten verwendet werden (nehmen Sie also an, dass alle Bindungen nur in eine Richtung erfolgen, mit Ausnahme von Auswahlen in Listen). Guter Punkt zu "Datenansichten". Das war ein Konzept, das in dem XAML-Jüngerbeitrag angesprochen wurde, auf den ich mich oben in meiner Frage bezog.
Devuxer
0

Manchmal ist es gut, einen Wertekonverter zu verwenden, um die Virtualisierung zu nutzen.

Ein Beispiel dafür, was in einem Projekt, in dem wir bitmaskierte Daten für Hunderttausende von Zellen in einem Raster anzeigen mussten. Als wir die Bitmasken im Ansichtsmodell für jede einzelne Zelle dekodierten, dauerte das Laden des Programms viel zu lange.

Als wir jedoch einen Wertekonverter erstellten, der eine einzelne Zelle dekodierte, wurde das Programm in einem Bruchteil der Zeit geladen und reagierte genauso, da der Konverter nur aufgerufen wird, wenn der Benutzer eine bestimmte Zelle ansieht (und dies nur aufgerufen werden müsste maximal dreißig Mal, wenn der Benutzer seinen Blick auf das Raster verschiebt).

Ich weiß nicht, wie MVVM diese Lösung beanstandet hat, aber sie hat die Ladezeit um 95% verkürzt.

kleineg
quelle