快速说明,这样我就不会浪费任何时间。从nuget安装MVVMLight时,我最终收到错误null : The term 'null' is not recognized as the name of a cmdlet, function, script file, or operable program
。尽管有此问题,MVVMLight似乎仍然可以正常工作,但以下将要描述的问题除外,但是我想提一下这是为了以防万一。
问题
我遇到一个问题,命令执行完成后按钮无法重新启用。如果执行速度非常快,有时它们有时会起作用,但是任何耗时的操作似乎永远无法起作用。这尖叫着比赛条件。
我正在使用Task执行冗长的操作,以便可以更新UI。任务的第一步和最后一步是适当翻转IsBusy(其中每个CanExecute方法returns !IsBusy;
)
我建立了一个简单的示例,该示例将使用Thread.Sleep来模拟慢速操作,并且很好地展示了该问题。 WaitOneSecondCommand
似乎间歇性地工作。 WaitTenSecondsCommand
和WaitThirtySecondsCommand
从不起作用。
“通过不起作用”表示按钮保持禁用状态,直到我单击表单上的某个位置。
我尝试过的事情
我已经做了大量的研究,到目前为止,我尝试过的解决方案并没有改变这种行为。最终,我以为我误会了所有其他“修复程序”的暴力破解。
我尝试的一件事是扩展RelayCommands以引发属性更改。举个例子:
从:
public RelayCommand WaitOneSecondCommand {
get; set;
}
至:
public RelayCommand WaitTenSecondsCommand {
get {
return _waitTenSecondsCommand;
}
set {
_waitTenSecondsCommand = value;
RaisePropertyChanged();
}
}
我没想到它会起作用,但是我想尝试一下。我也尝试添加
WaitTenSecondsCommand.RaiseCanExecuteChanged();
我还尝试将
WaitTenSecondsCommand.RaiseCanExecuteChanged()
添加到CommandExecute方法中,但这也没有任何改变。private void WaitTenSecondsCommandExecute() {
Task.Run(() => {
IsBusy = true;
Thread.Sleep(10000);
IsBusy = false;
WaitTenSecondsCommand.RaiseCanExecuteChanged();
});
}
我还阅读了有关CommandManager的信息,因此也添加了
CommandManager.InvalidateRequerySuggested()
。我将其添加到IsBusy中,认为这将是垃圾邮件,但我认为它可以消除任何疑问再次,这闻到了竞争状况,我在这里使用Task,但是由于使用了IsBusy标志,因此这些任务不能同时运行,并且不能相互冲突。
完整代码
这是一个基本的WPF应用程序,它使用.Net 4.6.1和从Nuget安装的MVVMLight 5.2.0。
MainWindow.xaml
<Window x:Class="StackOverflowExample.View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" MinHeight="100" Width="150" ResizeMode="NoResize" SizeToContent="Height"
DataContext="{Binding Source={StaticResource Locator}, Path=Main}">
<StackPanel>
<Button Height="70" Margin="5" Command="{Binding WaitOneSecondCommand}">Wait 1 Second</Button>
<Button Height="70" Margin="5" Command="{Binding WaitTenSecondsCommand}">Wait 10 Seconds</Button>
<Button Height="70" Margin="5" Command="{Binding WaitThirtySecondsCommand}">Wait 30 Seconds</Button>
</StackPanel>
<Grid>
<Label HorizontalAlignment="Left" Width="84">IsBusy:</Label>
<TextBox IsReadOnly="True" HorizontalAlignment="Right" Width="45" Text="{Binding IsBusy}" Margin="4,5,5,5" />
</Grid>
</Window>
我为IsBusy添加了绑定,以清楚地表明它正在正确更新。这也是知道10秒和30秒命令何时完成的唯一可靠方法。我不希望任何消息框强制您单击“确定”,从而导致CanExecute更新。那是创可贴修复。
MainViewModel.cs
public class MainViewModel : ViewModelBase {
private bool _isBusy;
private RelayCommand _waitTenSecondsCommand;
public MainViewModel() {
WaitOneSecondCommand = new RelayCommand(WaitOneSecondCommandExecute, WaitOneSecondCommandCanExecute);
WaitTenSecondsCommand = new RelayCommand(WaitTenSecondsCommandExecute, WaitTenSecondsCommandCanExecute);
WaitThirtySecondsCommand = new RelayCommand(WaitThirtySecondsCommandExecute, WaitThirtySecondsCommandCanExecute);
}
public RelayCommand WaitOneSecondCommand {
get; set;
}
public RelayCommand WaitTenSecondsCommand {
get {
return _waitTenSecondsCommand;
}
set {
_waitTenSecondsCommand = value;
RaisePropertyChanged();
WaitTenSecondsCommand.RaiseCanExecuteChanged();
}
}
public RelayCommand WaitThirtySecondsCommand {
get; set;
}
public bool IsBusy {
get {
return _isBusy;
}
set {
_isBusy = value;
RaisePropertyChanged();
CommandManager.InvalidateRequerySuggested();
}
}
private void WaitOneSecondCommandExecute() {
Task.Run(() => {
IsBusy = true;
Thread.Sleep(1000);
IsBusy = false;
});
}
private void WaitTenSecondsCommandExecute() {
Task.Run(() => {
IsBusy = true;
Thread.Sleep(10000);
IsBusy = false;
WaitTenSecondsCommand.RaiseCanExecuteChanged();
});
}
private void WaitThirtySecondsCommandExecute() {
Task.Run(() => {
IsBusy = true;
Thread.Sleep(30000);
IsBusy = false;
});
}
private bool WaitOneSecondCommandCanExecute() {
return !IsBusy;
}
private bool WaitTenSecondsCommandCanExecute() {
return !IsBusy;
}
private bool WaitThirtySecondsCommandCanExecute() {
return !IsBusy;
}
}
请注意,在视图模型中,我仅在WaitTenSeconds上放置了一个后备字段,以表明它不会更改行为。
完整示例:
https://dl.dropboxusercontent.com/u/24480203/StackOverflowExample.zip
最佳答案
这个问题完全归功于Viv:https://stackoverflow.com/a/18385949/865868
我面临的问题是我正在一个单独的线程中更新IsBusy,而主UI线程显然依赖于它来更新按钮。
我最近还参加了为期一周的WPF培训,现在我对异步等待有了更深入的了解,在阅读了之前链接的问题后,我运用了新发现的知识。
我原来的程序基本上没有变化。我可能应该为实际用法添加一个“值更改”检查,但真正的更改是为相应的命令添加了RaiseCanExecuteChanged()方法调用。
public bool CanWaitOneSecond {
get {
return _canWaitOneSecond;
}
set {
_canWaitOneSecond = value;
RaisePropertyChanged();
WaitOneSecondCommand.RaiseCanExecuteChanged();
}
}
现在,相应的CommandExecute方法仅使用异步等待
private async void WaitOneSecondCommandExecute() {
CanWaitOneSecond = true;
await Task.Delay(1000);
CanWaitOneSecond = false;
}
做完了我还修改了示例代码,使其具有三个独立变量,因此可以分别按下所有按钮。
我希望这可以节省一些人的研究时间,如果此实现存在任何问题,请告诉我。