2010-08-02 10 views
5

Ho un'app WPF che contiene semplicemente un pulsante e una casella di testo per visualizzare alcuni output. Quando l'utente fa clic sul pulsante, viene avviato un thread che disabilita il pulsante, stampa elementi nella casella di testo di output, quindi il thread si arresta (a quel punto voglio che il pulsante sia di nuovo abilitato).WPF: problemi di controllo dello stato abilitato/disabilitato di un pulsante utilizzando Command binding e una discussione

L'applicazione sembra disabilitare correttamente il pulsante, nonché aggiornare correttamente la casella di testo. Tuttavia, non riesce sempre a riattivare correttamente il pulsante quando il thread è completato! Qualcuno può dirmi cosa sto facendo di sbagliato?

Ecco un frammento del mio XAML:

<Grid> 
    <Grid.RowDefinitions> 
     <RowDefinition Height="Auto"/> 
     <RowDefinition Height="Auto"/> 
     <RowDefinition Height="*"/> 
    </Grid.RowDefinitions> 
    <Button Grid.Row="0" HorizontalAlignment="Center" Command="{Binding ExecuteCommand}">E_xecute</Button> 
    <Label Grid.Row="1" Content="Output Window:" HorizontalAlignment="Left"/> 
    <TextBlock Grid.Row="2" Text="{Binding Output}"/> 
</Grid> 

Ecco il mio codice ViewModel (sto usando il disegno MVVM di Josh Smith):

public class WindowViewModel : ViewModelBase 
{ 
    private bool _threadStopped; 
    private RelayCommand _executeCommand; 
    private string _output; 

    public WindowViewModel() 
    { 
     _threadStopped = true; 
    } 

    public string Output { get { return _output; } set { _output = value; OnPropertyChanged("Output"); } } 

    public ICommand ExecuteCommand 
    { 
     get 
     { 
      if (_executeCommand == null) 
      { 
       _executeCommand = new RelayCommand(p => this.ExecuteThread(p), p => this.CanExecuteThread); 
      } 
      return _executeCommand; 
     } 
    } 

    public bool CanExecuteThread 
    { 
     get 
     { 
      return _threadStopped; 
     } 
     set 
     { 
      _threadStopped = value; 
     } 
    } 

    private void ExecuteThread(object p) 
    { 
     ThreadStart ts = new ThreadStart(ThreadMethod); 
     Thread t = new Thread(ts); 
     t.Start(); 
    } 

    private void ThreadMethod() 
    { 
     CanExecuteThread = false; 
     Output = string.Empty; 
     Output += "Thread Started: Is the 'Execute' button disabled?\r\n"; 
     int countdown = 5000; 

     while (countdown > 0) 
     { 
      Output += string.Format("Time remaining: {0}...\r\n", countdown/1000); 
      countdown -= 1000; 
      Thread.Sleep(1000); 
     } 
     CanExecuteThread = true; 
     Output += "Thread Stopped: Is the 'Execute' button enabled?\r\n"; 
    } 
} 

risposta

1

Avrai bisogno di aiutare WPF sapere che il stato eseguibile del comando è cambiato. Il modo più semplice per farlo è di:

CommandManager.InvalidateRequerySuggested() 

all'interno CanExecuteThread:

set 
{ 
    _threadStopped = value; 
    CommandManager.InvalidateRequerySuggested() 
} 

EDIT (ora che ho tempo): il problema reale è probabile che non si è notifica quando la CanExecuteThread modifiche alle proprietà . Dovrebbe sollevare PropertyChanged in ordine per WPF per rilevare il cambiamento:

public bool CanExecuteThread 
{ 
    get { return _threadStopped; } 
    set 
    { 
     if (_threadStopped != value) 
     { 
      _threadStopped = value; 
      this.OnPropertyChanged(() => this.CanExecuteThread); 
     } 
    } 
} 

assume il sopra la classe di base ViewModel ha un metodo OnPropertyChanged.

Detto questo, volevo anche sottolineare che si potrebbe semplificare le cose, semplicemente utilizzando un BackgroundWorker:

