RandyB
RandyB

Reputation: 375

CanExecute method of command not firing after update to Catel 4.4.0

I've recently updated my application from Catel 3.9.0 to 4.4.0. Things are generally working well with the exception of a Print button which appears on most of my views.

The application lets the Customer select and load a text file which is then parsed and validated. If there are any errors, they are displayed in the View in a grid control. Displaying any errors should also enable a Print button to let the Customer preview and print a report of the errors.

The View architecture is:

MyView.xaml (Catel UserControl)
    CommonErrorsWarnings.xaml (Catel UserControl)
        ErrorsAndWarningsGrid (DevExpress GridControl)

The ErrorsAndWarningsGrid is bound to an ObservableCollection in a common parent class of the associated ViewModel. This ObservableCollection holds objects containing the validation errors/warnings.

Also on the View is a Print button bound to a command on the common parent ViewModel class. It has the following "can execute" method:

/// <summary>
/// Method to check whether the PrintReport command can be executed.
/// </summary>
/// <returns><c>true</c> if the command can be executed; otherwise <c>false</c></returns>
protected virtual bool OnPrintReportCanExecute()
{
    Debug.Print(string.Format("OnPrintReportCanExecute called in [{0}].  Errors/Warnings count = {1}", this.Title, this.RecordErrorsAndWarnings.Count));
    return this.RecordErrorsAndWarnings != null && this.RecordErrorsAndWarnings.Count > 0;
}

(The Debug.Print statement is just to assist knowing when this method is called.)

All of this was working fine in Catel 3.9.0 -- when the Customer loaded the text file and validation errors were added to the RecordErrorsAndWarnings collection, the Print button was enabled. After moving to 4.4.0, it has stopped working. The OnPrintReportCanExecute method is not called as often as in 3.9.0 and never when the RecordErrorsAndWarnings count is greater than 0. It appears the CanExecute method is not being called during/after the collection is updated. It seems to only be called when the collection is first "set" when the ViewModel is created, but not when the collection is "gotten" to add items to it.

I've tried some of the suggestions in the Catel documentation: manually adding interesting properties and registering the ViewPropertySelector, but neither seems to work.

A temporary workaround (from another SO question) is to call ViewModelCommandManager.InvalidateCommands(true) after updating the collection, but my concern is whether there is a better/simpler solution.

If you have any questions or need more info, please let me know. Thanks!

Upvotes: 0

Views: 387

Answers (1)

Geert van Horrik
Geert van Horrik

Reputation: 5724

For performance reasons, Catel no longer automatically subscribes to the CommandManager to invalidate the state (saves a lot of CanExecute calls). If you want this all behavior back, you can create a custom class that subscribes to the command managerand invalidates the commands for you.

2 disclaimers

  1. This was removed for a reason (performance), so this is not the recommended approach. But this allows you to get back the old behavior.
  2. I haven't tested this code, but it should work nearly out of the box.

Code

public class RequeryAllTheThings
{
    private IViewModelManager _viewModelManager;

    public RequeryAllTheThings(IViewModelManager viewModelManager)
    {
        Argument.IsNotNull(() => viewModelManager);

        _viewModelManager = viewModelManager;

        System.Windows.Input.CommandManager.RequerySuggested += OnCommandManagerRequerySuggested;
    }

    private void OnCommandManagerRequerySuggested(object sender, SomeEventArgs e)
    {
        InvalidateCommands();
    }

    private void InvalidateCommands()
    {
        var viewModels = _viewModelManager.ActiveViewModels;
        foreach (var viewModel in viewModels)
        {
            var viewModelBase = viewModel as ViewModelBase;
            if (viewModelBase != null)
            {
                var viewModelCommandManager = viewModelBase.GetViewModelCommandManager();
                viewModelCommandManager.InvalidateCommands();
            }
        }
    }
}

Upvotes: 0

Related Questions