I convertitori di valore hanno più problemi di quanti ne valgano?


20

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 IValueConvertero IMultiValueConverter, trasmettere il valore o i valori da objectal 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
    • RaisePropertyChangeddeve essere chiamato per ciascuna proprietà derivata (vedere l'aggiornamento 2 di seguito)
    • 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 BoolToVisibilitye ciò che chiamo MatchToVisibilitye 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, Value2ecc 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 FullNamesenza 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.

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.


Solo una nota: BooleanToVisibilityprende un valore correlato alla visibilità (vero / falso) e lo traduce in un altro. Sembra un uso ideale di a ValueConverter. D'altra parte, MatchToVisibilitysta codificando la logica di business in View(quali tipi di elementi dovrebbero essere visibili). Secondo me questa logica dovrebbe essere spinta verso il basso ViewModel, o anche più in quello che io chiamo il EditModel. Ciò che l'utente può vedere dovrebbe essere qualcosa sotto test.
Scott Whitlock,

@Scott, questo è un buon punto. L'app a cui sto lavorando in questo momento non è in realtà un'app "aziendale", in cui esistono diversi livelli di autorizzazione per gli utenti, quindi non stavo pensando in questo senso. MatchToVisibilitysembrava 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.
Devuxer,

Esempio: supponiamo che tu abbia un sistema stereo che potrebbe essere in modalità radio, CD o MP3. Supponiamo che ci siano elementi visivi corrispondenti a ciascuna modalità in diverse parti dell'interfaccia utente. È possibile (1) consentire alla vista di decidere quale grafica corrisponde a quale modalità e attivarle / disattivarle di conseguenza, (2) esporre le proprietà sul modello di vista per ciascun valore della modalità (ad es. IsModeRadio, IsModeCD) o (3) esporre proprietà sul modello di vista per ciascun elemento / gruppo grafico (ad es. IsRadioLightOn, IsCDButtonGroupOn). (1) mi è sembrato naturale per me, perché ha già la consapevolezza della modalità. Cosa ne pensi in questo caso?
Devuxer,

Questa è la domanda più lunga che abbia mai visto in tutto il SE! :]
trejder

Risposte:


10

Ho usato ValueConvertersin alcuni casi e messo la logica ViewModelin altri. La mia sensazione è che a ValueConverterdiventi parte del Viewlivello, quindi se la logica è davvero parte del Viewallora mettilo lì, altrimenti mettilo nel ViewModel.

Personalmente non vedo alcun problema con la ViewModelgestione di Viewconcetti specifici come Brushes perché nelle mie applicazioni ViewModelesiste solo una superficie testabile e vincolante per il View. Tuttavia, alcune persone mettono molta logica di business nel ViewModel(io no) e in quel caso ViewModelè più simile a una parte del loro livello aziendale, quindi in quel caso non vorrei cose specifiche di WPF.

Preferisco una separazione diversa:

  • View- Roba WPF, a volte non verificabile (come XAML e code-behind) ma anche ValueConverters
  • ViewModel - classe verificabile e associabile che è anche specifica di WPF
  • EditModel - parte del livello aziendale che rappresenta il mio modello durante la manipolazione
  • EntityModel - parte del livello aziendale che rappresenta il mio modello come persistente
  • Repository- responsabile della persistenza del EntityModelnel database

Quindi, nel modo in cui lo faccio, ho poca utilità per ValueConverters

Il modo in cui mi sono allontanato da alcuni dei tuoi "Con" è di rendere il mio ViewModelmolto generico. Ad esempio, uno ViewModelche ho, chiamato ChangeValueViewModelimplementa una proprietà Label e una proprietà Value. Sul Viewc'è un Labelche si lega alla proprietà Label e un TextBoxche si lega alla proprietà Value.

Ho quindi un ChangeValueViewche è un tipo DataTemplatedisattivato ChangeValueViewModel. Ogni volta che WPF vede che ViewModello applica View. Il costruttore del mio ChangeValueViewModelprende la logica di interazione di cui ha bisogno per aggiornare il suo stato da EditModel(di solito passa solo in a Func<string>) e l'azione che deve intraprendere quando l'utente modifica il Valore (solo un Actionche esegue una logica in EditModel).

Il genitore ViewModel(per lo schermo) prende un EditModelnel suo costruttore e crea semplicemente un'istanza di elementi elementari appropriati ViewModelcome ChangeValueViewModel. Poiché il genitore ViewModelsta iniettando l'azione da eseguire quando l'utente apporta qualsiasi modifica, può intercettare tutte queste azioni e intraprendere altre azioni. Pertanto, l'azione di modifica iniettata per a ChangeValueViewModelpotrebbe apparire come:

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

Ovviamente il foreachloop può essere refactored altrove, ma ciò che fa è agire, applicarlo al modello, quindi (supponendo che il modello abbia aggiornato il suo stato in un modo sconosciuto), dice a tutti i bambini ViewModeldi andare e ottenere il loro stato da di nuovo il modello. Se lo stato è cambiato, sono responsabili dell'esecuzione dei loro PropertyChangedeventi, se necessario.

