Sto lavorando a un'applicazione WPF con viste che richiedono numerose conversioni di valore. Inizialmente, la mia filosofia (ispirata in parte a questo vivace dibattito sui discepoli di XAML ) era che avrei dovuto fare il modello di vista strettamente sul supporto dei requisiti dei dati della vista. Ciò significava che qualsiasi conversione di valore richiesta per trasformare i dati in cose come visibilità, pennelli, dimensioni, ecc. Sarebbe gestita con convertitori di valori e convertitori di più valori. Concettualmente, questo sembrava abbastanza elegante. Il modello di vista e la vista avrebbero entrambi uno scopo distinto e sarebbero ben disaccoppiati. Verrà tracciata una linea chiara tra "dati" e "aspetto".
Bene, dopo aver dato a questa strategia "il vecchio tentativo del college", ho dei dubbi sul fatto che voglio continuare a sviluppare in questo modo. In realtà sto prendendo in seria considerazione l'idea di scaricare i convertitori di valore e di mettere la responsabilità di (quasi) tutta la conversione del valore esattamente nelle mani del modello di visualizzazione.
La realtà dell'utilizzo dei convertitori di valore non sembra essere all'altezza del valore apparente di preoccupazioni nettamente separate. Il mio più grande problema con i convertitori di valore è che sono noiosi da usare. Devi creare una nuova classe, implementare IValueConverter
o IMultiValueConverter
, trasmettere il valore o i valori da object
al tipo corretto, testare DependencyProperty.Unset
(almeno per i convertitori multi-valore), scrivere la logica di conversione, registrare il convertitore in un dizionario di risorse [vedi aggiornamento di seguito ] e infine collegare il convertitore usando XAML piuttosto prolisso (che richiede l'uso di stringhe magiche sia per le legature che per il nome del convertitore[vedi aggiornamento sotto]). Il processo di debug non è nemmeno un picnic, poiché i messaggi di errore sono spesso criptici, specialmente nella modalità di progettazione di Visual Studio / Expression Blend.
Questo non vuol dire che l'alternativa - rendere il modello di visualizzazione responsabile di tutta la conversione del valore - sia un miglioramento. Questo potrebbe benissimo essere una questione di erba più verde dall'altra parte. Oltre a perdere l'elegante separazione delle preoccupazioni, devi scrivere un sacco di proprietà derivate e assicurarti di chiamare coscienziosamente RaisePropertyChanged(() => DerivedProperty)
quando imposti le proprietà di base, che potrebbero rivelarsi un problema di manutenzione spiacevole.
Di seguito è riportato un elenco iniziale dei vantaggi e degli svantaggi di consentire ai modelli di visualizzazione di gestire la logica di conversione e di eliminare i convertitori di valore:
- Professionisti:
- Meno vincoli totali poiché i multi-convertitori vengono eliminati
- Meno stringhe magiche (percorsi di associazione
+ nomi delle risorse del convertitore) Non è più necessario registrare ciascun convertitore (oltre a mantenere questo elenco)- Meno lavoro per scrivere ogni convertitore (non sono necessarie interfacce di implementazione o casting)
- Può iniettare facilmente dipendenze per facilitare le conversioni (ad es. Tabelle dei colori)
- Il markup XAML è meno dettagliato e più facile da leggere
- Il riutilizzo del convertitore è ancora possibile (sebbene sia necessaria una certa pianificazione)
- Nessun problema misterioso con DependencyProperty.Unset (un problema che ho notato con i convertitori multi-valore)
* Le barrature indicano i vantaggi che scompaiono se si utilizzano le estensioni di markup (vedere l'aggiornamento di seguito)
- Contro:
- Accoppiamento più forte tra modello di vista e vista (ad esempio, le proprietà devono affrontare concetti come visibilità e pennelli)
- Altre proprietà totali per consentire la mappatura diretta per ogni associazione in vista
(vedere l'aggiornamento 2 di seguito)RaisePropertyChanged
deve essere chiamato per ciascuna proprietà derivata- Deve ancora fare affidamento sui convertitori se la conversione si basa su una proprietà di un elemento dell'interfaccia utente
Quindi, come probabilmente puoi dire, ho qualche bruciore di stomaco su questo problema. Sono molto riluttante a seguire la strada del refactoring solo per rendermi conto che il processo di codifica è altrettanto inefficiente e noioso se utilizzo convertitori di valore o esponendo numerose proprietà di conversione di valore nel mio modello di visualizzazione.
Mi sto perdendo qualche pro / contro? Per coloro che hanno provato entrambi i metodi di conversione del valore, quale hai trovato meglio di te e perché? Ci sono altre alternative? (I discepoli menzionarono qualcosa sui fornitori di descrittori di tipi, ma non riuscivo a capire cosa stessero parlando. Qualunque intuizione su questo sarebbe apprezzata.)
Aggiornare
Oggi ho scoperto che è possibile utilizzare qualcosa chiamato "estensione markup" per eliminare la necessità di registrare convertitori di valori. In realtà, non solo elimina la necessità di registrarli, ma in realtà fornisce intellisense per la selezione di un convertitore durante la digitazione Converter=
. Ecco l'articolo che mi ha fatto iniziare: http://www.wpftutorial.net/ValueConverters.html .
La possibilità di utilizzare un'estensione di markup modifica un po 'l'equilibrio nei miei elenchi di pro e contro e nella discussione sopra (vedi barrati).
Come risultato di questa rivelazione, sto sperimentando un sistema ibrido in cui uso i convertitori BoolToVisibility
e ciò che chiamo MatchToVisibility
e il modello di visualizzazione per tutte le altre conversioni. MatchToVisibility è fondamentalmente un convertitore che mi consente di verificare se il valore associato (di solito un enum) corrisponde a uno o più valori specificati in XAML.
Esempio:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
Fondamentalmente ciò che fa è verificare se lo stato è Finito o Annullato. In tal caso, la visibilità viene impostata su "Visibile". Altrimenti, viene impostato su "Nascosto". Questo si è rivelato uno scenario molto comune e avere questo convertitore mi ha salvato circa 15 proprietà nel mio modello di vista (più le istruzioni RaisePropertyChanged associate). Quando digiti Converter={vc:
, "MatchToVisibility" appare in un menu di intellisense. Ciò riduce notevolmente la possibilità di errori e rende meno noioso l'uso dei convertitori di valori (non è necessario ricordare o cercare il nome del convertitore di valore desiderato).
Nel caso in cui tu sia curioso, incollerò il codice qui sotto. Una caratteristica importante di questa implementazione di MatchToVisibility
è che si controlla se il valore limite è una enum
, e se lo è, i controlli per assicurarsi Value1
, Value2
ecc sono anche enumerazioni dello stesso tipo. Ciò fornisce un controllo in fase di progettazione e in fase di esecuzione se uno qualsiasi dei valori enum è errato. Per migliorare questo con un controllo in fase di compilazione, puoi invece utilizzare quanto segue (l'ho digitato a mano, quindi per favore perdonami se ho commesso degli errori):
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}}"
Sebbene sia più sicuro, è troppo prolisso per valerne la pena. Potrei anche usare una proprietà sul modello di vista se ho intenzione di farlo. Ad ogni modo, sto scoprendo che il controllo in fase di progettazione è perfettamente adeguato per gli scenari che ho provato finora.
Ecco il codice per 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;
}
}
Ecco il codice per 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();
}
}
Ecco il metodo di estensione ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
Aggiornamento 2
Da quando ho pubblicato questa domanda, mi sono imbattuto in un progetto open source che utilizza "IL weaving" per iniettare codice NotifyPropertyChanged per proprietà e proprietà dipendenti. Ciò rende l'implementazione della visione di Josh Smith del modello di vista come un "convertitore di valore per gli steroidi" un gioco da ragazzi. Puoi semplicemente usare "Proprietà implementate automaticamente" e il tessitore farà il resto.
Esempio:
Se inserisco questo codice:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... questo è ciò che viene compilato:
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);
}
}
Questo è un enorme risparmio nella quantità di codice che devi digitare, leggere, scorrere, ecc. Ancora più importante, tuttavia, ti evita di dover capire quali sono le tue dipendenze. È possibile aggiungere nuove "proprietà ottiene" come FullName
senza dover scrupolosamente salire la catena di dipendenze per aggiungere RaisePropertyChanged()
chiamate.
Come si chiama questo progetto open source? La versione originale si chiama "NotifyPropertyWeaver", ma il proprietario (Simon Potter) da allora ha creato una piattaforma chiamata "Fody" per ospitare un'intera serie di tessitori IL. L'equivalente di NotifyPropertyWeaver in questa nuova piattaforma si chiama PropertyChanged.Fody.
- Istruzioni per la configurazione di Fody: http://code.google.com/p/fody/wiki/SampleUsage (sostituisci "Virtuosity" con "PropertyChanged")
- PropertyChanged. Sito del progetto: http://code.google.com/p/propertychanged/
Se preferisci utilizzare NotifyPropertyWeaver (che è un po 'più semplice da installare, ma non sarà necessariamente aggiornato in futuro oltre alle correzioni di bug), ecco il sito del progetto: http://code.google.com/p/ notifypropertyweaver /
Ad ogni modo, queste soluzioni di tessitori IL cambiano completamente il calcolo nel dibattito tra il modello di visualizzazione sugli steroidi rispetto ai convertitori di valore.
MatchToVisibility
sembrava essere un modo conveniente per abilitare alcuni semplici selettori di modalità (ho una vista in particolare con una tonnellata di parti che possono essere accese e spente. Nella maggior parte dei casi, le sezioni della vista sono persino etichettate (con x:Name
) per adattarsi alla modalità corrispondono a.) Non mi è mai venuto in mente che si tratta di "logica commerciale", ma darò un pensiero al tuo commento.
BooleanToVisibility
prende un valore correlato alla visibilità (vero / falso) e lo traduce in un altro. Sembra un uso ideale di aValueConverter
. D'altra parte,MatchToVisibility
sta codificando la logica di business inView
(quali tipi di elementi dovrebbero essere visibili). Secondo me questa logica dovrebbe essere spinta verso il bassoViewModel
, o anche più in quello che io chiamo ilEditModel
. Ciò che l'utente può vedere dovrebbe essere qualcosa sotto test.