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.
Related
!!! SOLVED, THANK YOU VERY MUCH !!!
I'm writing my first MVVM application (in WPF C#). Because of that, I want to use commands instead "Click" event defined in a view. The command, which I want to induce is really simple, it should to create and open a view.
I have written RelayCommand class which inherits ICommand interface.
internal class RelayCommand : ICommand
{
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute ?? throw new ArgumentNullException("execute");
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
CommandManager.RequerySuggested += value;
}
remove
{
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
I write a method changing value of the field, which represent a view.
private bool openSoundsWindow;
private bool openChordsWindow;
public bool OpenSoundsWindow
{
get { return openSoundsWindow; }
set { openSoundsWindow = value; }
}
public bool OpenChordsWindow
{
get { return openChordsWindow; }
set { openChordsWindow = value; }
}
public void OpenSounds()
{
openSoundsWindow = true;
}
public void OpenChords()
{
OpenChordsWindow = true;
}
I wrote in view model class commands by RelayCommand and OnPropertyChanged event. View model class inherits INotifyPropertyChanged.
private MainModel model = new MainModel();
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool OpenSoundsWindow
{
get { return model.OpenSoundsWindow; }
set
{
model.OpenSoundsWindow = value;
OnPropertyChanged(nameof(OpenSoundsWindow));
}
}
private ICommand openSounds = null;
public ICommand OpenSounds
{
get
{
if (openSounds == null)
{
openChords = new RelayCommand(
(object o) =>
{
model.OpenSounds();
OnPropertyChanged(nameof(OpenSoundsWindow));
var newSoundsWindow = new Sounds();
newSoundsWindow.Show();
},
(object o) =>
{
return model.OpenSoundsWindow != null;
});
}
return openSounds;
}
}
I created instance of view model in view's xaml code.
xmlns:vm="clr-namespace:HearingTeacher.ViewModels"
d:DataContext="{d:DesignInstance Type=vm:MainViewModel}"
<Window.Resources>
<vm:MainViewModel x:Key="mainViewModel" />
</Window.Resources>
I binded property command for buttons with created commands in view model.
<Button Grid.Row="0" Content="Sounds" Command="{Binding Path=OpenSounds,
UpdateSourceTrigger=PropertyChanged}" />
Compiler doesn't throw any exception, and .NET starts an application correctly, but commands doesn't work.
I would like to set a button to disabled and activate it at runtime when another method sets the _canExecute field to true.
Unfortunately I don't know how to trigger this event and update the view.
The CommandHandler class already implements RaiseCanExecuteChanged. But it's unclear how to use it.
View
<Button Content="Button" Command="{Binding ClickCommand}" />
ViewModel
public ViewModel(){
_canExecute = false;
}
private bool _canExecute;
private ICommand _clickCommand;
public ICommand ClickCommand => _clickCommand ?? (_clickCommand = new CommandHandler(MyAction, _canExecute));
private void MyAction()
{
// Do something after pressing the button
}
private void SomeOtherAction(){
// If all expectations are satisfied, the button should be enabled.
// But how does it trigger the View to update!?
_canExecute = true;
}
CommandHandler
public class CommandHandler : ICommand
{
private Action _action;
private bool _canExecute;
public CommandHandler(Action action, bool canExecute)
{
_action = action;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return _canExecute;
}
public event EventHandler CanExecuteChanged;
public void Execute(object parameter)
{
_action();
}
public void RaiseCanExecuteChanged()
{
CanExecuteChanged?.Invoke(this, new EventArgs());
}
}
You may add a method like this to your CommandHandler class:
public void SetCanExecute(bool canExecute)
{
_canExecute = canExecute;
RaiseCanExecuteChanged();
}
Then change the type of the ClickCommand property to CommandHandler
public CommandHandler ClickCommand => ...
and just call
ClickCommand.SetCanExecute(true);
this question is just for understanding. I'm fine with my IDE telling me about errors that aren't there, as long as the application works.
I'm developing a c# WPF application using the MVVM pattern; data and CommandBindings.
However, I've noticed that when I use a binding to bind to a Command, the command doesn't execute, however, I don't get any errors shown in either the IDE, or debug output.
For example:
Command="{Binding MyCommand}"
<!-- Or -->
Command="{Binding cmd:Commands.MyCommand}"
However, just writing
Command="cmd:Command.MyCommand"
works just fine, although the XAML-editor shows me an error, saying that the command cannot be found.
Why is this so?
You need to bind to a property of type ICommand.
This property will implement a RelayCommand with your function.
The default implementation of the RelayCommand is as follows:
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
private readonly Action methodToExecute;
private readonly Func<bool> canExecuteEvaluator;
public RelayCommand(Action methodToExecute, Func<bool> canExecuteEvaluator)
{
this.methodToExecute = methodToExecute;
this.canExecuteEvaluator = canExecuteEvaluator;
}
public RelayCommand(Action methodToExecute)
: this(methodToExecute, null)
{
}
public bool CanExecute(object parameter)
{
if (canExecuteEvaluator == null)
{
return true;
}
bool result = canExecuteEvaluator.Invoke();
return result;
}
public void Execute(object parameter)
{
methodToExecute.Invoke();
}
}
In the ViewModel you need to implement the property of type ICommand with your OnClick-function:
public ICommand MyCommand
{
get
{
return new RelayCommand(() =>
{
doSomething();
});
}
}
Now you are able to bind the Button-Command of your view to the ICommand dynamically at runtime:
Command="{Binding MyCommand}"
Besides, keep in mind that Command="cmd:Command.MyCommand" is a static implementation.
Here is relayCommand that I use:
public sealed class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _action;
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion Fields
#region Constructors
public RelayCommand(Action<object> action)
{
if (action != null)
_action = action;
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion Constructors
#region ICommand Members
public void Execute(object parameter)
{
if (_execute != null)
_execute(parameter);
else
{
_action(parameter ?? "Command parameter is null!");
}
}
[DebuggerStepThrough]
public bool CanExecute(object parameter)
{
return _canExecute == null || _canExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
#endregion
}
What you need is to implement in viewModel command and you will be able to bind it like this:
Command="{Binding MyCommand}"
Edit
As for me, I prefer to use two libraries - interactions and Interactivity. With their help it is easy to bind all events to viewModel. For example:
xaml:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:CallMethodAction TargetObject="{Binding}" MethodName="OnClick"/>
</i:EventTrigger>
</i:Interaction.Triggers>
and viewModel:
public void OnClick(object sender, RoutedEventArgs e)
{
//your code
}
I try to use commands with the MVVM - Pattern and I don't know how to "bind" a command to a special event, e.g. MouseUp or MouseEnter. How to do this?
First you should define ICommnad property in your ViewModel.
public ICommand MouseUpCommand
{
get
{
if (this.mouseUpCommand == null)
{
this.mouseUpCommand = new RelayCommand(this.OnMouseUp);
}
return this.mouseUpCommand;
}
}
private void OnMouseUp()
{
// Handle MouseUp event.
}
You can find lot of ICommand implementations. One of them:
public class RelayCommand : ICommand
{
public RelayCommand(Action<object> execute)
{
this._execute = execute;
...
}
...
public void Execute(object parameter)
{
_execute(parameter);
}
}
Then add event trigger within which invoke your Command:
<i:EventTrigger EventName="MouseUp">
<i:InvokeCommandAction Command="{Binding MouseUpCommand}"/>
</i:EventTrigger>
Read the EventToCommand at the following page, please
Look at WPF Binding UI events to commands in ViewModel.
For this you need System.Windows.Interactivity.dll which you can get from Nuget
Completing #AnatoliiG post here's an implementation and a sample usage of the RelayCommand class.
Code:
public class RelayCommand : ICommand
{
#region Fields
readonly Action<object> _execute;
readonly Predicate<object> _canExecute;
#endregion // Fields
#region Constructors
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
#endregion // Constructors
#region ICommand Members
[DebuggerStepThrough]
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
}
Usage:
// To use this class within your viewmodel class:
RelayCommand _myCommand;
public ICommand MyCommand
{
get
{
if (_myCommand == null)
{
_myCommand = new RelayCommand(p => this.DoMyCommand(p),
p => this.CanDoMyCommand(p) );
}
return _myCommand;
}
}
My query is how can I create a class behaving same as RelayCommand but without implementing ICommand for my MVVM application? Any suggestions are much appreciated.
My RelayCommand [which implements ICommand] is as below:
public class RelayCommand:ICommand
{
private readonly Action<object> m_Execute;
private readonly Predicate<object> m_CanExecute;
public RelayCommand(Action<object> exec) : this(exec, null) { }
public RelayCommand(Action<object> exec, Predicate<object> canExec)
{
if (exec == null)
throw new ArgumentNullException("exec");
m_Execute = exec;
m_CanExecute = canExec;
}
#region ICommand Members
public bool CanExecute(object parameter)
{
if (parameter == null)
return true;
else
return m_CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add
{
if (m_CanExecute != null)
CommandManager.RequerySuggested += value;
}
remove
{
if (m_CanExecute != null)
CommandManager.RequerySuggested -= value;
}
}
public void Execute(object parameter)
{
m_Execute(parameter);
}
#endregion
}