I have following ICommand implementation, which works great, but I want to expand it so I can pass external canExecute parameter
public class AsyncRelayCommand : ICommand
{
private readonly Func<object, Task> callback;
private readonly Action<Exception> onException;
private bool isExecuting;
public bool IsExecuting
{
get => isExecuting;
set
{
isExecuting = value;
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
public event EventHandler CanExecuteChanged;
public AsyncRelayCommand(Func<object, Task> callback, Action<Exception> onException = null)
{
this.callback = callback;
this.onException = onException;
}
public bool CanExecute(object parameter) => !IsExecuting;
public async void Execute(object parameter)
{
IsExecuting = true;
try
{
await callback(parameter);
}
catch (Exception e)
{
onException?.Invoke(e);
}
IsExecuting = false;
}
}
Can this implementation be extended in a way so when caller's CanExecute() changes, both Execute1AsyncCommand and Execute2AsyncCommand will acknowledge that? Here is my caller class:
public class Caller : ObservableObject
{
public ObservableTask Execute1Task { get; } = new ObservableTask();
public ObservableTask Execute2Task { get; } = new ObservableTask();
public ICommand Execute1AsyncCommand { get; }
public ICommand Execute2AsyncCommand { get; }
public Caller()
{
Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async);
Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async);
}
private bool CanExecute(object o)
{
return Task1?.Running != true && Task2?.Running != true;
}
private async Task Execute1Async(object o)
{
Task1.Running = true;
try
{
await Task.Run(()=>Thread.Sleep(2000)).ConfigureAwait(true);
Task1.RanToCompletion = true;
}
catch (Exception e)
{
Task1.Faulted = true;
}
}
private async Task Execute2Async(object o)
{
Task2.Running = true;
try
{
await Task.Run(() => Thread.Sleep(2000)).ConfigureAwait(true);
Task2.RanToCompletion = true;
}
catch (Exception e)
{
Task2.Faulted = true;
}
}
}
In other callers I still want to be able to use AsyncRelayCommand() with just callback being mandatory. In this case CanExecute should be evaluated internally from AsyncRelayCommand as in my original implementation.
For completeness, here is my view:
<StackPanel>
<Button Content="Execute Task 1"
Command="{Binding Execute1AsyncCommand}" />
<Button Content="Execute Task 2"
Command="{Binding Execute2AsyncCommand}" />
<TextBlock Text="Task 1 running:" />
<TextBlock Text="{Binding Task1.Running}" />
<TextBlock Text="Task 2 running:" />
<TextBlock Text="{Binding Task2.Running}" />
</StackPanel>
And ObservableTask class:
public class ObservableTask : ObservableObject
{
private bool running;
private bool ranToCompletion;
private bool faulted;
public Task Task { get; set; }
public bool WaitingForActivation => !Running && !RanToCompletion && !Faulted;
public bool Running
{
get => running;
set
{
running = value;
if (running)
{
RanToCompletion = false;
Faulted = false;
}
}
}
public bool RanToCompletion
{
get => ranToCompletion;
set
{
ranToCompletion = value;
if (ranToCompletion)
{
Running = false;
}
}
}
public bool Faulted
{
get => faulted;
set
{
faulted = value;
if (faulted)
{
Running = false;
}
}
}
}
What I want to achieve is after user press one button both become disabled until all tasks are done.
Solution
I ended up with the following implementation which so far works as intended:
public class AsyncRelayCommand : ICommand
{
private bool isExecuting;
private readonly Func<object, Task> execute;
private readonly Predicate<object> canExecute;
private readonly Action<Exception, object> onException;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Func<object, Task> execute, Predicate<object> canExecute = null, Action<Exception, object> onException = null)
{
this.execute = execute;
this.canExecute = canExecute;
this.onException = onException;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !isExecuting && (canExecute == null || canExecute(parameter));
private async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
isExecuting = true;
InvalidateRequerySuggested();
await execute(parameter);
}
catch (Exception e)
{
onException?.Invoke(e, parameter);
}
finally
{
isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage:
public class Caller: ObservableObject
{
public ObservableTask Task1 { get; } = new ObservableTask();
public ObservableTask Task2 { get; } = new ObservableTask();
public ObservableTask Task3 { get; } = new ObservableTask();
public ICommand Execute1AsyncCommand { get; }
public ICommand Execute2AsyncCommand { get; }
public ICommand Execute3AsyncCommand { get; }
public Caller()
{
// Command with callers CanExecute method and error handled by callers method.
Execute1AsyncCommand = new AsyncRelayCommand(Execute1Async, CanExecuteAsMethod, Execute1ErrorHandler);
// Command with callers CanExecute parameter and error handled inside task therefore not needed.
Execute2AsyncCommand = new AsyncRelayCommand(Execute2Async, _=>CanExecuteAsParam);
// Some other, independent command.
// Minimum example - CanExecute is evaluated inside command, error handled inside task.
Execute3AsyncCommand = new AsyncRelayCommand(Execute3Async);
}
public bool CanExecuteAsParam => !(Task1.Running || Task2.Running);
private bool CanExecuteAsMethod(object o)
{
return !(Task1.Running || Task2.Running);
}
private async Task Execute1Async(object o)
{
Task1.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task1.RanToCompletion = true;
}
private void Execute1ErrorHandler(Exception e, object o)
{
Task1.Faulted = true;
}
private async Task Execute2Async(object o)
{
try
{
Task2.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task2.RanToCompletion = true;
}
catch (Exception e)
{
Task2.Faulted = true;
}
}
private async Task Execute3Async(object o)
{
try
{
Task3.Running = true;
await Task.Run(() => { Thread.Sleep(2000); }).ConfigureAwait(true);
Task3.RanToCompletion = true;
}
catch (Exception e)
{
Task3.Faulted = true;
}
}
}
Thank you everybody for invaluable help!
I have some ready-to-use solution.
Regular synchronous delegate, thus it can replace simple RelayCommand.
Delegate executed on a pooled Thread.
CanExecute is false while command is executing, thus it will disable the control automatically.
Implementation
public interface IAsyncCommand : ICommand
{
Task ExecuteAsync(object param);
}
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly Action<object> _execute;
private readonly Predicate<object> _canExecute;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Action<object> execute, Predicate<object> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await Task.Run(() => _execute(parameter));
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage
private IAsyncCommand _myAsyncCommand;
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(parameter =>
{
Thread.Sleep(2000);
}));
Note: you can't deal with ObservableCollection from non-UI Thread, as workaround I suggest this one.
Asynchronous delegate version
public class AsyncRelayCommand : IAsyncCommand
{
private bool _isExecuting;
private readonly Func<object, Task> _executeAsync;
private readonly Predicate<object> _canExecute;
private Dispatcher Dispatcher { get; }
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public AsyncRelayCommand(Func<object, Task> executeAsync, Predicate<object> canExecute = null)
{
_executeAsync = executeAsync;
_canExecute = canExecute;
Dispatcher = Application.Current.Dispatcher;
}
private void InvalidateRequerySuggested()
{
if (Dispatcher.CheckAccess())
CommandManager.InvalidateRequerySuggested();
else
Dispatcher.Invoke(CommandManager.InvalidateRequerySuggested);
}
public bool CanExecute(object parameter) => !_isExecuting && (_canExecute == null || _canExecute(parameter));
public async Task ExecuteAsync(object parameter)
{
if (CanExecute(parameter))
{
try
{
_isExecuting = true;
InvalidateRequerySuggested();
await _executeAsync(parameter);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
finally
{
_isExecuting = false;
InvalidateRequerySuggested();
}
}
}
public void Execute(object parameter) => _ = ExecuteAsync(parameter);
}
Usage
private IAsyncCommand _myAsyncCommand;
public IAsyncCommand MyAsyncCommand => _myAsyncCommand ?? (_myAsyncCommand = new AsyncRelayCommand(async parameter =>
{
await Task.Delay(2000);
}));
If your Caller had a method called CanExecute like this:
private bool CanExecute()
{
return SomeCondition && OtherCondition;
}
Then you would be able to pass it to your AsyncRelayCommand as an instance of delegate type Func<bool>, of course, if your AsyncRelayCommand defined constructor with the needed parameter:
public AsyncRelayCommand(Func<object, Task> callback, Func<bool> canExecute, Action<Exception> onException = null)
{
this.callback = callback;
this.onException = onException;
this.canExecute = canExecute;
}
Then you pass it to the constructor like this:
MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, CanExecute, ErrorHandler);
Thus, your AsyncRelayCommand would be able to invoke canExecute delegate and will get the actual results.
Or you can leave CanExecute as the property, but when you create AsyncRelayCommand, wrap it to the lambda expression like this
MyAsyncCommand = new AsyncRelayCommand(ExecuteAsync, () => CanExecute, ErrorHandler);
To apply fallback logic to your CanExecute for AsyncRelayCommand you can change the code in the following way:
have an instance variable of type Func<bool> called, let's say, _canExecute. Then assign it in the constructor with whatever value accepted as the argument Func<bool> canExecute even if it's null. Then in your public CanExecute(object param) just check if _canExecute is null, just return !IsExecuting as you're doing it now, if it's not null, then return whatever _canExecute return.
Related
I develop a WPF application, obviously, I use MVVM pattern. Without an external library (MvvmCross, MvvmLight, ...)
And I've tried to implement ICommand:
Option 1
public class Command : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action _action;
public Command1(Action action, Func<bool> canExecute)
{
_action = action;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action();
}
Option 2
public class Command : ICommand
{
private readonly Func<bool> _canExecute;
private readonly Action<object> _action;
public Command1(Action<object> action, Func<bool> canExecute)
{
_action = action;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action(parameter);
}
Option 3
...with some delegates
public class Command : ICommand
{
private readonly Func<object, bool> _canExecute;
private readonly Action<object> _execute;
public Command(Action<object> execute) => _execute = execute ?? throw new ArgumentNullException(nameof(execute));
public Command(Action execute)
: this((Action<object>)delegate { execute(); })
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
}
public Command(Action<object> execute, Func<object, bool> canExecute)
: this(execute) => _canExecute = canExecute ?? throw new ArgumentNullException(nameof(canExecute));
public Command(Action execute, Func<bool> canExecute)
: this(delegate
{
execute();
}, (object o) => canExecute())
{
if (execute == null)
{
throw new ArgumentNullException(nameof(execute));
}
if (canExecute == null)
{
throw new ArgumentNullException(nameof(canExecute));
}
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter) => _canExecute != null ? _canExecute(parameter) : true;
public void Execute(object parameter) => _execute(parameter);
}
In all cases:
public class MainViewModel : BaseViewModel
{
public ICommand MyCommand = new Command(() => MyVoid());
private void MyVoid()
{
// do something
}
}
public class MainViewModel : BaseViewModel
{
public ICommand MyCommand = new Command(MyVoid);
private void MyVoid()
{
// do something
}
}
I've a CS0201 error (Only assignment, call, increment, decrement, await, and new object expressions can be used as a statement).
I don't understand why.
In other projects, which use MVVM pattern (Xamarin.Forms, Xamarin, ...), I use Xamarin.Forms.Command or MvxCommand (MvvmCross) and it works...
I don't suggest to name the realying class Command because you may get naming conflict with some built-in classes.
The closest to your try code:
public ICommand MyCommand => new Command(parameter => { MyVoid(); });
or the same in "block" syntax
public ICommand MyCommand
{
get
{
return new Command(parameter => { MyVoid(); });
}
}
But it's wrong approach because it will create new Command instance each time the command was called. Give to Garbage Collector as less work as possible. Examples of the correct ways doing it you may find below.
You're developing another bicycle. :) Look here.
Here is copy-paste from my project. It's exactly the same as in above link.
public class RelayCommand : ICommand
{
private readonly Action<object> _execute;
private readonly Func<object, bool> _canExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter) => _canExecute == null || _canExecute(parameter);
public void Execute(object parameter) => _execute(parameter);
}
And the usage
<Button Contecnt="Click me!" Command="{Binding MyCommand}"/>
private ICommand _myCommand;
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
// do here the execution
}));
And with parameter (Binding for CommandParameter is available too)
<Button Contecnt="Click me!" Command="{Binding MyCommand}" CommandParameter="test value"/>
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
if (parameter is string p && p == "test value")
{
// do here the execution
}
}));
And finally, usage of optional CanExecute
public ICommand MyCommand => _myCommand ?? (_myCommand = new RelayCommand(parameter =>
{
// do here the execution
// don't put the same condition as in CanExecute here,
// it was already checked before execution has entered this block
},
parameter => (x > y) && (a + b > c) // any condition or just return a bool
));
if CanExecute returns false, the command will not be executed and Button or MenuItem becomes automatically disabled. Just test it.
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)
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);
}
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
I am using MVVM solution provided in my previous question
XAML
<ProgressBar x:Name="StatusProgressBar" IsIndeterminate="{Binding IsIndeterminate, Mode=OneWay}" Height="18" Width="120" VerticalAlignment="Center" Background="White" BorderBrush="#FF05438D" />
ViewModel
Notice here in DoExecuteGetIpCommand() method if i do same thing in code behind on content rendered event works correctly but in mvvm all codes fires at same time so progress bar update after all time consuming process.
So i want to set ProgressBar IsIndeterminate Property true while time consuming method is working after done finally set IsIndeterminate to false. any idea to this and why it is happening but working fine in code behind Content rendered event.
public class MainWindowViewModel : INotifyPropertyChanged
{
public bool _isIndeterminate;
private string _ipAdrress;
private bool _errorOccured;
public event PropertyChangedEventHandler PropertyChanged;
GetPublicIP getPublicIP = new GetPublicIP();
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
/*
public bool IpIndeterminate
{
get { return _isIndeterminate; }
set
{
_isIndeterminate = value;
OnPropertyChanged(nameof(IpIndeterminate));
}
}
*/
//OR
//IsIndeterminate here is problem
public bool IsIndeterminate => _isIndeterminate;
public string IpAddress => _ipAdrress;
public Brush IpForeground => _errorOccured ? new SolidColorBrush(Colors.IndianRed) : new SolidColorBrush(Colors.Black);
public FontWeight IpFontWeight => _errorOccured ? FontWeights.SemiBold : FontWeights.Normal;
public ICommand GetIpCommand
{
get { return new RelayCommand(param => DoExecuteGetIpCommand()); }
}
private async void DoExecuteGetIpCommand()
{
_isIndeterminate = true;
try
{
_errorOccured = false;
//_ipAdrress = await MyService.GetIpAddress();
_ipAdrress = await getPublicIP.GetIPAddressAsync();//time consuming method.
}
finally
{
//Commented this because progress bar immediately Is indeterminate go false.
//_isIndeterminate = false;
}
if (await getPublicIP.ExceptionOccursAsync() == true)
{
_errorOccured = true;
}
OnPropertyChanged(nameof(IsIndeterminate));
OnPropertyChanged(nameof(IpAddress));
OnPropertyChanged(nameof(IpForeground));
OnPropertyChanged(nameof(IpFontWeight));
}
}
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
/// <summary>
/// Creates a new command that can always execute.
/// </summary>
/// <param name="execute">The execution logic.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Creates a new command.
/// </summary>
/// <param name="execute">The execution logic.</param>
/// <param name="canExecute">The execution status logic.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute"); //NOTTOTRANS
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public void Execute(object parameter)
{
_execute(parameter);
}
#endregion // ICommand Members
}
You have to change your code like this: (as dymanoid said)
private async void DoExecuteGetIpCommand()
{
_isIndeterminate = true;
//Tell the UI that something changed...
OnPropertyChanged(nameof(IsIndeterminate));
try
{
_errorOccured = false;
_ipAdrress = await getPublicIP.GetIPAddressAsync();//time consuming method.
}
finally
{
_isIndeterminate = false;
}
if (await getPublicIP.ExceptionOccursAsync() == true)
{
_errorOccured = true;
}
OnPropertyChanged(nameof(IsIndeterminate));
OnPropertyChanged(nameof(IpAddress));
OnPropertyChanged(nameof(IpForeground));
OnPropertyChanged(nameof(IpFontWeight));
}
I have a simple button that uses a command when executed, this is all working fine but I would like to pass a text parameter when the button is clicked.
I think my XAML is ok, but I'm unsure how to edit my RelayCommand class to receive a parameter:
<Button x:Name="AddCommand" Content="Add"
Command="{Binding AddPhoneCommand}"
CommandParameter="{Binding Text, ElementName=txtAddPhone}" />
public class RelayCommand : ICommand
{
private readonly Action _handler;
private bool _isEnabled;
public RelayCommand(Action handler)
{
_handler = handler;
}
public bool IsEnabled
{
get { return _isEnabled; }
set
{
if (value != _isEnabled)
{
_isEnabled = value;
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
}
}
public bool CanExecute(object parameter)
{
return IsEnabled;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_handler();
}
}
Change Action to Action<T> so that it takes a parameter (probably just Action<object> is easiest).
private readonly Action<object> _handler;
And then simply pass it the parameter:
public void Execute(object parameter)
{
_handler(parameter);
}
You could just do
public ICommand AddPhoneCommand
{
get
{
return new Command<string>((x) =>
{
if(x != null) { AddPhone(x); }
};
}
}
Then, of course have your AddPhone:
public void AddPhone(string x)
{
//handle x
}
You can simply do this (no change to RelayCommand or ICommand required):
private RelayCommand _addPhoneCommand;
public RelayCommand AddPhoneCommand
{
get
{
if (_addPhoneCommand == null)
{
_addPhoneCommand = new RelayCommand(
(parameter) => AddPhone(parameter),
(parameter) => IsValidPhone(parameter)
);
}
return _addPhoneCommand;
}
}
public void AddPhone(object parameter)
{
var text = (string)parameter;
...
}
public void IsValidPhone(object parameter)
var text = (string)parameter;
...
}
Currenlty, I'm using as Below.
In xaml,
<Button Content="X" Width="33" Height="16" Padding="1,-2,1,0"
Command="{Binding ElementName=UserControlName, Path=DataContext.DenyCommand}"
<Button.CommandParameter>
<wpfext:UICommandParameter UICommandCallerCallback="{Binding ElementName=UserControlName, Path=UIDenyCallBackCommand}"/>
</Button.CommandParameter>
</Button>
In xaml.cs,
public UICommandCallerCallback UIDenyCallBackCommand
{
get;
private set;
}
public UserControlName()
{
this.UIDenyCallBackCommand = this.UIAccessDenyCallBack;
this.InitializeComponent();
}
public void UIAccessDenyCallBack(object commandParameter, object callbackData)
{
ShowADenyMsgBox();
}
private void ShowDenyMsgBox()
{
RightsDenied win = new RightsDenied(); //xaml window
win.Owner = GetImmediateWindow();
win.WindowStartupLocation = WindowStartupLocation.CenterScreen;
win.ShowDialog();
}
In ViewModel.cs,
internal ViewModel()
{
this.DenyCommand= new DenyCommand(this.AccessDeny);
}
public void AccessDeny(ICommandState commandState)
{
commandState.InvokeCallerCallback("AccessDenied");
}
public CommandCallback DenyCommand
{
get;
private set;
}
UICommandCallerCallback is declared as below.
public delegate void UICommandCallerCallback(object commandParameter, object callbackData);
CommandCallback class is as below.
public class CommandCallback:ICommand
{
private readonly Action<ICommandState> executeMethod;
private readonly Func<ICommandState, bool> canExecuteMethod;
public CommandCallback(Action<ICommandState> executeMethod)
: this(executeMethod, null)
{
}
public CommandCallback(Action<ICommandState> executeMethod, Func<ICommandState, bool> canExecuteMethod)
{
if (executeMethod == null)
{
throw new ArgumentNullException("executeMethod");
}
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public bool CanExecute(object parameter)
{
return this.canExecuteMethod != null ? this.canExecuteMethod((ICommandState)parameter) : true;
}
public void Execute(object parameter)
{
if (parameter == null)
{
throw new ArgumentNullException("parameter","CommandCallback parameter cannot be null");
}
if (!(parameter is ICommandState))
{
throw new ArgumentException("expects a parameter of type ICommandState","parameter");
}
ICommandState state = (ICommandState)parameter;
this.executeMethod.Invoke(state);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
}
It's working fine if it just to pop up the dialog box, but I want to wait for the result of the dialog and want to continue AccessDeny() function. For eg.
public void AccessDeny(ICommandState commandState)
{
1. processs
2. open xaml window and wait for the dialogresult. (i.e Yes No or Cancel)
3. Based on the result, continue processing.
}
What could be the best way to do this work flow? Please advise. Thanks.
Read through User Interaction Patterns in this documentation.