【问题标题】:Use ICommand in WPF在 WPF 中使用 ICommand
【发布时间】:2017-06-10 11:50:55
【问题描述】:

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

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

有没有更清洁/最好的方法?

【问题讨论】:

标签: c# wpf icommand


【解决方案1】:

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

防止应用程序冻结的关键是在后台线程上执行任何长时间运行的操作。最简单的方法是启动一个任务。要禁用窗口,您可以将其 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; 时按钮不会重新激活,但我会找到解决方案。谢谢
  • 是的,您为每次调用长时间运行的后台操作创建一个新任务。调用命令的 RaiseCanExecuteChanged() 方法应该会导致 CanLoad() 委托再次被调用并刷新命令的状态。
  • 我没有RaisePropertyChanged() 的定义。我正在使用只有“OnPropertyChanged”的 VIewModelBase,例如 this one
  • 你将需要另一个 ICommand 接口的实现,即另一个 RelayCommand 类。 MvvmLight 库中有一个可用的方法,它有一个 RaiseCanExecuteChanged() 方法和一个 RaisePropertyChanged() 方法:nuget.org/packages/MvvmLight
【解决方案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 Task.Run() 实际上只是写作Task.Factory.StartNew... 的简洁版本,在.Net 4.5 中引入。一旦您了解了它们的工作原理,async/await 关键字的使用也非常优雅。简单来说,一旦启动该任务,UI 线程将返回到它正在执行的操作。当该任务完成时,UI 线程将“从中断处继续”并执行该行之后的任何剩余代码(可能是更多 awaitable 调用)。
【解决方案3】:

我建议使用 Akka.Net:您可以在 github 上找到一个 WPF 示例。

我已经forked 它来执行停止和启动命令: 我的目标是展示 Akka.Net actor 和 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);
    }

【讨论】:

    【解决方案4】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-10-13
      • 1970-01-01
      • 2015-07-12
      • 2014-05-23
      • 1970-01-01
      • 1970-01-01
      • 2013-10-02
      相关资源
      最近更新 更多