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 IValueConverter
oder 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)IMultiValueConverter
object
DependencyProperty.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
(siehe Update 2 unten)RaisePropertyChanged
muss für jede abgeleitete Eigenschaft aufgerufen werden- 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 BoolToVisibility
und das, was ich nenne, MatchToVisibility
und 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 MatchToVisibility
ist , dass es überprüft , ob der gebundene Wert eine ist enum
, und wenn es ist, es überprüft , um sicherzustellen Value1
, Value2
usw. 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, FullName
ohne 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.
- Anweisungen zum Einrichten von Fody: http://code.google.com/p/fody/wiki/SampleUsage (ersetzen Sie "Virtuosity" durch "PropertyChanged")
- PropertyChanged.Fody-Projektsite: http://code.google.com/p/propertychanged/
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.
MatchToVisibility
schien 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.
BooleanToVisibility
Nimmt einen Wert, der mit der Sichtbarkeit zusammenhängt (wahr / falsch) und übersetzt ihn in einen anderen. Dies scheint eine ideale Verwendung von a zu seinValueConverter
. Auf der anderen SeiteMatchToVisibility
ist die Codierung der Geschäftslogik inView
(welche Arten von Elementen sollten sichtbar sein). Meiner Meinung nach sollte diese Logik auf das heruntergedrückt werdenViewModel
, oder sogar noch weiter in das, was ich das nenneEditModel
. Was der Benutzer sehen kann, sollte sich im Test befinden.