How to prevent CommandManager from invoking CanExecute whenever there is UI interaction? - c#

Our UI currently has a lot of controls that are bound to commands with some complex CanExecutes. The problem we are facing is that whenever CommandManager determines that the UI needs to be re-evaulated, all commands run their CanExecute, which in turn, causes quite a performance hit for specific scenarios.
Reading this post: How does CommandManager.RequerySuggested work?
It seems that the CommandManager will re-evaulate on simple key down, mouse move events, etc. Is there a way to prevent this from happening, and instead, have the command manager re-evaluate when manually invoked?

A solution might be to implement a simpler version of the RelayCommand class that simply stores the event handlers itself and exposes a public method to fire them when appropriate:
public class RelayCommand : ICommand
{
public event EventHandler CanExecuteChanged;
// Further ICommand implementation omitted...
public void Invalidate()
{
var handler = this.CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
}
}
You then call the following in your viewModel to re-evaluate the command:
fooCommand.Invalidate();
Of course, this leaves you with the opposite problem that you now have to manually re-evaluate all commands...
Edit
To elaborate on the comments, most RelayCommand's implement the CanExecuteChanged event like this:
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
When the UI subscribes to the command's CanExecuteChanged event it is actually being indirectly subscribed to the CommandManager.RequerySuggested event which is why your CanExecute method is being called every time the CommandManager suggest a re-query.
The simpler RelayCommand I have suggested avoids this problem by not subscribing to the CommandManager.RequerySuggested event.

Related

Raise an event before anybody has subscribed to it

I would like to raise an event and notify existing subscribers if any exist. But I also expect new subscribers to be notified of all events raised so far as soon as they subscribe. Is this possible out of the box or do I need to implement that functionality myself? Right now my code look like this:
public delegate void GridCompiled(int gridsize);
public event GridCompiled OnGridCompiled;
ctor(){
if (OnGridCompiled != null)
OnGridCompiled(gridsize);
}
If event has 0 subscribers it won't be raised and it also won't be raised for subscribers that subscribe after event has been raised.
In case I need to implement that myself, what are my options?
There is no tracking of raising events, so you would have to implement the functionality yourself. You would need a list to store your previous event arguments in order and execute the related events when a new event listener is added:
class Example {
private readonly List<GridCompiledEventArgs> m_previousEventArgs = new List<EventArgs>();
private EventHandler<GridCompiledEventArgs> m_gridCompiled;
public event EventHandler<GridCompiledEventArgs> GridCompiled {
add {
//Raise previous events for the caller
foreach(GridCompiledEventArgs e in m_previousEventArgs) {
value(this, e);
}
//Add the event handler
m_gridCompiled += value;
}
remove {
m_gridCompiled -= value;
}
}
protected virtual void OnGridCompiled(GridCompiledEventArgs e) {
if (m_gridCompiled != null) {
m_gridCompiled(this, e);
}
m_previousEventArgs.Add(e);
}
}
There are two things you have consider for this solution. If you want to adress them, your solution will become more complex:
If GridCompiledEventArgs can be changed by an event handler (e.g. to return a status to the caller), the event args will be stored in the previous event list with those changes. Also, if an event handler keeps a reference to the event args they might even change it later. If you don't want that, you have to store a copy in m_previousEventArgs and create another copy before you raise the "historic" event.
It is best practice to allow derived classes to override the OnGridCompiled method instead of handling the event or changing its behavior. If a derived class changes OnGridCompiled to intercept the event and not raise it in certain cases, this behavior will not always apply for the "historic" event, since it is raised without OnGridCompiled (which might be just the behavior you want). If you want to change that you have to implement a solution that goes through OnGridCompiled. If this is an option for you, you can avoid this problem by making the class sealed and the OnGridCompiled method private instead of protected virtual.

Why can't I raise or call event if I have overloaded add and remove in C#

