2010-02-17 15 views
38

Considerare il riferimento Josh Smith' article WPF Apps With The Model-View-ViewModel Design Pattern, in particolare l'implementazione di esempio di RelayCommand (nella figura 3). (Non c'è bisogno di leggere l'intero articolo per questa domanda.)L'implementazione di RelayCommand di Josh Smith è difettosa?

In generale, penso che l'implementazione è eccellente, ma ho una domanda circa la delegazione di CanExecuteChanged abbonamenti a RequerySuggested evento s' il CommandManager. I documentation for RequerySuggested afferma:

Poiché questo evento è statico, sarà contenere solo sul gestore come un debole riferimento. Gli oggetti che ascoltano questo evento dovrebbero mantenere un forte riferimento al loro gestore di eventi su , evitando che venga sottoposto a garbage collection. Questo può essere eseguito con un campo privato e assegnando il gestore come valore precedente o successivo a associato a questo evento.

Eppure l'attuazione campione di RelayCommand non mantiene tale al gestore sottoscritto:

public event EventHandler CanExecuteChanged 
{ 
    add { CommandManager.RequerySuggested += value; } 
    remove { CommandManager.RequerySuggested -= value; } 
} 
  1. Questo fuoriuscire il riferimento debole fino al cliente 's il RelayCommand, richiedendo che l'utente di il RelayCommand comprende l'implementazione di CanExecuteChanged e mantiene un riferimento dal vivo?
  2. Se è così, ha senso per, ad esempio, modificare l'implementazione di RelayCommand essere qualcosa di simile al seguente per mitigare il potenziale GC prematura del CanExecuteChanged abbonato:

    // This event never actually fires. It's purely lifetime mgm't. 
    private event EventHandler canExecChangedRef; 
    public event EventHandler CanExecuteChanged 
    { 
        add 
        { 
         CommandManager.RequerySuggested += value; 
         this.canExecChangedRef += value; 
        } 
        remove 
        { 
         this.canExecChangedRef -= value; 
         CommandManager.RequerySuggested -= value; 
        } 
    } 
    
+0

Ottima domanda ... Mi chiedo anche che htf sia RequerySuggested sapendo quando è necessaria una richiesta .. –

+1

RequerySuggested è documentato per attivare quando è necessario aggiornare lo stato CanExecute. Credo che possa anche essere attivato manualmente dai client se il client sa qualcosa che WPF stesso non conosce. –

+0

@Greg - E in base a quali parametri RequerySuggested sa se lo stato CanExecute potrebbe dover essere aggiornato? :) – VitalyB

risposta

7

Credo anche che questa implementazione sia difettosa, perché perde definitivamente il riferimento debole al gestore eventi. Questo è davvero qualcosa di molto brutto.
Sto utilizzando il toolkit MVVM Light e lo RelayCommand implementato al suo interno e viene implementato esattamente come nell'articolo.
Il seguente codice non potrà mai invocare OnCanExecuteEditChanged:

private static void OnCommandEditChanged(DependencyObject d, 
             DependencyPropertyChangedEventArgs e) 
{ 
    var @this = d as MyViewBase; 
    if (@this == null) 
    { 
     return; 
    } 

    var oldCommand = e.OldValue as ICommand; 
    if (oldCommand != null) 
    { 
     oldCommand.CanExecuteChanged -= @this.OnCanExecuteEditChanged; 
    } 
    var newCommand = e.NewValue as ICommand; 
    if (newCommand != null) 
    { 
     newCommand.CanExecuteChanged += @this.OnCanExecuteEditChanged; 
    } 
} 

Tuttavia, se cambio in questo modo, esso funzionerà:

private static EventHandler _eventHandler; 

private static void OnCommandEditChanged(DependencyObject d, 
             DependencyPropertyChangedEventArgs e) 
{ 
    var @this = d as MyViewBase; 
    if (@this == null) 
    { 
     return; 
    } 
    if (_eventHandler == null) 
     _eventHandler = new EventHandler(@this.OnCanExecuteEditChanged); 

    var oldCommand = e.OldValue as ICommand; 
    if (oldCommand != null) 
    { 
     oldCommand.CanExecuteChanged -= _eventHandler; 
    } 
    var newCommand = e.NewValue as ICommand; 
    if (newCommand != null) 
    { 
     newCommand.CanExecuteChanged += _eventHandler; 
    } 
} 

