在 WPF 中使用 ICommand

Use ICommand in WPF

在 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
}

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

有cleaner/best方法吗?

我建议使用 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());
        }
    }

从 Actor 反过来

    public ChartingActor(Action<float, DateTime> dataPointSetter)
    {
        this._dataPointSetter = dataPointSetter;

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

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

到 ViewModel

    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);
    }

I want that my application not freeze while running but I want the features to be disabled.

防止应用程序冻结的关键是在后台线程上执行任何 long-running 操作。最简单的方法是启动一个任务。要禁用 window,您可以将其 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

我在这些情况下避免 UI 冻结的方法是在 ICommand 执行中使用 async/await,并在后台线程上执行 long-running 代码。您修改后的代码看起来像这样:

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。

在我看来,这里最好的方法是使用 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
    }

}