While copying code for RelayCommand from Josh Smith article I copied following code
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
Then after reading this answer on SO I also copied in my class following code from DelegateCommand class of Prism.
protected void NotifyCanExecuteChanged()
{
if (CanExecuteChanged != null)
{
CanExecuteChanged(this, EventArgs.Empty);
}
}
But his gives me an error in NotifyCanExecuteChanged method
The event 'CanExecuteChanged' can only appear on the left hand side of += or -=
This error is not coming if I remove the add and remove overload from event. Can someone please help me understand the reason behind this?
With a field-like event (which is the name for the simple form without add/remove, then when you do if(CanExecuteChanged != null) or CanExecuteChanged(this, ...), the CanExecuteChanged is referring to the backing field, which is a delegate field of type EventHandler. You can invoke a delegate field. However, that is not the case in your example, because there is no obvious thing to invoke. There certainly isn't a local field, and the forwarded event (CommandManaged.RequerySuggested) does not intrinsically expose any "invoke" capability.
Basically, for that to work you would need access to an invoke mechanism. Most commonly, I would expect that to take the form of:
CommandManager.OnRequerySuggested();
but if there is a method that invokes this event (and there doesn't need to be), it could be called anything.
(the On* is a common pattern for a "raise this event" API, doubly-so if it is polymorphic)
I think you want CommandManager.InvalidateRequerySuggested. It forces the RequerySuggested event to be raised.
It seems like your class inherits from the class where event is declared. Event can be directly raised only in the base class, not in inherited class.
If you want to raise it in inherited class, write the following method in your base and call it from inherited class:
protected void RaiseMyEvent()
{
if (MyEvent != null)
{
MuEvent(this, args)
}
}

CanExecute Logic for DelegateCommand

Update: The focus became MVVM instead of the actual question so I'm updating it.
I'm having a problem with CanExecute for DelegateCommand. It doesn't update before I call RaiseCanExecuteChanged, is this the desired behavior?
I uploaded a simple sample project reproducing this problem here : http://dl.dropbox.com/u/39657172/DelegateCommandProblem.zip
The problem is this, I have two Buttons like this. One is Binding Command to a RelayCommand implementation and the other is binding to the Prism implementation of DelegateCommand
<Button Command="{Binding DelegateSaveCommand}"/>
<Button Command="{Binding RelaySaveCommand}"/>
The ViewModel ICommands
DelegateSaveCommand = new DelegateCommand(Save, CanSaveDelegate);
RelaySaveCommand = new RelayCommand(param => Save(), param => CanSaveRelay);
and the CanExecute method/predicate
public bool CanSaveDelegate()
{
return HasChanges;
}
public bool CanSaveRelay
{
get { return HasChanges; }
}
Both are using the property HasChanges. When HasChanges is updated, only the CanSaveRelay updates. Is this the way it's meant to be?
As it already was mentioned, this is intended behavior of DelagateCommand, not a bug.
DelegateCommand doesn't raise CanExecuteChanged event automatically, you have to raise that event manually by calling RaiseCanExecuteChanged when appropriate. Whereas RelayCommand relays on CommandManager.RequerySuggested event for that. This event is raised every time the user clicks somewhere or presses a button.
For situations when it is not very convenient or there is no appropriate place for calling RaiseCanExecuteChanged (like in your scenario you have to subscribe to PropertyChanged event on the model, etc) I have created the following simple wrapper that ensures that the CanExecute method of the wrapped command is executed automatically on CommandManager.RequerySuggested event:
public class AutoCanExecuteCommandWrapper : ICommand
{
public ICommand WrappedCommand { get; private set; }
public AutoCanExecuteCommandWrapper(ICommand wrappedCommand)
{
if (wrappedCommand == null)
{
throw new ArgumentNullException("wrappedCommand");
}
WrappedCommand = wrappedCommand;
}
public void Execute(object parameter)
{
WrappedCommand.Execute(parameter);
}
public bool CanExecute(object parameter)
{
return WrappedCommand.CanExecute(parameter);
}
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
}
You can use it like this:
DelegateSaveCommand = new AutoCanExecuteCommandWrapper(new DelegateCommand(Save, CanSaveDelegate));
If you want to stick to DelegateCommand you can use ObservesCanExecute:
DelegateSaveCommand = new DelegateCommand(Save, CanSaveDelegate).ObservesCanExecute(CanSaveDelegate);
Note that there is also ObservesProperty available if you are using a property for your CanExecute check. But then your property has to call NotifyPropertyChanged.
There is a bug in the DelegateCommand provided by Prism which doesn't raise the CanExecute event. I beat my head against the wall for a day until I dove into the DelegateCommand class provided by the Prism framework. I don't have the code with me, but I can post my resolution in a bit.
The alternative is to use one of the other RelayCommand frameworks out there.
Edit
Rather than reposting the code, there are other SO questions that provide resolutions:
WPF-Prism CanExecute method not being called
And Kent B. has a good article: MVVM Infrastructure: DelegateCommand

Serialising my class is failing because of an eventhandler

I wasn't expecting to come across this error. I imagine I'm doing something wrong somewhere else.
I have an MVVM application.
My model can serialise its self using a BinaryFormatter. This was working fine.
Today I added in an event handler to my model, and the viewmodel that contains the model subscribes to this event.
Now when I try and serialise the model I get an error because my viewmodel isn't serialisable (by design).
I am sure it's down to the subscription to the event, because I've removed the subscription (and only that) and serialisation works again.
I can't apply the [NonSerialized] attribute to the handler because it's not a field.
It there a way around this issue?
you can do this:
[field:NonSerialized]
public event EventHandler MyEvent;
You can make the event a field like this:
[NonSerialized]
private EventHandler _eventHandler;
public event EventHandler MyEvent
{
add { _eventHandler += value; }
remove { _eventHandler -= value; }
}
I don't know how useful this is, but...
...extending what Pieter mentioned, you can also have mutliple delegate handlers wrapped into the same event, so you could (theoretically) make your event, in effect, both serializable and non-serializable by doing something like this:
[NonSerialized]
private EventHandler _nonSerializableeventHandler;
private EventHandler _eventHandler;
public event EventHandler MyEvent
{
add
{
if (value.Method.DeclaringType.IsSerializable)
_eventHandler += value;
else
_nonSerializableeventHandler += value;
}
remove
{
{
if (value.Method.DeclaringType.IsSerializable)
_eventHandler -= value;
else
_nonSerializableeventHandler -= value;
}
}
}

Is it possible to "chain" EventHandlers in c#?

Is is possible to delegate events from inner object instance to corrent object's event handlers with a syntax like this:
public class MyControl {
public event EventHandler Finish;
private Wizard wizard;
public MyControl( Wizard wizard ) {
this.wizard = wizard;
// some other initialization going on here...
// THIS is what I want to do to chain events
this.wizard.Finish += Finish;
}
}
The motivation for the above structure is that I have many wizard-like UI flows and wanted to separate the Back, Forward & Cancel handling to a single class to respect Open Closed Principle and Single Responsibility Principle in my design.
Adding a method OnFinish and doing the normal checking there is always possible but on case there are lot's of nested events, it's going to end up with lot's of boilerplate code.
Two options. First:
public event EventHandler Finish
{
add { wizard.Finish += value; }
remove { wizard.Finish -= value; }
}
Second, as you mentioned:
public event EventHandler Finish;
wizard.Finish += WizardFinished;
private void WizardFinished(object sender, EventArgs e)
{
EventHandler handler = Finish;
if (handler != null)
{
handler(this, e);
}
}
The benefit of the second form is that the source of the event then appears to be the intermediate class, not the wizard - which is reasonable as that's what the handlers have subscribed to.

Categories

Resources