public class WindowViewModel : ViewModel 
{ 
    private readonly BackgroundWorker backgroundWorker; 

    public WindowVieWModel() 
    { 
     backgroundWorker = new BackgroundWorker(); 
     backgroundWorker.DoWork += delegate 
     { 
      // do work here (what's currently in ThreadMethod) 
     }; 
     backgroundWorker.RunWorkerCompleted += delegate 
     { 
      // this will all run on the UI thread after the work is done 
      this.OnPropertyChanged(() => this.CanExecuteThread); 
     }; 
    } 

    ... 

    public bool CanExecuteThread 
    { 
     get { !this.backgroundWorker.IsBusy; } 
    } 

    private void ExecuteThread(object p) 
    { 
     // this will kick off the work 
     this.backgroundWorker.RunWorkerAsync(); 

     // this property will have changed because the worker is busy 
     this.OnPropertyChanged(() => this.CanExecuteThread); 
    } 
} 

Si potrebbe refactoring questo oltre ad essere ancora più bello, ma si ottiene l'idea.

+0

Ho inserito la riga di codice come suggerito, ma il pulsante appare ancora disattivato al termine del thread. È solo quando mi concentro su qualcosa all'interno della Finestra (ad esempio un clic del mouse o un tasto di scelta rapida) il pulsante diventa nuovamente abilitato. (NOTA: Tuttavia, stavo vedendo questo anche prima della tua soluzione suggerita). Qualsiasi altro suggerimento sarebbe molto apprezzato. –

+0

Prova a farlo sul thread dell'interfaccia utente tramite Dispatcher.Invoke chiama –

+0

Grazie, Kent! L'ha fatto! Pubblicherò la risposta per gli altri per vedere la soluzione. –

0

Ecco la risposta, come suggerito da Kent Boogaart, e funziona. Fondamentalmente, ho dovuto chiamare CommandManager.InvalidateRequerySuggested sul thread dell'interfaccia utente posizionandolo all'interno di una chiamata di chiamata Dispatcher. Si noti inoltre che sono riuscito a sbarazzarsi dell'accessorio Set sulla proprietà CanExecuteThread, poiché non era più necessario con questa soluzione. Grazie, Kent!

public class WindowViewModel : ViewModelBase 
{ 
    private bool _threadStopped; 
    private RelayCommand _executeCommand; 
    private string _output; 
    private Dispatcher _currentDispatcher; 
    public WindowViewModel() 
    { 
     _threadStopped = true; 
     _currentDispatcher = Dispatcher.CurrentDispatcher; 
    } 

    public string Output { get { return _output; } set { _output = value; OnPropertyChanged("Output"); } } 

    public ICommand ExecuteCommand 
    { 
     get 
     { 
      if (_executeCommand == null) 
      { 
       _executeCommand = new RelayCommand(p => this.ExecuteThread(p), p => this.CanExecuteThread); 
      } 
      return _executeCommand; 
     } 
    } 

    private delegate void ZeroArgDelegate(); 

    public bool CanExecuteThread 
    { 
     get 
     { 
      return _threadStopped; 
     } 
    } 

    private void ExecuteThread(object p) 
    { 
     ThreadStart ts = new ThreadStart(ThreadMethod); 
     Thread t = new Thread(ts); 
     t.Start(); 
    } 

    private void ThreadMethod() 
    { 
     _threadStopped = false; 
     Output = string.Empty; 
     Output += "Thread Started: Is the 'Execute' button disabled?\r\n"; 
     int countdown = 5000; 

     while (countdown > 0) 
     { 
      Output += string.Format("Time remaining: {0}...\r\n", countdown/1000); 
      countdown -= 1000; 
      Thread.Sleep(1000); 
     } 
     _threadStopped = true; 
     _currentDispatcher.BeginInvoke(new ZeroArgDelegate(resetButtonState), null); 
     Output += "Thread Stopped: Is the 'Execute' button enabled?\r\n"; 
    } 

    private void resetButtonState() 
    { 
     CommandManager.InvalidateRequerySuggested(); 
    } 
} 
Problemi correlati