How can I raise CanExecuteChanged() programmatically? - c#

I need to programmatically raise CanExecuteChanged(). The ICommand class looks like this:
public class PlayCommand : ICommand
{
private readonly TpViewModel _tpViewModel;
private bool IsBusy;
public PlayCommand(TpViewModel tpViewModel)
{
_tpViewModel = tpViewModel;
}
public bool CanExecute(object parameter)
{
return !IsBusy;
}
public async void Execute(object parameter)
{
if (IsBusy)
return;
await Play();
}
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
private async Task Play()
{
IsBusy = true;
Console.WriteLine("Play")
IsBusy = false;
}
}
The Play button should be enabled as soon as the Play() method is completed. However, there seems to be a delay of a second or two before the button gets enabled.
I have tried CommandManager.InvalidateRequerySuggested() but it does not work.

You could use CommandManager.InvalidateRequerySuggested(), though this is fairly expensive. However, you can just extend your class with standard event patterns.
Note : This is only an example based on your code and not intended to be the worlds best-of-class ICommand implementation, modify it as you need
private EventHandler _canExecuteChanged;
public event EventHandler CanExecuteChanged
{
add
{
_canExecuteChanged += value;
CommandManager.RequerySuggested += value;
}
remove
{
_canExecuteChanged -= value;
CommandManager.RequerySuggested -= value;
}
}
public void RaiseCanExecuteChanged()
{
if (!IsBusy)
OnCanExecuteChanged();
}
protected virtual void OnCanExecuteChanged()
=> _canExecuteChanged?.Invoke(this, EventArgs.Empty);

Related

The event 'Command.CanExecuteChanged' can only appear on the left hand side of += or -=

Am I missing something in my Command-ViewModel?
public class Command : ICommand
{
public Command(Action execute, Func<bool> canExecute = null)
{
this.execute = execute;
this.canExecute = canExecute ?? new Func<bool>(() => true);
}
private Action execute;
private Func<bool> canExecute;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter)
{
return canExecute.Invoke();
}
public void Execute(object parameter)
{
execute?.Invoke();
}
}
Everytime I want to use CanExecuteChanged in my MainViewModel with this line of code ((Command)MyCommand).CanExecuteChanged(); it gives me this error The event 'Command.CanExecuteChanged' can only appear on the left hand side of += or -=
CanExecuteChanged is an event. You can only use it like this:
YourCommand.CanExecuteChanged += (sender, args) =>
{
// Do your magic here
};
public event EventHandler CanExecuteChanged;
is syntactic sugar. What the compiler actually generates when you put this in is something like*
private EventHandler _CanExecuteChanged;
public event EventHandler CanExecuteChanged
{
add { _CanExecuteChanged += value; }
remove { _CanExecuteChanged -= value; }
}
So the CanExecuteChange that's publicly exposed isn't the actual field but only something you can add and remove handlers with.
Related note: The backing field being private is also the reason for the normal pattern of having a protected OnXXXX() method in the base class.
public event EventHandler CanExecuteChanged;
protected void OnCanExecuteChanged(EventArgs args)
{
CanExecuteChanged?.Invoke(this, args);
}
*Note the "like" part; there's some null checking that's needed for proper add and remove as well.
To answer your question yes you are missing something from your code. I can't tell if you are using the Command class supplied by Xamarin.Forms but if you aren't then you really should be!
Ultimately you cannot interact with an event outside of the class that it belongs to apart from subscribing for the event notification which is what the 'The event 'Command.CanExecuteChanged' can only appear on the left hand side of += or -=' is telling you. To subscribe you implement something like the following:
MyCommand.CanExecuteChanged += (sender, e) =>
{
// Your code to react to the event goes here.
};
What you can do though and this is where you need to be using the Xamarin.Forms Command class (or you can implement something similar yourself in your Command class). Is call ChangeCanExecute e.g.
((Command)MyCommand).ChangeCanExecute();
This will then trigger the event to be fired and thus updating any UI controls that are bound to that command.
I deleted my Command-Class and now I am using the Xamarin.Forms Command Class which is making this a lot easier because now I can just use this delicious short line of code: ((Command)MyCommand).ChangeCanExecute(); to fire the event.
Thanks to all of you guys.
Just a another approach!
public sealed class Command : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged
{
add => CommandManager.RequerySuggested += value;
remove => CommandManager.RequerySuggested -= value;
}
public RelayCommand(Predicate<object> CanExecute, Action<object> Execute)
{
_canExecute = CanExecute;
_execute = Execute;
}
public bool CanExecute(object parameter)
{
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
}
Then just let the Command Manager handle updating your .CanExecuteChanged problem :)
If you need to force the Command Manager to "look again" call CommandManager.InvalidateRequerySuggested();
Here is a little more info on the Command Manager
How does CommandManager.RequerySuggested work?