L'unica differenza? Come indicato nella documentazione di CommandManager.RequerySuggested sto salvando il gestore di eventi in un campo.

0

io possa essere manca il punto qui, ma il seguente non costituisce il forte riferimento al gestore di eventi nel contructor?

_canExecute = canExecute;   
+3

'CanExecuteChanged' non è' canExecute'. –

+1

No. '_canExecute' è il delegato che valuta se il comando può essere eseguito. 'CanExecuteChanged' è un evento che si verifica quando' _canExecute' è rivalutato.Non esiste alcuna relazione tra '_canExecute' e gli handler iscritti all'evento' CanExecuteChanged' –

+0

Ovviamente. La mia risposta era equivalente alla Red Bull pre-oggi! RouteCommand è effettivamente l'oggetto che ascolta l'evento? La mia comprensione qui è limitata ma quello che sto vedendo è un oggetto che sta per creare un listener per l'evento CanExecuteChanged e quindi nell'esempio dato eseguirà _saveCommand.CanExecuteChanged + = myHandler. Al mio cervello (molto) addizionato di caffeina, sembrerebbe che la responsabilità per il forte riferimento risieda in SaveCommand, non in RelayCommand in quanto RelayCommand non fornisce l'ascoltatore per CanExecuteChanged. – Lazarus

7

Ebbene, secondo e riflettore è implementato allo stesso modo nella classe RoutedCommand, quindi credo che deve essere OK ... a meno che qualcuno nella squadra WPF ha fatto un errore;)

+0

Ammetto che è improbabile, soprattutto considerando che WPF è, io sospetto, il consumatore comune dell'evento CanExecuteChanged. Sto cercando di seguire i documenti come scritto, quindi, mentre il comportamento effettivo potrebbe funzionare, non posso fare a meno di sospettare che dipendiamo da un dettaglio di implementazione. –

5

credo che è difettoso.

reindirizzando gli eventi al CommandManager, non ottiene il seguente comportamento

Ciò assicura che il WPF comandando infrastruttura chiede tutti RelayCommand oggetti se possono eseguire ogni volta chiede i comandi incorporati .

Tuttavia, cosa succede quando si desidera informare tutti i controlli associati a un singolo comando per rivalutare lo stato di CanExecute?Nella sua realizzazione, si deve andare al CommandManager, che significa

Ogni singolo comando vincolante nell'applicazione si rivaluta

che include tutti quelli che non contano una collina di fagioli, quelli in cui la valutazione CanExecute ha effetti collaterali (come l'accesso al database o attività di lunga durata), quelli che attendono di essere raccolti ... È come usare un martello per guidare un chiodo friggen.

È necessario prendere in seria considerazione le conseguenze di ciò.

+0

In linea di principio sono d'accordo con te. D'altra parte, CanExecute non dovrebbe fare nulla di pesante in ogni caso, dal momento che può essere chiamato abbastanza frequentemente da WPF –

+0

@ Isak sì. Certo, non è sempre la realtà. E in quei casi reindirizzare ciecamente l'evento è una cattiva scelta. – Will

40

ho trovato la risposta a Josh di comment sulla sua "Understanding Routed Commands" articolo:

[...] è necessario utilizzare il modello WeakEvent nel vostro evento CanExecuteChanged . Ciò è dovuto al fatto che gli elementi visuali collegheranno tale evento e dal momento che l'oggetto comando potrebbe non essere mai raccolto automaticamente finché l'app non viene chiusa, esiste un potenziale molto reale per una perdita di memoria. [...]

L'argomento sembra essere che CanExecuteChanged implementatori devono contenere solo debolmente ai gestori registrati, dal momento che WPF Visuals devono stupido per sganciare se stessi. Questo è più facilmente implementato delegando allo CommandManager, che già lo fa. Presumibilmente per lo stesso motivo.

+0

Vorrei poterti dare più di un voto. :) –

+0

prego! –

Problemi correlati