在WPF中使用ICommand

3

如何在WPF中最好地使用命令?

我使用一些命令,这些命令可能需要一些时间来执行。我希望在运行时我的应用程序不会冻结,但我想要禁用某些功能。

以下是我的MainWindow.xaml:

<Window ...>
    <Window.DataContext>
        <local:MainViewModel/>
    </Window.DataContext>
    <Grid>          
        <Button Grid.Row="0"
                Grid.Column="0"
                Style="{StaticResource StyleButton}"
                Content="Load"
                Command="{Binding LoadCommand}"/>
        <Button Grid.Row="0"
                Grid.Column="1"
                Style="{StaticResource StyleButton}"
                Content="Generate"
                Command="{Binding GenerateCommand}"/>
    </Grid>
</Window>

我的 MainViewModel.cs 文件如下:

public class MainViewModel : ViewModelBase
{

    #region GenerateCommand
    #endregion

    #region Load command
    private ICommand _loadCommand;
    public ICommand LoadCommand
    {
        get
        {
            if (_loadCommand == null)
                _loadCommand = new RelayCommand(OnLoad, CanLoad);
            return _loadCommand;
        }
    }

    private void OnLoad()
    {
        //My code
    }
    private bool CanLoad()
    {
        return true;
    }
    #endregion
}

我看到了一个使用后台工作线程的解决方案,但我不知道如何使用它。我想知道是否应该通过命令创建一个实例。

有没有更简洁/更好的方法?


请查看:https://dev59.com/5V0a5IYBdhLWcg3wD1Lb - Ehsan Sajjad
4个回答

3

我希望我的应用在运行时不会冻结,但我希望功能被禁用。

防止应用程序冻结的关键是在后台线程上执行任何长时间运行的操作。最简单的方法是启动一个任务(Task)。要禁用窗口,您可以将其IsEnabled属性绑定到视图模型的源属性,在启动任务之前设置该属性。以下示例代码应该能给您提供思路:

public class MainViewModel : ViewModelBase
{
    private RelayCommand _loadCommand;
    public ICommand LoadCommand
    {
        get
        {
            if (_loadCommand == null)
                _loadCommand = new RelayCommand(OnLoad, CanLoad);
            return _loadCommand;
        }
    }

