A.Pissicat
A.Pissicat

Reputation: 3293

Use ICommand in WPF

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 ?

Upvotes: 3

Views: 2483

Answers (4)

Dmitry Polishuk
Dmitry Polishuk

Reputation: 196

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
    }

}

Upvotes: 0

Andrew Stephens
Andrew Stephens

Reputation: 10203

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.

Upvotes: 2

mm8
mm8

Reputation: 169390

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

Upvotes: 3

user6996876
user6996876

Reputation:

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

Upvotes: 1

Related Questions