我有一个WPF应用程序,它只包含一个Button和一个Textbox来显示一些输出。当用户单击Button时,会启动一个禁用Button的线程,将东西打印到输出Textbox,然后线程停止(此时我希望再次启用Button)。
应用程序似乎正确禁用了Button,并正确更新了Textbox。但是,当线程完成时,它总是无法正确地重新启用Button!谁能告诉我我做错了什么?
这是我的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代码(我正在使用Josh Smith的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";
}
}
答案 0 :(得分:1)
您需要帮助WPF知道命令的可执行状态已更改。这样做的简单方法是:
CommandManager.InvalidateRequerySuggested()
在CanExecuteThread中:
set
{
_threadStopped = value;
CommandManager.InvalidateRequerySuggested()
}
编辑(现在我有时间):实际问题可能是您在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);
}
}
你可以进一步重构这个更好,但你明白了。
答案 1 :(得分:0)
正如Kent Boogaart所建议的那样,这是答案,并且它有效。基本上,我必须通过将它放在Dispatcher调用调用中来调用UI线程上的CommandManager.InvalidateRequerySuggested。另请注意,我能够摆脱CanExecuteThread属性上的Set访问器,因为此解决方案不再需要它。谢谢,肯特!
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();
}
}