2010-02-17 6 views
38

Considere la referencia Josh Smith' article WPF Apps With The Model-View-ViewModel Design Pattern, específicamente la implementación de ejemplo de RelayCommand (en la figura 3). (No es necesario leer todo el artículo para esta pregunta.)¿La implementación de Josh Smith del RelayCommand está defectuosa?

En general, creo que la aplicación es excelente, pero tengo una pregunta sobre la delegación de CanExecuteChanged suscripciones a RequerySuggested caso de que el CommandManager 's. Los documentation for RequerySuggested estados:

Ya que este evento es estático, que se sólo se sostienen sobre el manejador como una débil referencia . Los objetos que escuchan este evento deben mantener una fuerte referencia a su controlador de eventos al para evitar que sea basura. Este se puede lograr teniendo un campo privado y asignando el controlador como el valor anterior o posterior al asociado a este evento.

Sin embargo, la implementación de ejemplo de RelayCommand no mantiene dicha al controlador suscrito:

public event EventHandler CanExecuteChanged 
{ 
    add { CommandManager.RequerySuggested += value; } 
    remove { CommandManager.RequerySuggested -= value; } 
} 
  1. ¿Este fugas de la referencia débil hasta el cliente del RelayCommand 's, lo que requiere que el usuario de el RelayCommand entiende la implementación de CanExecuteChanged y mantiene una referencia en vivo por sí mismos?
  2. Si es así, ¿tiene sentido que, por ejemplo, modificar la ejecución de RelayCommand a ser algo así como lo siguiente para mitigar el potencial GC prematuro de la CanExecuteChanged suscriptor:

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

Gran pregunta ... También me pregunto si htf es RequerySuggested saber cuándo es necesaria una nueva consulta. –

+1

RequerySuggested se activa cuando es posible que se deba actualizar el estado CanExecute. Creo que los clientes también pueden despedirlo manualmente si el cliente sabe algo que WPF no sabe. –

+0

@Greg - ¿Y con qué parámetros sabe RequerySuggested si puede ser necesario actualizar el estado de CanExecute? :) – VitalyB

Respuesta

7

Yo también creo que esta implementación está defectuosa, porque definitivamente se filtra la referencia débil al controlador de eventos. Esto es algo realmente muy malo.
Estoy utilizando el kit de herramientas MVVM Light y el RelayCommand implementado en el mismo y se implementa como en el artículo.
El siguiente código nunca invocará 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; 
    } 
} 

Sin embargo, si la cambio de este tipo, que funcionará:

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

La única diferencia? Tal como se indica en la documentación de CommandManager.RequerySuggested, guardo el controlador de eventos en un campo.

0

puedo estar Perdiendo el punto aquí, ¿no es la siguiente la fuerte referencia al controlador de eventos en el contructor?

_canExecute = canExecute;   
+3

'CanExecuteChanged' no es' canExecute'. –

+1

No. '_canExecute' es el delegado que evalúa si el comando se puede ejecutar. 'CanExecuteChanged' es un evento que ocurre cuando' _canExecute' se reevalúa.No existe relación entre '_canExecute' y los manejadores suscritos al evento' CanExecuteChanged' –

+0

Por supuesto. ¡Mi respuesta fue el equivalente a Red Bull de hoy! ¿RouteCommand es realmente el objeto que escucha el evento? Mi comprensión aquí es limitada, pero lo que estoy viendo es un objeto que va a crear un oyente para el evento CanExecuteChanged y luego, en el ejemplo dado, ejecutará _saveCommand.CanExecuteChanged + = myHandler. A mi cerebro (muy) con cafeína, parecería que la responsabilidad de la fuerte referencia recae en SaveCommand, no en RelayCommand, ya que RelayCommand no proporciona al oyente CanExecuteChanged. – Lazarus

7

Bueno, de acuerdo con el reflector se implementa de la misma manera en la clase RoutedCommand, así que supongo que debe estar bien ... a menos que alguien en el equipo de WPF cometió un error;)

+0

Admito que es poco probable, especialmente dado que WPF es, sospecho, el consumidor común del evento CanExecuteChanged. Sin embargo, estoy tratando de seguir los documentos tal como están escritos, así que aunque el comportamiento real pueda funcionar, no puedo evitar sospechar que dependemos de un detalle de implementación. –

5

creo que Es defectuoso.

mediante la reconducción de los acontecimientos que el Administrador de comandos, lo hace llegar el siguiente comportamiento

Esto asegura que el WPF mando infraestructura pide a todos los objetos RelayCommand si pueden ejecutarse siempre que se pide a los comandos internos .

Sin embargo, ¿qué sucede cuando desea informar a todos los controles vinculados a un solo comando para volver a evaluar el estado de CanExecute?En su aplicación, debe ir al Administrador de comandos, lo que significa

Cada unión en la aplicación de comandos se vuelve a evaluar

que incluye todos los que no importan una colina de frijoles, aquellos en los que la evaluación CanExecute tiene efectos secundarios (como el acceso a la base de datos o tareas de larga ejecución), los que están esperando ser recogidos ... Es como usar un mazo para manejar un clavo de friggen.

Tienes que considerar seriamente las ramificaciones de hacer esto.

+0

En principio estoy de acuerdo con usted. Pero, por otro lado, CanExecute no debería hacer nada pesado, ya que puede ser llamado con bastante frecuencia por WPF –

+0

@Isak yep. Por supuesto, eso no siempre es realidad. Y en esos casos, redirigir ciegamente el evento es una mala elección. – Will

40

he encontrado la respuesta en Josh comment en su artículo "Understanding Routed Commands":

[...] usted tiene que utilizar el patrón WeakEvent en su caso CanExecuteChanged . Esto se debe a que los elementos visuales conectarán ese evento y, dado que el objeto de comando nunca se recolectará como basura hasta que se cierre la aplicación , existe un potencial muy real para una pérdida de memoria. [...]

El argumento parece ser que CanExecuteChanged ejecutores sólo deben mantener débilmente a los gestores registrados, ya que WPF Visuals son estúpidos para desenganchar a sí mismos. Esto se implementa más fácilmente delegando al CommandManager, que ya hace esto. Presumiblemente por la misma razón.

+0

Ojalá pudiera darle más de un voto positivo. :) –

+0

¡De nada! –

Cuestiones relacionadas