    private void OnLoad()
    {
        IsEnabled = false;
        _canLoad = false;
        _loadCommand.RaiseCanExecuteChanged();

        Task.Factory.StartNew(()=> { System.Threading.Thread.Sleep(5000); })  //simulate som long-running operation that runs on a background thread...
            .ContinueWith(task =>
            {
                //reset the properties back on the UI thread once the task has finished
                IsEnabled = true;
                _canLoad = true;
            }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    }

    private bool _canLoad = true;
    private bool CanLoad()
    {
        return _canLoad;
    }

    private bool _isEnabled;
    public bool IsEnabled
    {
        get { return _isEnabled; }
        set { _isEnabled = value; RaisePropertyChanged(); }
    }
}

请注意,由于控件具有线程亲和性,因此您无法从后台线程访问任何UI元素:http://volatileread.com/Thread/Index?id=1056

你的解决方案似乎是最简单的。我只需要为每个命令创建一个任务,对吧? 我只有一个问题与 CanLoad() 有关,当我将 IsEnabled = true; _canLoad = true; 设置后,该按钮并未重新激活,但我会想出一个解决办法的。谢谢 - A.Pissicat
是的,每次调用长时间运行的后台操作时,都需要创建一个新任务。调用命令的RaiseCanExecuteChanged()方法应该会再次调用CanLoad()委托,并刷新命令的状态。 - mm8
我没有“RaisePropertyChanged()”的定义。我正在使用一个只有“OnPropertyChanged”的ViewModelBase,就像这个一样。 - A.Pissicat
你需要另一个ICommand接口的实现,即另一个RelayCommand类。在MvvmLight库中有一个可用的RelayCommand类,它既有RaiseCanExecuteChanged()方法,也有RaisePropertyChanged()方法:https://www.nuget.org/packages/MvvmLight/ - mm8

2

在这些情况下,我避免UI冻结的方法是在ICommand执行中使用async/await,并在后台线程上执行长时间运行的代码。修改后的代码如下:

public ICommand LoadCommand
{
    get
    {
        if (_loadCommand == null)
            _loadCommand = new RelayCommand(async o => await OnLoadAsync(), CanLoad);
        return _loadCommand;
    }
}

private async Task OnLoadAsync()
{
    await Task.Run(() => MyLongRunningProcess());
}

如果后台任务需要更新与UI绑定的任何内容,则需要将其包装在Dispatcher.Invoke(或Dispatcher.BeginInvoke)中。

如果您想防止命令再次执行,只需在await Task.Run(...行之前将"CanLoad"设置为true,然后在其之后将其设置为false。


这种写法和 mm8 的答案类似吗?执行上有什么区别吗?顺便说一下,这种写法甚至更容易些。 - A.Pissicat
@A.Pissicat Task.Run()实际上只是写Task.Factory.StartNew...的简洁版本,它在.Net 4.5中被引入。一旦你理解了async/await关键字的工作原理,它们也非常优雅易用。简单来说,UI线程会在启动任务后返回到之前的状态。当该任务完成时,UI线程将“从离开的地方”继续执行任何剩余的代码(这可能是更多的awaitable调用)。 - Andrew Stephens

1
我建议使用Akka.Net:你可以在github上找到一个与WPF相关的例子。
我已经forked它来实现停止和启动命令: 我的目标是展示Akka.Net演员和ViewModel之间的双向通信。
你会发现ViewModel像这样调用ActorSystem。
    private void StartCpuMethod() {
        Debug.WriteLine("StartCpuMethod");
        ActorSystemReference.Start();
    }
    private void StopCpuMethod() {
        Debug.WriteLine("StopCpuMethod");
        ActorSystemReference.Stop();
    }

有一个Actor接收这些消息

    public CPUReadActor()
    {
        Receive<ReadCPURequestMessage>(msg => ReceiveReadDataMessage());
        Receive<ReadCPUSyncMessage>(msg => ReceiveSyncMessage(msg));
    }

    private void ReceiveSyncMessage(ReadCPUSyncMessage msg)
    {
        switch (msg.Op)
        {
            case SyncOp.Start:
                OnCommandStart();
                break;
            case SyncOp.Stop:
                OnCommandStop();
                break;
            default:
                throw new Exception("unknown Op " + msg.Op.ToString());
        }
    }

另一种方式是从演员的角度来看。
    public ChartingActor(Action<float, DateTime> dataPointSetter)
    {
        this._dataPointSetter = dataPointSetter;

        Receive<DrawPointMessage>(msg => ReceiveDrawPointMessage(msg));
    }

    private void ReceiveDrawPointMessage(DrawPointMessage msg)
    {
        _dataPointSetter(msg.Value, msg.Date);
    }

到视图模型

    public MainWindowViewModel()
    {
        StartCpuCommand = new RelayCommand(StartCpuMethod);
        StopCpuCommand = new RelayCommand(StopCpuMethod);

        SetupChartModel();
        Action<float, DateTime> dataPointSetter = new Action<float, DateTime>((v, d) => SetDataPoint(v, d));

        ActorSystemReference.CreateActorSystem(dataPointSetter);
    }

    private void SetDataPoint(float value, DateTime date)
    {
        CurrentValue = value;
        UpdateLineSeries(value, date);
    }

0

在我看来,最好的方法是使用async/await。https://msdn.microsoft.com/ru-ru/library/mt674882.aspx

public class MainViewModel : ViewModelBase
{

    public MainViewModel()
    {
        LoadCommand = new RelayCommand(async ol => await OnLoadAsync(), CanLoad);
    }

    public ICommand LoadCommand { get; }

    private async void OnLoadAync()
    {
        await SomethingAwaitable();
    }

    private Task<bool> SomethingAwaitable()
    {
        //Your code
    }

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接