The RelayCommand for enable/disable a button don't work like expected

The case is that I try to disable a button in the window form when it was clicked and after some time (some seconds) it should be enabled again.
But this didn't work. After a click on the button the command set the enabled to false and after some seconds the command set it back to true (I tested it, the order is right and it set it to true again) but the button is still not enabled on the window form.
For that case I use a RelayCommmand. The RelayCommand is a standard class you find on Internet and will be shown in the end.
To organise the command I wrote a class called Testclass:
class Testclass
{
private bool _testValueCanExecute;
public bool TestValueCanExecute
{
get { return _testValueCanExecute; }
set
{
_testValueCanExecute = value;
OnPropertyChanged();
}
}
public ICommand TestValueCommand { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
public Testclass()
{
TestValueCommand = new RelayCommand(TestMethod, param => _testValueCanExecute);
TestValueCanExecute = true;
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private async void TestMethod(object obj)
{
TestValueCanExecute = false;
await Task.Delay(3000);
TestValueCanExecute = true;
}
}
In the XAML File I added a button as followed:
<Button x:Name="TestButton" Command="{Binding TestValueCommand}" Content="Test Button" HorizontalAlignment="Left" Margin="149,96,0,0" VerticalAlignment="Top" Width="75"/>
The MainWindow code looks as followed:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new Testclass();
}
}
So the RelayCommand use the TestMethod method set the command enable variable to false, wait 3 seconds and set them back to true. But as I wrote above the button on the window form still not enabled.
It would be nice to understand what happens here and how I can solve this.
Update:
I use the following Code for the RelayCommand:
public class RelayCommand : ICommand
{
private Action<object> execute;
private 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)
{
this.execute = execute;
this.canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
return this.canExecute == null || this.canExecute(parameter);
}
public void Execute(object parameter)
{
this.execute(parameter);
}
}
The RelayCommand is a standard class you find on Internet ...
There is no such thing as a "standard class you find on Internet". In fact there are several different implementations of the RelayCommand available "on the Internet".
A good implementation should contain a method for raising the CanExecuteChanged event. MvvmLight's implementation has a RaiseCanExecuteChanged() method that does this. You need to call this one to "refresh" the status of the command:
private async void TestMethod(object obj)
{
RelayCommand cmd = TestValueCommand as RelayCommand;
TestValueCanExecute = false;
cmd.RaiseCanExecuteChanged();
await Task.Delay(3000);
TestValueCanExecute = true;
cmd.RaiseCanExecuteChanged();
}
The event is not raised automatically when you set the TestValueCanExecute property and raise the PropertyChanged event for the view model.
Edit: Your implementation doesn't have any RaiseCanExecuteChanged() method. Add one to your RelayCommand class and call it as per above:
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
I strongly recommend using existing frameworks, instead of inveting the wheel once again.
Take a look at ReactiveUI ReactiveCommand
In your case, it would do all the work by itself:
TestValueCommand = ReactiveCommand.CreateFromTask(async () => await Task.Delay(500));
You bind that command to a button in xaml and the button is disabled until command is done.
You can easily add another condition for disabling the command, and then, binding will disable button

Async ICommand implementation

