Reputation: 16764
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)
Upvotes: 1
Views: 2198
Reputation: 169400
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);
}
Upvotes: 2
Reputation: 4116
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
Upvotes: 0