2010-08-02 2 views
5

У меня есть приложение WPF, которое просто содержит кнопку и текстовое поле для отображения некоторого вывода. Когда пользователь нажимает кнопку, запускает поток, который отключает Button, печатает материал в текстовом поле вывода, тогда поток останавливается (в какой момент я хочу, чтобы кнопка была включена снова).WPF: проблема с управлением включенным/отключенным состоянием кнопки с использованием привязки команд и потока

Приложение, как представляется, правильно отключает кнопку, а также правильно обновляет текстовое поле. Тем не менее, он всегда не позволяет повторно включить кнопку правильно, когда поток завершается! Может ли кто-нибудь сказать мне, что я делаю неправильно?

Вот отрывок из моего 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> 

Вот мой ViewModel код (я использую дизайн MVVM Джоша Смита):

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

ответ

1

Вам нужно помочь WPF знать, что исполняемое состояние команды изменилось. Простой способ сделать это:

CommandManager.InvalidateRequerySuggested() 

внутри CanExecuteThread:

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

EDIT (теперь, когда у меня есть время): актуальная проблема, вероятно, что вы не уведомляя, когда CanExecuteThread изменения свойств , Он должен поднять PropertyChanged для того, чтобы WPF обнаружить изменение:

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

выше предполагает, что ваша ViewModel базового класса имеет OnPropertyChanged метод.

То есть, я также хотел бы отметить, что вы могли бы упростить вещи, просто используя 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); 
    } 
} 

Вы могли бы реорганизовать это дальше будет еще лучше, но вы получите идею.

+0

Я поместил строку кода, как вы предлагали, но кнопка по-прежнему отключена, когда поток завершен. Только когда я сосредотачиваюсь на чем-то в окне (т. Е. Щелчке мыши или клавише быстрого доступа), кнопка снова активируется. (ПРИМЕЧАНИЕ. Однако я видел это еще до вашего предлагаемого решения). Любые другие предложения будут высоко оценены. –

+0

Попробуйте сделать это в потоке пользовательского интерфейса через Dispatcher.Invoke call –

+0

Спасибо, Кент! Вот так! Я отправлю ответ другим, чтобы увидеть решение. –

0

Вот ответ, предложенный Кентом Боогаартом, и он работает. В принципе, мне пришлось вызвать CommandManager.InvalidateRequerySposed в потоке пользовательского интерфейса, поместив его в вызов Dispatcher invoke. Также обратите внимание, что я смог избавиться от Access Accessor в свойстве CanExecuteThread, поскольку это больше не требовалось с этим решением. Благодарю, Кент!

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