2010-08-02 8 views
5

Tengo una aplicación WPF que simplemente contiene un botón y un cuadro de texto para mostrar algunos resultados. Cuando el usuario hace clic en el botón, se inicia un hilo que desactiva el botón, imprime cosas en el cuadro de texto de salida, luego el hilo se detiene (en este punto quiero que el botón se habilite de nuevo).WPF: Problemas para controlar el estado de un botón habilitado/deshabilitado mediante el enlace de comandos y un hilo

La aplicación parece desactivar el botón correctamente, así como actualizar el cuadro de texto correctamente. Sin embargo, siempre falla al volver a habilitar el botón correctamente cuando se completa el hilo. ¿Alguien puede decirme lo que estoy haciendo mal?

He aquí un fragmento de mi 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> 

Aquí está mi código de modelo de vista (estoy usando el diseño MVVM de 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"; 
    } 
} 

Respuesta

1

Tendrá que ayudar a WPF saben que la el estado ejecutable del comando ha cambiado. La forma más sencilla de hacer esto es:

CommandManager.InvalidateRequerySuggested() 

dentro CanExecuteThread:

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

EDITAR (ahora que tengo tiempo): el problema real es probable que usted no esté notificando cuando los cambios en las propiedades CanExecuteThread . Esto debe despertar PropertyChanged con el fin de WPF para detectar el cambio:

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

Lo anterior supone que su clase base ViewModel tiene un método OnPropertyChanged.

Dicho esto, también quería señalar que se podría simplificar las cosas por el simple uso de 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); 
    } 
} 

Se podría refactorizar esto más a ser aún más agradable, pero usted consigue la idea.

+0

He colocado la línea de código como sugirió, pero el botón aún aparece deshabilitado cuando termina la secuencia. Solo cuando le doy foco a algo dentro de la ventana (es decir, un clic del mouse o una tecla de atajo), el botón se vuelve a habilitar. (NOTA: Sin embargo, estaba viendo esto incluso antes de su solución sugerida). Cualquier otra sugerencia sería muy apreciada. –

+0

Trate de hacerlo en el hilo de la interfaz de usuario a través de un Dispatcher.Invocar llamada –

+0

¡Gracias, Kent! Eso lo hizo! Voy a publicar la respuesta para que otros vean la solución. –

0

Aquí está la respuesta, como lo sugirió Kent Boogaart, y funciona. Básicamente, tuve que llamar a CommandManager.InvalidateRequerySuggested en el hilo de UI colocándolo dentro de una llamada de invocación de Dispatcher. También tenga en cuenta que pude deshacerme del acceso de conjunto en la propiedad CanExecuteThread, ya que ya no era necesario con esta solución. Gracias, 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(); 
    } 
} 
Cuestiones relacionadas