3

What is the best way to use commands in WPF ?

I use some commands, thoses commands can take a time to execute. I want that my application not freeze while running but I want the features to be disabled.

there is my 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>

and my 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
}

I saw a solution with background worker but I don't know how to use it. And I wonder if I should create one instance by command.

Is there a cleaner/best way ?

A.Pissicat
  • 3,023
  • 4
  • 38
  • 93

4 Answers4

3

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

The key to prevent the application from freezing is to perform any long-running operation on a background thread. The easiest way to do this is to start a Task. To disable the window you could bind its IsEnabled property to a source property of the view model that you set prior to starting the task. The following sample code should give you the idea:

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

Note that you cannot access any UI element from a background thread since controls have thread affinity: http://volatileread.com/Thread/Index?id=1056

mm8
  • 163,881
  • 10
  • 57
  • 88
  • Your solution seems to be the simplest. I just have to create one task for each command right ? I just have a problem with `CanLoad()`, the button doesn't reactivate when I set the `IsEnabled = true; _canLoad = true;`, but I will find a solution. Thank you – A.Pissicat Jan 25 '17 at 11:16
  • Yes, you create a new task for each call to a long-running background operation. Calling the RaiseCanExecuteChanged() method of the command should cause the CanLoad() delegate to invoked again and the command's status to be refreshed. – mm8 Jan 25 '17 at 11:18
  • I have no definition for `RaisePropertyChanged()`. I'm using a VIewModelBase with only 'OnPropertyChanged' like [this one](http://stackoverflow.com/questions/1315621/implementing-inotifypropertychanged-does-a-better-way-exist) – A.Pissicat Jan 25 '17 at 11:32
  • You will need another implementation of the ICommand interface then, i.e. another RelayCommand class. There is one available in the MvvmLight library that both has a RaiseCanExecuteChanged() method and a RaisePropertyChanged() method: https://www.nuget.org/packages/MvvmLight/ – mm8 Jan 25 '17 at 11:35
2

My approach to avoid UI freezing in these scenarios is to use async/await in the ICommand execution, and execute the long-running code on a background thread. Your modified code would look something like this:

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

If that background task needs to update anything bound to the UI then it needs to be wrapped in a Dispatcher.Invoke (or Dispatcher.BeginInvoke).

If you want to prevent the command from being executed a second time just set "CanLoad" to true before the await Task.Run(... line, and back to false after it.

Andrew Stephens
  • 9,413
  • 6
  • 76
  • 152
  • This way to write is like the answer by mm8 ? Is there a difference in execution ? By the way this is even easier to write. – A.Pissicat Jan 25 '17 at 11:23
  • @A.Pissicat `Task.Run()` is really just a concise version of writing `Task.Factory.StartNew...`, introduced in .Net 4.5. The `async/await` keywords are also very elegant to use, once you understand how they work. In simple terms the UI thread will return to what it was doing once it has launched that task. When that task completes, the UI thread will "pick up where it left off" and execute any remaining code that comes after that line (which could be more `await`able calls). – Andrew Stephens Jan 25 '17 at 11:37
1

I'd suggest to use Akka.Net: you can find an example with WPF on github.

I've forked it to impement stop and start commands: my goal was to show bidirectional communication between Akka.Net actors and ViewModel.

You'll find the ViewModel calling the ActorSystem like this

    private void StartCpuMethod() {
        Debug.WriteLine("StartCpuMethod");
        ActorSystemReference.Start();
    }
    private void StopCpuMethod() {
        Debug.WriteLine("StopCpuMethod");
        ActorSystemReference.Stop();
    }

with an Actor receiving those messages

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

and the other way round from an 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);
    }

to the 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);
    }
0

The best way here it's a use of async/await, in my opinion. 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
    }

}