2010-01-05 16 views
15

Diciamo in qualche astratto ViewModel classe base ho una proprietà plain-old come segue:Change Notification in MVVM gerarchie

public Size Size 
{ 
    get { return _size; } 
    set 
    { 
     _size = value; 
     OnPropertyChanged("Size"); 
    } 
} 

Ho quindi creare una più specifica ViewModel, che eredita dalla precedente, che contiene la seguente proprietà:

public Rect Rectangle 
{ 
    get { return new Rect(0, 0, _size.Width, _size.Height); } 
} 

Ora, in qualche View classe I legano alla proprietà il già citato del ViewModel Rectangle. Tutto funziona bene, fino a quando non cambio le dimensioni. Quando le modifiche di Size, Rectangle non lo sanno e la modifica non si propaga alla vista. E poiché Rectangle si trova nella classe figlia, non è possibile semplicemente aggiungere uno OnPropertyChanged("Rectangle") al setter Size.

Ora immagino di avere molte proprietà diverse come Rectangle, che dipendono tutte dalle proprietà di classe base e che nessuna di queste modifiche viene propagata. Ho bisogno di un modo leggero ed elegante per concatenare le notifiche di cambiamento, preferibilmente uno che non richiede molto codice e non mi obbliga a usare le proprietà di dipendenza.

Ovviamente ci sono molte brutte soluzioni qui- quello che sto cercando è qualcosa di pulito e intelligente. Mi sembra che questo sarebbe uno scenario molto comune, e mi sembra che ci possa essere un modo MVVM-friendly per farlo.

risposta

9

Recentemente ho bloggato su questo problema esatto. Includo un attributo [DependsUpon("Size")] con il rettangolo. Mi piace VERAMENTE questo approccio, perché mantiene la conoscenza delle dipendenze con il codice che crea la dipendenza, non il contrario.

Date un'occhiata: http://houseofbilz.com/archive/2009/11/14/adventures-in-mvvm----dependant-properties-with-inotifypropertychanged.aspx

+0

Ottima idea. L'ho cambiato un po 'per creare un dizionario in fase di costruzione (per motivi di prestazioni), ma il concetto è valido. – Charlie

3

Si può semplicemente eseguire l'override OnPropertyChanged nei ViewModel derivati ​​in questo modo:

protected override void OnPropertyChanged(string propertyName) { 
    base.OnPropertyChanged(propertyName); 
    if (propertyName == "Size") { 
     base.OnPropertyChanged("Rectangle"); 
    } 
} 

Un'altra possibilità ... Qualche tempo fa ho messo insieme una bella bella classe base ViewModel che supporta gli attributi su immobili come:

Quindi la classe di base ViewModel raccoglie questi DependsOnAttribute in fase di esecuzione e nel suo metodo OnPropertyChanged fondamentalmente cerca solo di vedere quali altre proprietà devono essere invalidate quando una proprietà cambia e si verifica

+0

Overriding OnPropertyChanged è sicuramente una soluzione, ma non elegante. Mi piace di più il tuo secondo suggerimento, comunque. – Charlie

+0

Non tutte le soluzioni richiedono eleganza. Sovrascrivere OnPropertyChanged è molto semplice e lo faccio ancora in molti casi, a meno che le dipendenze non diventino pelose, cosa che mi ha portato a fare il secondo approccio. – Josh

+0

Hai ragione, ma ricorda nella mia domanda che ho detto esplicitamente: "Ora immagina di avere molte proprietà diverse come Rectangle, che dipendono tutte dalle proprietà di classe base e che nessuna di queste modifiche viene propagata." Quindi l'ipotesi è che la gerarchia stia già diventando pelosa. – Charlie

1

Un modo MVVM pulito sarebbe quello di utilizzare un sottoscrivere/notificare meccanismo Messenger (come in di Josh Smith MvvmFoundation)

Creare un oggetto Messenger Singleton da qualche parte - la classe principale App è sempre un buon posto per questo

public partial class App : Application 
{ 
    private static Messenger _messenger; 
    public static Messenger Messenger 
    { 
     get 
     { 
      if (_messenger == null) 
      { 
       _messenger = new Messenger(); 
      } 
      return _messenger; 
     } 
    } 
} 

Nel setter formato dalla classe base, notificare cambiamenti:

public Size Size 
{ 
    get { return _size; } 
    set 
    { 
     _size = value; 
     OnPropertyChanged("Size"); 

     App.Messenger.NotifyColleagues("SIZE_CHANGED"); 
    } 
} 

Ora potete lasciare la vostra vista ereditata Il modello di ascolto di questi cambiamenti, e sollevare gli eventi PropertyChanged a seconda dei casi ...

public MyViewModel : MyViewModelBase 
{ 
    public MyViewModel() 
    { 
     App.Messenger.Register("SIZE_CHANGED",() => OnPropertyChanged("Rectangle")); 
    } 
} 

Naturalmente - è possibile aggiungere il numero di abbonamenti a questo messaggio di cui hai bisogno - uno per ogni proprietà che ha bisogno di modifiche da notificare di nuovo a la vista...

Spero che questo aiuti :)

+0

Questo fa esattamente la stessa cosa che sostituisce OnPropertyChanged per il figlio nel genitore, non è sicuro che lo renda più pulito – AwkwardCoder

+0

Questa non è affatto una cattiva idea, ma voglio qualcosa di un po 'più leggero. – Charlie

+0

Il vantaggio dell'utilizzo di un approccio di Messenger può essere nei casi in cui si hanno gerarchie profondamente nidificate in cui sarebbe scomodo e forse inefficiente per concatenare gli eventi di modifica delle proprietà. – jpierson

0

forse perché im un ragazzo VB, ma nel codice rettangolo sembra che si accede alla dichiarazione _size privata anziché la proprietà Size pubblica che non generare l'evento OnPropertyChanged per avvisare la vista.

Anche io potrei essere fuori base, ma il rettangolo non dovrebbe essere un oggetto reale mentre la dimensione è una proprietà di tale oggetto? Forse è quello che stai facendo ... alcune metodologie C# sono ancora molto estranee a me.

+0

Sto usando la dichiarazione _size privata ma è irrilevante. Quello è un getter, non un setter. La vista deve essere notificata che Rectangle sta cambiando quando Size cambia, non quando si accede a Rectangle. – Charlie

4

Io uso PropertyObserver di Josh Smith, che si può ottenere dalla sua biblioteca MVVM Fondazione a http://mvvmfoundation.codeplex.com/.

Usage:

_viewmodel_observer = new PropertyObserver<OtherViewModel>(_OtherViewModel) 
    .RegisterHandler(m => m.Size, m => RaisePropertyChanged(Rectangle); 

di Brian approccio attributo è troppo bello. Una cosa che mi piace di PropertyObserver è che posso eseguire codice arbitrario; permettendomi di verificare le condizioni che potrebbero farmi evitare il rilancio o compiere altre azioni tutte insieme.