2010-11-04 9 views
6

Mi sto davvero grattando la testa con questo. Ho una finestra principale che apre una finestra di dialogo. Dopo la chiusura della finestra di dialogo, il metodo CanExecute sui comandi associati nella finestra di dialogo è ancora in esecuzione. Ciò sta causando alcuni seri problemi nella mia applicazione.Quando si separa l'ui dai comandi?

Esempio:

MainWindow ha un pulsante con un gestore di clic. Questo è il gestore di eventi click:

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

Nella finestra mi legano un elementi di controllo a una risorsa statica nella finestra di dialogo, e ogni elemento della lista ha 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> 

Si tratta di ViewModel:

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

E questo è l'implementazione 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; 
    } 
} 

Ora, se chiudo la finestra di dialogo e imposto un punto di interruzione nel metodo CanExecute (sto usando Prism DelegateCommand con iscrizione evento debole) sul viewmodel, noto che si attiva anche se la finestra di dialogo è stata chiusa. Perché mai il collegamento tra il pulsante nella finestra di dialogo e il comando sul ViewModel è ancora attivo?

E sto verificando se viene eseguito chiudendo la finestra e in un secondo momento impostando un punto di interruzione nel metodo "CanClickMe" nel viewmodel. Verrà eseguito per un po ', quindi improvvisamente si fermerà (probabilmente a causa di GC). Questo comportamento non deterministico sta causando problemi perché nell'applicazione reale il viewmodel potrebbe già essere disposto.

+0

A che punto vedi questo? L'istanza della finestra sarà ancora inclusa dopo la chiusura, finché non si abbandona l'evento Click. Questo per consentire al chiamante di accedere alle proprietà nella finestra (si pensi, ad esempio, a una finestra Opzioni). Inoltre, cosa c'è nel CanExecute sta causando un problema? Il problema potrebbe essere in realtà che stai creando effetti collaterali in CanExecute? –

+0

Vedere i miei commenti – Marius

risposta

0

Ho visto questa cattura molte volte in diversi progetti, non sono sicuro se questo insetto raccapricciante si annida anche nella tua app, ma vale la pena controllare.

C'è una known memory leak issue in WPF 3.5 (compreso SP1), in pratica si possono incontrare se si sono vincolanti a qualcosa che non è un DependencyProperty o non implementa INotifyPropertyChanged. E questo è esattamente ciò che il tuo codice tratta.

Basta implementare INotifyPropertyChanged su ItemViewModel e vedere come va. Spero che questo ti aiuti.

+1

Ho implementato INotifyPropertyChanged su ItemViewModel, ma questo non ha aiutato. – Marius

0

È possibile cancellare la raccolta CommandBindings della finestra, quando si chiude.

0

piuttosto che avere il comando come una proprietà, si potrebbe provare la seguente:

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

È possibile utilizzare il modello WeakEvent per attenuare questo problema. Per favore fai riferimento alla seguente domanda StackOverflow: Is Josh Smith's implementation of the RelayCommand flawed?

+0

Non c'è nulla di sbagliato nell'implementazione, il CommandManager impianta correttamente il modello di evento debole (che è anche la risposta accettata nella domanda a cui si riferisce gli stati) – Marius

Problemi correlati