WPF:使用命令绑定和线程控制按钮的启用/禁用状态时遇到问题
我有一个 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 代码(我使用 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";
}
}
I have a WPF app that simply contains a Button and a Textbox to display some output. When the user clicks the Button, a thread starts which disables the Button, prints stuff to the output Textbox, then the thread stops (at which point I want the Button to be enabled again).
The application appears to disable the Button properly, as well as update the Textbox properly. However, it always fails to re-enable the Button properly when the thread completes! Can anyone tell me what I'm doing wrong?
Here's a snippet of my 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>
Here's my ViewModel code (I'm using Josh Smith's MVVM design):
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";
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您需要帮助 WPF 了解命令的可执行状态已更改。执行此操作的简单方法是:
在 CanExecuteThread 内:
编辑(现在我有时间):实际问题可能是您在 CanExecuteThread 属性更改时没有通知。它应该引发
PropertyChanged
以便 WPF 检测到更改:上面假设您的
ViewModel
基类具有OnPropertyChanged
方法。也就是说,我还想指出,您可以通过简单地使用 BackgroundWorker 来简化事情:
您可以进一步重构它以使其变得更好,但您明白了。
You'll need to help WPF know that the executable state of the command has changed. The simple way to do this is:
inside CanExecuteThread:
EDIT (now that I have time): the actual problem is likely that you're not notifying when the
CanExecuteThread
property changes. It should raisePropertyChanged
in order for WPF to detect the change:The above assumes your
ViewModel
base class has anOnPropertyChanged
method.That said, I also wanted to point out that you could simplify things by simply using a
BackgroundWorker
:You could refactor this further to be even nicer, but you get the idea.
这是肯特·布加特(Kent Boogaart)建议的答案,而且它有效。基本上,我必须通过将 CommandManager.InvalidateRequerySuggested 放置在 Dispatcher 调用调用中来在 UI 线程上调用 CommandManager.InvalidateRequerySuggested。另请注意,我能够删除 CanExecuteThread 属性上的 Set 访问器,因为此解决方案不再需要它。谢谢,肯特!
Here is the answer, as suggested by Kent Boogaart, and it works. Basically, I had to call CommandManager.InvalidateRequerySuggested on the UI thread by placing it within a Dispatcher invoke call. Also notice that I was able to get rid of the Set accessor on the CanExecuteThread property, as it was no longer required with this solution. Thank, Kent!