Questo gestisce abbastanza bene l'interazione tra, per esempio, una casella di riepilogo e un pannello dei dettagli. Quando l'utente seleziona una nuova scelta, la aggiorna EditModelcon la scelta e EditModelcambia i valori delle proprietà esposte per il pannello dei dettagli. I ViewModelbambini che sono responsabili della visualizzazione delle informazioni del pannello dei dettagli vengono automaticamente informati della necessità di verificare la presenza di nuovi valori e, se modificati, generano i loro PropertyChangedeventi.


/cenno. È abbastanza simile al mio aspetto.
Ian,

+1. Grazie per la tua risposta, Scott, ho più o meno gli stessi livelli che hai tu, e inoltre non inserisco la logica aziendale nel modello di visualizzazione. (Per la cronaca, sto usando EntityFramework Code First e ho un livello di servizio che si traduce tra modelli di vista e modelli di entità e viceversa.) Quindi, dato questo, immagino che stai dicendo che non ci sono molti costi mettere tutta / la maggior parte della logica di conversione nel livello del modello di vista.
Devuxer,

@DanM - Sì, sono d'accordo. Preferirei la conversione nel ViewModellivello. Non tutti sono d'accordo con me, ma dipende da come funziona la tua architettura.
Scott Whitlock,

2
Stavo per dire +1 dopo aver letto il primo paragrafo, ma poi ho letto il tuo secondo e non sono affatto d'accordo con l'inserimento di codice specifico per la vista nei ViewModels. L'unica eccezione è se ViewModel è stato creato appositamente per andare dietro una vista generica (come a CalendarViewModelper un CalendarViewUserControl o a DialogViewModelper a DialogView). Questa è solo la mia opinione però :)
Rachel

@ Rachel - beh, se avessi continuato a leggere oltre il mio secondo paragrafo, vedresti che è esattamente quello che stavo facendo. :) Non c'è alcuna logica aziendale nei miei messaggi ViewModel.
Scott Whitlock,

8

Se la conversione è qualcosa correlato alla vista, come decidere la visibilità di un oggetto, determinare quale immagine visualizzare o capire quale colore del pennello usare, metto sempre i miei convertitori nella vista.

Se relativo all'attività, come determinare se un campo deve essere mascherato o se un utente dispone dell'autorizzazione per eseguire un'azione, la conversione avviene nel mio ViewModel.

Dai tuoi esempi, penso che ti manca un grande pezzo di WPF: DataTriggers. Sembra che tu stia usando i convertitori per determinare i valori condizionali, ma i convertitori dovrebbero davvero essere per convertire un tipo di dati in un altro.

Nel tuo esempio sopra

Esempio: supponiamo che tu abbia un sistema stereo che potrebbe essere in modalità radio, CD o MP3. Supponiamo che ci siano elementi visivi corrispondenti a ciascuna modalità in diverse parti dell'interfaccia utente. È possibile (1) consentire alla vista di decidere quale grafica corrisponde a quale modalità e attivarle / disattivarle di conseguenza, (2) esporre le proprietà sul modello di vista per ciascun valore della modalità (ad es. IsModeRadio, IsModeCD) o (3) esporre proprietà sul modello di vista per ciascun elemento / gruppo grafico (ad es. IsRadioLightOn, IsCDButtonGroupOn). (1) mi è sembrato naturale per me, perché ha già la consapevolezza della modalità. Cosa ne pensi in questo caso?

Vorrei usare a DataTriggerper determinare quale immagine visualizzare, non a Converter. Un convertitore serve per convertire un tipo di dati in un altro, mentre un trigger viene utilizzato per determinare alcune proprietà in base a un valore.

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

L'unica volta che prenderei in considerazione l'uso di un convertitore per questo è se il valore associato contenesse effettivamente i dati dell'immagine e avessi bisogno di convertirlo in un tipo di dati che l'interfaccia utente potesse comprendere. Ad esempio, se l'origine dati contenesse una proprietà chiamata ImageFilePath, prenderei in considerazione l'utilizzo di un convertitore per convertire la stringa contenente il percorso del file immagine in una BitmapImageche potrebbe essere utilizzata come origine per la mia immagine

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

Il risultato finale è che ho uno spazio dei nomi di libreria pieno di convertitori generici che convertono un tipo di dati in un altro e raramente devo codificare un nuovo convertitore. Ci sono occasioni in cui vorrò i convertitori per conversioni specifiche, ma sono abbastanza rare che non mi dispiace scriverle.


+1. Sollevi alcuni punti positivi. Ho usato i trigger in precedenza, ma nel mio caso non sto cambiando le fonti di immagini (che sono una proprietà), sto cambiando interi Gridelementi. Sto anche tentando di fare cose come impostare i pennelli per primo piano / sfondo / tratto in base ai dati nel mio modello di visualizzazione e ad una tavolozza di colori specifica definita nel file di configurazione. Non sono sicuro che sia perfetto per un trigger o un convertitore. L'unico problema che sto riscontrando finora con l'inserimento della maggior parte della logica di visualizzazione nel modello di visualizzazione è il cablaggio di tutte le RaisePropertyChanged()chiamate.
devuxer