I'm facing a strange behavior by my asny ICommand implementation when I tried to disable the command while it's executing by default (even when no CanExecute predicate was passed to it).
public bool CanExecute(object parameter)
{
if (CanExecutePredicate == null)
{
return !mIsExecuting;
}
return !mIsExecuting && CanExecutePredicate(parameter);
}
public async void Execute(object parameter)
{
mIsExecuting = true;
await ExecuteAsync(parameter);
mIsExecuting = false;
}
I tried to introduce a private bool, which I set to true just before executing and to false afterwards. When execution is finished the bool is set, but CanExecute is only called after I click a mousebutton or move the mouse or w/e.
Now I tried to call
CanExecute(null);
after
mIsExecuting = false;
but that doesn't help neither. I dont know what I'm missing.
Thanks for your help
EDIT:
To clarify I add the constructors for this class aswell:
public AsyncRelayCommand(Func<object, Task> execute)
: this(execute, null)
{
}
public AsyncRelayCommand(Func<object, Task> asyncExecute,
Predicate<object> canExecutePredicate)
{
AsyncExecute = asyncExecute;
CanExecutePredicate = canExecutePredicate;
}
protected virtual async Task ExecuteAsync(object parameter)
{
await AsyncExecute(parameter);
}
In async scenarios, WPF tends not to know when to check CanExecute, that's why you have the "CanExecuteChanged" event in the Icommand interface.
You should have something like this in your command implementation:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
With the code above you can now do this:
public async void Execute(object parameter)
{
mIsExecuting = true;
RaiseCanExecuteChanged ( ); // Not necessary if Execute is not called locally
await ExecuteAsync(parameter);
mIsExecuting = false;
RaiseCanExecuteChanged ( );
}
This will tell WPF you want to refresh the CanExecute state of command.

Missing understanding of MVVM

