MVVMLight CanExecute 不工作,直到窗口单击 [英] MVVMLight CanExecute not working until window click

查看:99
本文介绍了MVVMLight CanExecute 不工作,直到窗口单击的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

快速记录,这样我就不会浪费任何人的时间.从 nuget 安装 MVVMLight 时,我最终收到错误 null : 术语null"未被识别为 cmdlet、函数、脚本文件或可运行程序的名称.尽管如此,MVVMLight 似乎工作正常,除了下面将描述的问题,但我想提一下以防万一.

Quick note so I do not waste anyone's time. When installing MVVMLight from nuget I eventually get an error null : The term 'null' is not recognized as the name of a cmdlet, function, script file, or operable program. MVVMLight seems to work fine despite this except the issue that will be described below, but I wanted to mention this just in case.

我遇到了命令执行完成后按钮无法重新启用的问题.如果执行速度很快,它们似乎有时会起作用,但是任何需要一段时间的操作似乎都不起作用.这是竞争条件的尖叫.

I am experiencing an issue with buttons not re-enabling after command execution completes. It seems they sometimes work if the execution is very fast, but any operation that takes a while seems to never work. This screams of race condition.

我正在使用 Task 来执行冗长的操作,以便 UI 可以更新.任务的第一步和最后一步是适当地翻转 IsBusy(其中每个 CanExecute 方法返回 !IsBusy;)

I am using Task to execute the lengthy operations so the UI can update. The first and last step of the Task is to flip IsBusy appropriately (where each CanExecute method returns !IsBusy;)

我已经构建了一个简单的例子,它将使用 Thread.Sleep 来模拟慢速操作,它很好地展示了这个问题.WaitOneSecondCommand 似乎间歇性地工作.WaitTenSecondsCommandWaitThirtySecondsCommand 从不工作.

I have built a simple example that will use Thread.Sleep to simulate slow operations and it showcases the problem quite well. The WaitOneSecondCommand seems to work intermittently. The WaitTenSecondsCommand and WaitThirtySecondsCommand never work.

不起作用我的意思是按钮保持禁用状态,直到我点击表单上的某处.

By does not work I mean the buttons remain disabled until I click somewhere on the form.

我进行了大量研究,到目前为止我尝试过的解决方案并没有改变这种行为.万一我误解了,我最终导致强行执行所有不同的修复".

I have done a fair amount of research and the solutions I have tried so far have not changed this behaviour. I eventually resulted to brute forcing all the different "fixes" in case I am misunderstanding.

我尝试过的一件事是扩展 RelayCommands 以提高属性更改.举个例子:

One thing I tried was expanding the RelayCommands to raise property changed. As an example:

来自:

public RelayCommand WaitOneSecondCommand {
    get; set;
}

致:

public RelayCommand WaitTenSecondsCommand {
    get {
        return _waitTenSecondsCommand;
    }

    set {
        _waitTenSecondsCommand = value;
        RaisePropertyChanged();
    }
}

我没想到它会起作用,但我想尝试一下.我也尝试添加 WaitTenSecondsCommand.RaiseCanExecuteChanged();

I did not expect it to work, but I wanted to try it. I also tried adding WaitTenSecondsCommand.RaiseCanExecuteChanged();

我也尝试将 WaitTenSecondsCommand.RaiseCanExecuteChanged() 添加到 CommandExecute 方法,但这也没有改变任何东西.

I also tried adding WaitTenSecondsCommand.RaiseCanExecuteChanged() to the CommandExecute methods, but that also did not change anything.

private void WaitTenSecondsCommandExecute() {
    Task.Run(() => {
        IsBusy = true;
        Thread.Sleep(10000);
        IsBusy = false;
        WaitTenSecondsCommand.RaiseCanExecuteChanged();
    });
}

我还了解了 CommandManager,因此我还添加了 CommandManager.InvalidateRequerySuggested().我将它添加到 IsBusy 认为这将是非常垃圾邮件,但我认为它会消除任何疑问

I also read about the CommandManager, so I added CommandManager.InvalidateRequerySuggested() as well. I added it to the IsBusy thinking this would be very spammy, but I figured it would remove any doubt

这又是一种竞争条件的味道,我在这里使用了 Task,但是这些任务不能同时运行,并且由于使用了 IsBusy 标志而不能相互冲突.

Again this smells of race condition, and I am using Task here, but these tasks cannot run at the same time, and cannot conflict with each other due to the IsBusy flag being used.

这是一个基本的 WPF 应用程序,使用 .Net 4.6.1 和从 Nuget 安装的 MVVMLight 5.2.0.

This is a basic WPF application using .Net 4.6.1 with MVVMLight 5.2.0 installed from Nuget.

主窗口.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 更新的 MessageBoxes.这是一个创可贴修复.

I've added the binding for IsBusy to clearly show that it is updating properly. It's also the only reliable way to know when the 10 and 30 second Commands complete. I do not want any MessageBoxes that force you to click OK causing the CanExecute to update. That is a bandaid fix.

MainViewModel.cs

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 上放置了一个支持字段,以表明它不会改变行为.

Note that in the viewmodel I only put a backing field on WaitTenSeconds to showcase that it does not change the behaviour.

推荐答案

Viv 在这个问题上完全归功于:https:///stackoverflow.com/a/18385949/865868

Full credit goes to Viv over in this question: https://stackoverflow.com/a/18385949/865868

我面临的问题是我正在一个单独的线程中更新 IsBusy,而主 UI 线程显然依赖它来更新按钮.

The problem I am facing is that I am updating IsBusy in a separate thread and the main UI thread obviously is depending on it to update the buttons.

2020 年更新!

我想重新审视这个答案,因为我的理解有缺陷,但我觉得还有更好的方法.目标应该是不修改 IsBusy 的设置,或根本不修改任何支持属性.此外,应避免使用 async void.

I wanted to revisit this answer because my understanding was flawed, though I feel there is still a better way. The goal should have been to not modify the setting of IsBusy, or any supporting property at all. Also, async void should be avoided.

相应的 CommandExecute 方法现在只使用 async await

The corresponding CommandExecute method now simply makes use of async await

private async Task WaitOneSecondCommandExecute() {
    CanWaitOneSecond = true;
    await Task.Delay(1000);
    CanWaitOneSecond = false;
}

我对CanWaitOneSecond"做了这个改动

And I made this change to "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 以支持 async Task 而不是 async void

I also made a slight change to the constructor where I set up the RelayCommand to support async Task instead of async void

Commands = new List<RelayCommand>();
Commands.Add(WaitOneSecondCommand = new RelayCommand(async () => await WaitOneSecondCommandExecute(), WaitOneSecondCommandCanExecute));

我希望这可以为某人节省大量研究时间,如果此实现有任何问题,请告诉我.我还是希望把它清理干净

I hope this saves someone a lot of time researching, and please if there are any issues with this implementation please let me know. I still hope to clean this up

这篇关于MVVMLight CanExecute 不工作,直到窗口单击的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