4

I am using a couple of Buttons bound to RelayCommands initialized with CanExecute delegates.

RelayCommand DeleteCommand;
bool CanDelete()
{
    return BoolProp1 && BoolProp2;
}

...

DeleteCommand = new RelayCommand(Delete, CanDelete);

BoolProp1 and BoolProp2 are regular properties with setters correctly raising PropertyChanged, but as we all know, this is not enough to make SL reevaluate CanExecute on commands. That's why i also call Delete.RaiseCanExecuteChanged() in both setters.

All this works fine (buttons are disabled and enabled properly) up to some point, where is all stops. At that point, calling Delete.RaiseCanExecuteChanged() no longer fires my breakpoints in CanDelete() and buttons forever stay the way they were.

I spend 2 hours trying to isolate the exact cause with no effect. I suspect multiple RaiseCanExecuteChanged() calls during single "binding iteration" somehow break the mechanism.

Any hints? I'm already considering using an additional IsExecutable field refreshed through INotifyPropertyChanged...

UPDATE

RelayCommand is actually GalaSoft.MvvmLight.Command.RelayCommand from MVVM Light Toolkit. ILSpy shows a pretty trivial implementation of ICommand:

public bool CanExecute(object parameter)
{
    return this._canExecute == null || this._canExecute.Invoke();
}

public void RaiseCanExecuteChanged()
{
    EventHandler canExecuteChanged = this.CanExecuteChanged;
    if (canExecuteChanged != null)
    {
         canExecuteChanged.Invoke(this, EventArgs.Empty);
    }
}

with _canExecute being a Func<bool> set once to the value passed to constructor.

I am still working to minimally reproduce the issue.

UPDATE

See my answer.

Jacek Gorgoń
  • 3,206
  • 1
  • 26
  • 43
  • 1
    Post more code. What is RelayCommandEx? What does the command implementation look like? Create a very simple repro and post full code if possible. – Phil Sandler Aug 03 '11 at 21:30
  • ``RelayCommandEx`` was my failed attempt to deal with this (tried caching CanExecute() value and not firing CanExecuteChanged if not needed). – Jacek Gorgoń Aug 03 '11 at 21:33
  • 1
    I have a vague memory of running into this, and it had to do with something (I think in my case it was the WPF Button) was not caching the EventHandler and weak references. Will try to figure out what it was when I have time, but may help get you on the right path. You may want to implement add/remove accessors for the CanExecuteChanged event and then set a breakpoint in the remove to see when it's being removed. – CodeNaked Aug 03 '11 at 21:50

2 Answers2

8

PEBKAC. My framework in certain cases ran the code

DeleteCommand = new RelayCommand(Delete, CanDelete);

more then once, overwriting commands that were actually bound to view with new instances.

If somebody has this problem - make sure you're calling RelayCommand.RaiseCanExecuteChanged() on the same instance that the view is bound to.

Jacek Gorgoń
  • 3,206
  • 1
  • 26
  • 43
0

For anyone else who faced the same issue and the accepted answer didn't help me (and for my own record, as I spent a few hours today with it).

If you're using MVVM Light in a VSTO add-in, make sure that the Office application gets a chance to process its own messages to get this to work. For example, in my case I had my Ribbon buttons listening to CanExecuteChanged of underlying VM's command objects, which would not fire no matter what I did. After spending a few hours, I realized that I had to let Office application take a breath and process incoming message to allow CanExecuteChanged to be caught by the add-in. What I then did was to hand over my RaiseCanExecuteChanged function to DispatcherHelper to let it fire asynchronously. It was only then that my Ribbon buttons started reacting to CanExecuteChanged events. Something like this:

DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
  doc.Activate();
  ResetVariablesCommand.RaiseCanExecuteChanged();
});
dotNET
  • 33,414
  • 24
  • 162
  • 251