Trying to get to grips with MVVM in WPF c#.
I am a slow learner...
I have my MainWindow.xaml.
This is the markup in question:
<Viewbox x:Name="vbxucProductCostMenu" Stretch="{Binding Stretch}" StretchDirection="Both">
//a user control
</Viewbox>
<Button Command="{Binding MagnifyMinimiseCommand}" CommandParameter="UniformToFill">
<Image Source="Images/button_plus_green.png"/>
</Button>
Part of my MainWindow.cs
public MainWindow()
{
InitializeComponent();
this.DataContext = new MagnifyMinimise();
}
My Viewmodel?
public class MagnifyMinimise : INotifyPropertyChanged
{
public MagnifyMinimise()
{
Minimise();
}
MagnifyMinimiseCommand _magnifyMinimiseCommand = new MagnifyMinimiseCommand();
public MagnifyMinimiseCommand MagnifyMinimiseCommand
{
get { return _magnifyMinimiseCommand; }
}
public event PropertyChangedEventHandler PropertyChanged;
public void Magnify()
{
Stretch = "UniformToFill";
}
public void Minimise()
{
Stretch = "None";
}
public string Stretch { get; set; }
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
my 'ICommand' class:
public class MagnifyMinimiseCommand : ICommand
{
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
//how do I set the property of stretch here!!
}
}
When I run this it starts up minimized which is good.
I then want to 'maximize' the viewbox when the user clicks that button.
By setting the breakpoint in the 'Execute' method i can see that it is being invoked and the 'parameter' is set to 'UniformToFill'.
But how do I get the Stretch property to 'read' that?
ADDITONAL:
I have changed it all to this (which does not work):
public class MagnifyMinimise : INotifyPropertyChanged
{
private ActionCommand<string> _magnifyMinimiseCommand;
public MagnifyMinimise()
{
Minimise();
_magnifyMinimiseCommand = new ActionCommand<string>(Magnify);
}
private void Magnify(string stretch)
{
// now the viewmodel handles it instead of the action
Stretch = stretch;
}
public event PropertyChangedEventHandler PropertyChanged;
public void Magnify()
{
Stretch = "UniformToFill";
}
public void Minimise()
{
Stretch = "None";
}
public string Stretch { get; set; }
private void OnPropertyChanged(string property)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
public class ActionCommand<T> : ICommand where T : class
{
private readonly Action<T> mAction;
public ActionCommand(Action<T> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
<Button Command="{Binding ActionCommand}" CommandParameter="UniformToFill">
<Image Source="Images/button_plus_green.png" />
</Button>
The easiest way is, like suggested by #Default, to use a RelayCommand. There is one (or an alternative) provided in every major MVVM framework (Prism, MVVM Light, Caliburn.Micro, ...).
That said, if you wanted to solve the issue with your vanilla implementation of a command, you'd just have to pass a reference to the viewmodel in the constructor:
public class MagnifyMinimiseCommand : ICommand
{
public MagnifyMinimiseCommand(MagnifyMinimise viewModel)
{
this.ViewModel = viewModel;
}
protected MagnifyMinimise ViewModel { get; }
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
this.ViewModel.IsMagnifying = "...";
}
}
You need to invoke PropertyChanged for Stretch. That's how i would do it:
private string _stretch;
public string Stretch
{
get { return _stretch; }
set {_stretch = value; OnPropertyChanged("Stretch"); }
}
Also you might want to consider using RelayCommand or DelegateCommand
Another sidenote: In MVVM try not to write any code in the view's code behind. Use App.xaml.cs for setting the DataContext of the view.
EDIT: To answer your question, i would create a DelegateCommand class like this:
class DelegateCommand : ICommand
{
private readonly Action<Object> _execute;
private readonly Func<Object, Boolean> _canExecute;
public DelegateCommand(Action<Object> execute) : this(null, execute) { }
public DelegateCommand(Func<Object, Boolean> canExecute, Action<Object> execute)
{
if (execute == null)
{
throw new ArgumentNullException("execute");
}
_execute = execute;
_canExecute = canExecute;
}
public event EventHandler CanExecuteChanged;
public Boolean CanExecute(Object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
public void Execute(Object parameter)
{
if (!CanExecute(parameter))
{
throw new InvalidOperationException("Command execution is disabled.");
}
_execute(parameter);
}
public void OnCanExecuteChanged()
{
if (CanExecuteChanged != null)
CanExecuteChanged(this, EventArgs.Empty);
}
}
and use it like this in your viewmodel:
public DelegateCommand MagnifyMinimiseCommand { get; private set; }
.....
MagnifyMinimiseCommand = new DelegateCommand(param => { Stretch = UniformToFill; });
then
<Button Command="{Binding MagnifyMinimiseCommand}">
<Image Source="Images/button_plus_green.png"/>
</Button>
Instead of using such a specific type of Command, you can create a more generic command and allow the viewmodel to handle the action itself. So create a generic type of ICommand:
public class ActionCommand<T> : ICommand where T : class
{
private readonly Action<T> mAction;
public ActionCommand(Action<T> action)
{
mAction = action;
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
mAction(parameter as T);
}
public event EventHandler CanExecuteChanged;
}
and then create it like this:
private ActionCommand<string> _magnifyMinimiseCommand;
public MagnifyMinimise()
{
_magnifyMinimiseCommand = new ActionCommand<string>(Magnify);
....
}
private void Magnify(string stretch)
{
// now the viewmodel handles it instead of the action
Stretch = stretch;
}
Also, as a common practice I usually expose the properties to the View as it's interfaces, so the MagnifyMinimiseCommand would for instance be an ICommand instead (you can still use the field to access the ActionCommands stuff).

Unit Test Event Handler

I got this event handle and how can I do unit test for this
public class MyLearningEvent
{
private event EventHandler _Closed;
public event EventHandler Closed
{
add
{
_Closed -= value;
_Closed += value;
}
remove
{
_Closed -= value;
}
}
public void OnClosed()
{
if (_Closed != null) _Closed(this, EventArgs.Empty);
}
}
Just modified code so that much clear
Thanks
You should not unit test that code. It's a feature which is built into .NET. Your event handling is flawed imho.
add
{
_Closed -= value;
_Closed += value;
}
Probably means that your invokers don't keep track on if they have subscribed or not. That can lead to memory leaks: http://blog.naviso.fr/wordpress/wp-content/uploads/2011/11/MemoryLeaks-English.jpg
A more robust (and thread safe implementation) is:
public class MyLearningEvent
{
public event EventHandler Closed = delegate {};
public void TriggerClosed()
{
Closed(this, EventArgs.Empty);
}
}
But you should not let anyone else trigger that event (make the TriggerClosed private/protected)
Try this method. This assumes MyClass.Close() raises the MyClass.Closed event.
public void ClosedEventHandlerIsNotCalledAfterBeingRemoved()
{
MyLearningEvent Target = new MyLearningEvent();
EventHandler Target_Closed = new EventHandler((sender, e) => { Assert.Fail("Closed EventHandler was raised after being removed."); });
Target.Closed += Target_Closed;
Target.Closed -= Target_Closed;
Target.OnClosed();
}

Categories

Resources