MVVMLight CanExecute在窗口点击

时间:2016-03-10 17:47:49

标签: c# wpf task mvvm-light relaycommand

快速记录,所以我不浪费任何人的时间。从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似乎间歇性地工作。 WaitTenSecondsCommandWaitThirtySecondsCommand永远无效。

无法正常工作我的意思是按钮保持禁用状态,直到我点击表单上的某处。

我尝试过的事情

我做了大量的研究,到目前为止我尝试的解决方案并没有改变这种行为。我最终导致暴力强迫所有不同的“修复”,以防我误解。

我尝试的一件事是扩展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;
    }
}

请注意,在viewmodel中,我只在WaitTenSeconds上放置一个支持字段,以显示它不会改变行为。

完整来源示例: https://dl.dropboxusercontent.com/u/24480203/StackOverflowExample.zip

2 个答案:

答案 0 :(得分:3)

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

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

我最近参加了为期一周的WPF培训,现在我对异步等待有了更好的理解,并且在阅读了我之前链接的问题后,我应用了我的新知识。

我原来的节目基本没变。我可能应该添加一个&#34;值已更改&#34;检查实际用法,但真正的改变是为相应的命令添加RaiseCanExecuteChanged()方法调用。

public bool CanWaitOneSecond {
    get {
        return _canWaitOneSecond;
    }

    set {
        _canWaitOneSecond = value;
        RaisePropertyChanged();
        WaitOneSecondCommand.RaiseCanExecuteChanged();
    }
}

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

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

完成。我还修改了我的示例代码以包含三个独立变量,因此我可以单独按下所有按钮。

我希望这可以节省大量时间进行研究,如果这个实现有任何问题,请告诉我。

答案 1 :(得分:0)

您需要触发&#39; RaiseCanExecuteChanged&#39;来自UI线程。使用可能不是这样的任务。您最简单的方法是查看是否可以解决问题,将其添加到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();
                    }));        }
}