2010-11-04 6 views
6

Realmente estoy rascándome la cabeza con este. Tengo una ventana principal que abre un diálogo. Una vez que se cierra el cuadro de diálogo, el método CanExecute en los comandos vinculados en el cuadro de diálogo todavía se está ejecutando. Esto está causando algunos problemas serios en mi aplicación.¿Cuándo se separa el ui de los comandos?

Ejemplo:

MainWindow tiene un botón con un controlador de clic. Este es el controlador de eventos Click:

private void Button_Click(object sender, RoutedEventArgs e) 
    { 
     DialogWindow window = new DialogWindow(); 
     window.ShowDialog(); 
    } 

En el diálogo que se unen a un artículo en el control a un recurso estático en la ventana de diálogo, y cada elemento de la lista tiene un comando:

<Window.Resources> 

    <Collections:ArrayList x:Key="itemsSource"> 
     <local:ItemViewModel Description="A"></local:ItemViewModel> 
     <local:ItemViewModel Description="B"></local:ItemViewModel> 
     <local:ItemViewModel Description="C"></local:ItemViewModel> 
    </Collections:ArrayList> 

    <DataTemplate DataType="{x:Type local:ItemViewModel}"> 
      <Button Grid.Column="1" Command="{Binding Path=CommandClickMe}" Content="{Binding Path=Description}" Style="{StaticResource {x:Static ToolBar.ButtonStyleKey}}"> 
      </Button> 
    </DataTemplate> 

</Window.Resources> 

<Grid> 
    <ToolBar ItemsSource="{StaticResource itemsSource}"></ToolBar> 
</Grid> 

Este es el modelo de vista:

public class ItemViewModel 
{ 
    private RelayWpfCommand<object> _commandClickMe; 

    public RelayWpfCommand<object> CommandClickMe 
    { 
     get 
     { 
      if (_commandClickMe == null) 
       _commandClickMe = new RelayWpfCommand<object>(obj => System.Console.Out.WriteLine("Hei mom"), obj => CanClickMe()); 

      return _commandClickMe; 
     } 
    } 

    private bool CanClickMe() 
    { 
     return true; 
    } 

    public string Description { get; set; } 

Y esta es la aplicación DelegateCommand:

public class RelayWpfCommand<T> : ICommand 
{ 
    public event EventHandler CanExecuteChanged 
    { 
     add { CommandManager.RequerySuggested += value; } 
     remove { CommandManager.RequerySuggested -= value; } 
    } 

    private readonly Predicate<T> _canExecute; 
    private readonly Action<T> _execute; 

    public RelayWpfCommand(Action<T> execute, Predicate<T> canExecute) 
    { 
     _execute = execute; 
     _canExecute = canExecute; 
    } 

    /// <summary> 
    /// Forces a notification that the CanExecute state has changed 
    /// </summary> 
    public void RaiseCanExecuteChanged() 
    { 
     CommandManager.InvalidateRequerySuggested(); 
    } 

    public bool CanExecute(T parameter) 
    { 
     return _canExecute(parameter); 
    } 

    public void Execute(T parameter) 
    { 
     _execute(parameter); 
    } 

    bool ICommand.CanExecute(object parameter) 
    { 
     if (!IsParameterValidType(parameter)) 
      return false; 

     return CanExecute((T)parameter); 
    } 

    void ICommand.Execute(object parameter) 
    { 
     if (!IsParameterValidType(parameter)) 
      throw new ArgumentException(string.Format("Parameter must be of type {0}", typeof(T))); 

     Execute((T)parameter); 
    } 

    private static bool IsParameterValidType(object parameter) 
    { 
     if (parameter != null && !typeof(T).IsAssignableFrom(parameter.GetType())) 
      return false; 

     return true; 
    } 
} 

Ahora, si cierro la ventana de diálogo y establezco un punto de interrupción en el método CanExecute (estoy usando Prism DelegateCommand con suscripción de evento débil) en el modelo de vista, noto que se dispara aunque el diálogo se ha cerrado. ¿Por qué en la tierra sigue vivo el enlace entre el botón del cuadro de diálogo y el comando del ViewModel?

Y estoy comprobando si se está ejecutando al cerrar la ventana y más tarde establecer un punto de interrupción en el método "CanClickMe" en el modelo de vista. Se ejecutará por un tiempo, luego se detendrá repentinamente (probablemente debido a GC). Este comportamiento no determinista está causando problemas porque en la aplicación real el modelo de vista podría estar ya dispuesto.

+0

¿En qué punto está viendo esto? La instancia de la ventana seguirá en el alcance una vez que se cierre, hasta que salga del evento Click. Esto es para permitir que la persona que llama tenga acceso a las propiedades en la ventana (piense, por ejemplo, en una ventana de Opciones). Además, ¿qué hay en CanExecute está causando un problema? ¿El problema podría ser que estás creando efectos secundarios en CanExecute? –

+0

Ver mis comentarios – Marius

Respuesta

0

He visto esta captura muchas veces en diferentes proyectos, no estoy seguro de si este error espeluznante acecha en su aplicación también, pero vale la pena comprobarlo.

Hay una known memory leak issue en WPF 3.5 (incluido SP1), que básicamente se puede encontrar si va a enlazar a algo que no es un DependencyProperty o no implementa INotifyPropertyChanged. Y esto es exactamente de lo que trata tu código.

Simplemente implemente INotifyPropertyChanged en ItemViewModel y vea cómo funciona. Espero que esto ayude.

+1

Implementé INotifyPropertyChanged en el ItemViewModel, pero esto no ayudó. – Marius

0

Puede borrar la colección CommandBindings de su ventana, cuando se cierre.

0

en lugar de tener su comando como una propiedad, ¿podría intentar lo siguiente:

public ICommand CommandClickMe 
{ 
    get 
    { 
     return new RelayWpfCommand<object>((obj)=>System.Console.Out.WriteLine("Hei mom"), obj => CanClickMe()); 
    } 
} 
1

Usted puede utilizar la Carta de WeakEvent para mitigar este problema. Consulte la siguiente pregunta de Stackoverflow: Is Josh Smith's implementation of the RelayCommand flawed?

+0

No hay nada defectuoso en la implementación, CommandManager implementa correctamente el patrón de evento débil (que también es la respuesta aceptada en la pregunta a la que hace referencia en los estados) – Marius

Cuestiones relacionadas