MVVMLight CanExecute 在 window 单击之前无法工作
MVVMLight CanExecute not working until window click
快速记下,以免浪费任何人的时间。从 nuget 安装 MVVMLight 时,我最终收到错误 null : The term 'null' is not recognized as the name of a cmdlet, function, script file, or operable program
。尽管如此,MVVMLight 似乎工作正常,但下面将描述的问题除外,但我想提一下以防万一。
问题
我遇到命令执行完成后按钮无法重新启用的问题。如果执行速度非常快,它们似乎有时会起作用,但任何需要一段时间的操作似乎都不起作用。这是竞争条件的尖叫声。
我正在使用 Task 执行冗长的操作,以便 UI 可以更新。 Task 的第一步也是最后一步是适当地翻转 IsBusy(其中每个 CanExecute 方法 returns !IsBusy;
)
我构建了一个简单的示例,它将使用 Thread.Sleep 来模拟缓慢的操作,它很好地展示了问题。 WaitOneSecondCommand
似乎间歇性地工作。 WaitTenSecondsCommand
和 WaitThirtySecondsCommand
从不工作。
不起作用 我的意思是按钮保持禁用状态,直到我单击表单上的某处。
我尝试过的事情
我进行了大量研究,到目前为止我尝试过的解决方案都没有改变这种行为。我最终导致暴力破解所有不同的 "fixes" 以防我误解。
我尝试的一件事是扩展 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="WhosebugExample.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 秒命令何时完成的唯一可靠方法。我不希望任何 MessageBoxes 强制您单击“确定”导致 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 上放置了一个支持字段以表明它不会改变行为。
您需要从 UI 线程触发 'RaiseCanExecuteChanged'。对于可能并非如此的任务。要查看这是否可以解决问题,最简单的方法是将其添加到 IsBusy setter。请注意,这不是您构建应用程序的方式(在 IsBusy setter 中),而是您应该检测任务的完成情况并在那里执行。
public bool IsBusy {
get {
return _isBusy;
}
set {
_isBusy = value;
RaisePropertyChanged();
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() => {
WaitOneSecondsCommand.RaiseCanExecuteChanged();
WaitTenSecondsCommand.RaiseCanExecuteChanged();
WaitThirtySecondsCommand.RaiseCanExecuteChanged();
})); }
}
这个问题的全部功劳都归功于 Viv:
我面临的问题是我在单独的线程中更新 IsBusy,而主 UI 线程显然依赖于它来更新按钮。
从 2020 年开始更新!
我想重新审视这个答案,因为我的理解是有缺陷的,尽管我觉得还有更好的方法。目标应该是不修改 IsBusy 的设置,或者根本不修改任何支持 属性。此外,应避免 async void。
相应的 CommandExecute 方法现在只使用 async await
private async Task WaitOneSecondCommandExecute() {
CanWaitOneSecond = true;
await Task.Delay(1000);
CanWaitOneSecond = false;
}
然后我将此更改为 "CanWaitOneSecond"
public bool CanWaitOneSecond {
get => _canWaitOneSecond ;
set {
_canWaitOneSecond = value;
Set(() => CanWaitOneSecond , ref _canWaitOneSecond , value);
RaiseAllCanExecuteChanged();
}
}
public void RaiseAllCanExecuteChanged() {
foreach (var command in Commands) {
command.RaiseCanExecuteChanged();
}
}
public List<RelayCommand> Commands {
get;
}
我还对设置 RelayCommand 以支持异步任务而不是异步 void 的构造函数进行了细微更改
Commands = new List<RelayCommand>();
Commands.Add(WaitOneSecondCommand = new RelayCommand(async () => await WaitOneSecondCommandExecute(), WaitOneSecondCommandCanExecute));
我希望这可以节省很多人的研究时间,如果此实现有任何问题,请告诉我。我还是希望清理一下
快速记下,以免浪费任何人的时间。从 nuget 安装 MVVMLight 时,我最终收到错误 null : The term 'null' is not recognized as the name of a cmdlet, function, script file, or operable program
。尽管如此,MVVMLight 似乎工作正常,但下面将描述的问题除外,但我想提一下以防万一。
问题
我遇到命令执行完成后按钮无法重新启用的问题。如果执行速度非常快,它们似乎有时会起作用,但任何需要一段时间的操作似乎都不起作用。这是竞争条件的尖叫声。
我正在使用 Task 执行冗长的操作,以便 UI 可以更新。 Task 的第一步也是最后一步是适当地翻转 IsBusy(其中每个 CanExecute 方法 returns !IsBusy;
)
我构建了一个简单的示例,它将使用 Thread.Sleep 来模拟缓慢的操作,它很好地展示了问题。 WaitOneSecondCommand
似乎间歇性地工作。 WaitTenSecondsCommand
和 WaitThirtySecondsCommand
从不工作。
不起作用 我的意思是按钮保持禁用状态,直到我单击表单上的某处。
我尝试过的事情
我进行了大量研究,到目前为止我尝试过的解决方案都没有改变这种行为。我最终导致暴力破解所有不同的 "fixes" 以防我误解。
我尝试的一件事是扩展 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="WhosebugExample.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 秒命令何时完成的唯一可靠方法。我不希望任何 MessageBoxes 强制您单击“确定”导致 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 上放置了一个支持字段以表明它不会改变行为。
您需要从 UI 线程触发 'RaiseCanExecuteChanged'。对于可能并非如此的任务。要查看这是否可以解决问题,最简单的方法是将其添加到 IsBusy setter。请注意,这不是您构建应用程序的方式(在 IsBusy setter 中),而是您应该检测任务的完成情况并在那里执行。
public bool IsBusy {
get {
return _isBusy;
}
set {
_isBusy = value;
RaisePropertyChanged();
Application.Current.Dispatcher.Invoke(
DispatcherPriority.ApplicationIdle,
new Action(() => {
WaitOneSecondsCommand.RaiseCanExecuteChanged();
WaitTenSecondsCommand.RaiseCanExecuteChanged();
WaitThirtySecondsCommand.RaiseCanExecuteChanged();
})); }
}
这个问题的全部功劳都归功于 Viv:
我面临的问题是我在单独的线程中更新 IsBusy,而主 UI 线程显然依赖于它来更新按钮。
从 2020 年开始更新!
我想重新审视这个答案,因为我的理解是有缺陷的,尽管我觉得还有更好的方法。目标应该是不修改 IsBusy 的设置,或者根本不修改任何支持 属性。此外,应避免 async void。
相应的 CommandExecute 方法现在只使用 async await
private async Task WaitOneSecondCommandExecute() {
CanWaitOneSecond = true;
await Task.Delay(1000);
CanWaitOneSecond = false;
}
然后我将此更改为 "CanWaitOneSecond"
public bool CanWaitOneSecond {
get => _canWaitOneSecond ;
set {
_canWaitOneSecond = value;
Set(() => CanWaitOneSecond , ref _canWaitOneSecond , value);
RaiseAllCanExecuteChanged();
}
}
public void RaiseAllCanExecuteChanged() {
foreach (var command in Commands) {
command.RaiseCanExecuteChanged();
}
}
public List<RelayCommand> Commands {
get;
}
我还对设置 RelayCommand 以支持异步任务而不是异步 void 的构造函数进行了细微更改
Commands = new List<RelayCommand>();
Commands.Add(WaitOneSecondCommand = new RelayCommand(async () => await WaitOneSecondCommandExecute(), WaitOneSecondCommandCanExecute));
我希望这可以节省很多人的研究时间,如果此实现有任何问题,请告诉我。我还是希望清理一下