@DanM In realtà farei tutte queste cose in un DataTrigger, anche cambiando gli elementi di Grid. Solitamente inserisco un punto in ContentControlcui dovrebbe trovarsi il mio contenuto dinamico e lo sostituisco con ContentTemplateun trigger. Ho un esempio al seguente link se sei interessato (scorri fino alla sezione con l'intestazione di Using a DataTrigger) rachel53461.wordpress.com/2011/05/28/…
Rachel

Ho usato modelli di dati e controlli del contenuto prima, ma non ho mai avuto bisogno di trigger perché ho sempre avuto un modello di vista unico per ogni vista. Ad ogni modo, la tua tecnica ha perfettamente senso ed è piuttosto elegante, ma è anche molto dettagliata. Con MatchToVisibility, si potrebbe abbreviare a questo: <TextBlock Text="I'm a Person" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Person}}"e<TextBlock Text="I'm a Business" Visibility={Binding ConsumerType, Converter={vc:MatchToVisibility IfTrue=Visible, IfFalse=Hidden, Value1=Business}}"
devuxer

1

Dipende da cosa stai testando , se non altro.

Nessun test: intermix Visualizza il codice con ViewModel a piacimento (puoi sempre rifattorizzare in seguito).
Test su ViewModel e / o inferiore: utilizzare i convertitori.
Test sui livelli del modello e / o inferiori: intermix Visualizza il codice con ViewModel a piacimento

ViewModel estrae il modello per la vista . Personalmente, userei ViewModel per Brushes, ecc. E salterei i convertitori. Test sui layer in cui i dati sono nella loro forma " più pura " (es. Layer del modello ).


2
Punti interessanti sui test, ma suppongo che non vedo come la logica del convertitore nel modello di visualizzazione danneggi la testabilità del modello di vista? Non sto certamente suggerendo di inserire i controlli UI effettivi nel modello di visualizzazione. Basta VIEW-specifici immobili come Visibility, SolidColorBrushe Thickness.
devuxer

@DanM: se stai usando un approccio View-first , allora nessun problema . Tuttavia, alcuni usano un approccio ViewModel-first in cui ViewModel fa riferimento a una vista, potrebbe essere problematico .
Jake Berger

Ciao Jay, sicuramente un primo approccio. La vista non conosce nulla del modello di vista tranne i nomi delle proprietà a cui deve associarsi. Grazie per il follow-up. +1.
Devuxer,

0

Questo probabilmente non risolverà tutti i problemi che hai citato, ma ci sono due punti da considerare:

Innanzitutto, è necessario posizionare il codice del convertitore da qualche parte nella prima strategia. Consideri quella parte della vista o il modello di vista? Se fa parte della vista, perché non posizionare le proprietà specifiche della vista nella vista anziché nel modello di vista?

In secondo luogo, sembra che il tuo progetto non convertitore tenti di modificare le proprietà degli oggetti reali già esistenti. Sembra che abbiano già implementato INotifyPropertyChanged, quindi perché non usare creare un oggetto wrapper specifico per la vista a cui legarsi? Ecco un semplice esempio:

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

Non intendevo implicare che sto cambiando le proprietà del mio modello di entità direttamente nella vista o nel modello di vista. Il modello di visualizzazione è sicuramente un livello diverso rispetto al livello del modello entità. In effetti, il lavoro che ho svolto finora è stato di sola lettura. Questo non vuol dire che la mia applicazione non comporterà alcuna modifica, ma non vedo alcuna conversione sui controlli utilizzati per la modifica (quindi supponiamo che tutti i binding siano unidirezionali, ad eccezione delle selezioni negli elenchi). Un buon punto su "visualizzazioni dei dati" però. Era un concetto sollevato nel post dei discepoli di XAML a cui mi riferivo all'inizio della mia domanda.
Devuxer,

0

A volte è utile utilizzare un convertitore di valore per sfruttare la virtualizzazione.

Un esempio di ciò che è accaduto in un progetto in cui dovevamo visualizzare i dati con maschera di bit per centinaia di migliaia di celle in una griglia. Quando abbiamo decodificato le maschere di bit nel modello di visualizzazione per ogni singola cella, il programma ha impiegato troppo tempo a caricarsi.

Ma quando abbiamo creato un convertitore di valore che decodificava una singola cella, il programma veniva caricato in una frazione del tempo ed era altrettanto reattivo perché il convertitore veniva chiamato solo quando l'utente stava guardando una determinata cella (e avrebbe solo bisogno di essere chiamato un massimo di trenta volte ogni volta che l'utente ha spostato la propria vista sulla griglia).

Non so come MVVM abbia lamentato questa soluzione, ma ha ridotto i tempi di caricamento del 95%.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.