1

I have the following view model used in MainWindow.xaml, the view model is called MainViewModel:

public abstract class AbstractPropNotifier : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged(string name)
    {
      PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    }
}

public sealed class MainViewModel : AbstractPropNotifier
{
    private bool _editEnabled;
    private bool _deleteEnabled;
    private ICommand _editCommand;
    private ICommand _deleteCommand;
    private IRssViewModel _selectedIrssi;
    private IAsyncCommand _addCommand;

    private readonly Dispatcher _dispatcher;

    public MainViewModel(Dispatcher dispatcher)
    {
      _dispatcher = dispatcher;

      IrssItems = new ObservableCollection<IRssViewModel>();
      Log = new ObservableCollection<string>();
      EditEnabled = false;
      DeleteEnabled = false;

      EditCommand = new RelayCommand(c => EditItem(), p => EditEnabled);
      DeleteCommand = new RelayCommand(DeleteItems, p => DeleteEnabled);
      AddCommand = new AsyncCommand(AddItem, () => true);
    }

    public ObservableCollection<IRssViewModel> IrssItems { get; set; }

    public IRssViewModel SelectedIrssi
    {
      get
      {
        return _selectedIrssi;
      }
      set
      {
        _selectedIrssi = value;
        OnPropertyChanged(nameof(SelectedIrssi));
        EditEnabled = DeleteEnabled = true;
      }
    }

    public ObservableCollection<string> Log { get; set; }

    public bool EditEnabled
    {
      get
      {
        return _editEnabled;
      }
      set
      {
        _editEnabled = value || SelectedIrssi != null;
        OnPropertyChanged(nameof(EditEnabled));
      }
    }

    public bool DeleteEnabled
    {
      get
      {
        return _deleteEnabled;
      }
      set
      {
        _deleteEnabled = value || SelectedIrssi != null;
        OnPropertyChanged(nameof(DeleteEnabled));
      }
    }

    public ICommand EditCommand
    {
      get
      {
        return _editCommand;
      }
      set
      {
        _editCommand = value;
      }
    }

    public ICommand DeleteCommand
    {
      get
      {
        return _deleteCommand;
      }
      set
      {
        _deleteCommand = value;
      }
    }

    public IAsyncCommand AddCommand
    {
      get
      {
        return _addCommand;
      }
      set
      {
        _addCommand = value;
      }
    }

    private void EditItem()
    {

    }

    private void DeleteItems(object selectedItems)
    {
      var list = selectedItems as IList;
      var newList = new List<IRssViewModel>(list.Cast<IRssViewModel>());

      if (MessageBox.Show($"Are you sure that want to delete {newList.Count} item{(newList.Count > 1 ? "s" : "")} ?", "Deletion", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
      {

        foreach (var item in newList)
        {
          IrssItems.Remove(item as IRssViewModel);
        }

        EditEnabled = DeleteEnabled = false;
      }
    }

    private async Task AddItem()
    {
      var win = new ManageIrssi("Add item");
      var result = win.ShowDialog();

      if (result.HasValue && result.Value)
      {
        foreach (var data in win.Model.Items)
        {
          //check stuff

          IrssItems.Add(data);
          await CreateConnection(data);
        }
      }
    }

    private async Task CreateConnection(IRssViewModel data)
    {
      await Task.Run(() =>
      {
        IrcManager manager = new IrcManager(new CustomLogger(), data);
        manager.Build(s => _dispatcher.Invoke(() => Log.Add(s)));

        data.IsConnected = true;
      });
    }
}

and AsynCommand is got from https://johnthiriet.com/mvvm-going-async-with-async-command/

public class AsyncCommand : IAsyncCommand
{
    public event EventHandler CanExecuteChanged;

    private bool _isExecuting;
    private readonly Func<Task> _execute;
    private readonly Func<bool> _canExecute;
    private readonly IErrorHandler _errorHandler;

    public AsyncCommand(
        Func<Task> execute,
        Func<bool> canExecute = null,
        IErrorHandler errorHandler = null)
    {
      _execute = execute;
      _canExecute = canExecute;
      _errorHandler = errorHandler;
    }

    public bool CanExecute()
    {
      return !_isExecuting && (_canExecute?.Invoke() ?? true);
    }

    public async Task ExecuteAsync()
    {
      if (CanExecute())
      {
        try
        {
          _isExecuting = true;
          await _execute();
        }
        finally
        {
          _isExecuting = false;
        }
      }

      RaiseCanExecuteChanged();
    }

    public void RaiseCanExecuteChanged()
    {
      CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    #region Explicit implementations
    bool ICommand.CanExecute(object parameter)
    {
      return CanExecute();
    }

    void ICommand.Execute(object parameter)
    {
      ExecuteAsync().GetAwaiter().GetResult();
    }
    #endregion
}

The problem I met is that After press a button Add, the last line data.IsConnected = true; is executed and then nothing happens means UI is frozen and no item is added in UI datagrid.

I removed also part _dispatcher.Invoke(() => Log.Add(s), same issue, UI frozen.

Why ? Where is my mistake ? Seems the problem is in await CreateConnection(data)

7
  • 1
    Just a note, there doesn't seem to be a need to make the command properties settable. You also don't need a backing field. A declaration like public IAsyncCommand AddCommand { get; } seems sufficient. The assignment in the constructor will still work. Commented Aug 13, 2019 at 7:28
  • 1
    Yeah, I will refactor later. Now, I'm focused on UI issue Commented Aug 13, 2019 at 7:32
  • Why did you pass a Dispatcher, where it's initialized? You can also try to wrap the logic in CreateConnection method into _dispatcher.InvokeAsync Commented Aug 13, 2019 at 8:10
  • I am hoping that var win = new ManageIrssi("Add item"); is not a window! Otherwise this is not MvvM! Going back to your issue, when you debug, can you see that you are in a different thread? Also put the dispatcher back in and a breakpoint in there and confirm that you are in the UI thread. Commented Aug 13, 2019 at 8:24
  • 1
    Try using a simple ICommand (not this AsyncCommand), with the delegate action being an async void. Commented Aug 13, 2019 at 9:12

2 Answers 2

2

Your sample code is neither compilable or minimal, but I can spot a flaw in the Execute method of your command:

void ICommand.Execute(object parameter)
{
  ExecuteAsync().GetAwaiter().GetResult();
}

Calling Result on a Task may deadlock and is a big no-no, especially in GUI applications. Try to fire away the Task and then return from the method:

async void ICommand.Execute(object parameter)
{
    await ExecuteAsync().ConfigureAwait(false);
}
Sign up to request clarification or add additional context in comments.

1 Comment

Yes, the issue was from Execute one, nice catch !
0

Problem is AddItem is on UI thread and since it is Awaits on UI Thread, your UI stalls. Take AddItem on new thread and release UI thread, dispatch it to main thread once it is complete and update the UI

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.