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!

Community
  • 1
  • 1
RandyB
  • 325
  • 4
  • 13

1 Answers1

0

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();
            }
        }
    }
}
Geert van Horrik
  • 5,689
  • 1
  • 18
  • 32
  • Thanks for the reply! I used your class and instantiated it in my App OnStartup method and it appears to be working well. I'm not noticing a performance hit. My app is like a batch processor: the Customer loads a text file of records, the app processes and validates them, and then the app displays the results. There's not much interacting for the Customer to do. I timed running 1000 to 50,000 records and there's only an added 7 seconds on 50K records for a total of 2 min 3 secs, so I'm not overly concerned. Thanks again! – RandyB Feb 10 '16 at 20:15
  • I did notice something interesting: I have another app which is NOT showing this "can execute" issue. They operate similarly, but in this second app the ViewModel descends directly from ViewModelBase. In my first app the ViewModel descends from an intermediate VM which then descends from ViewModelBase. Could that explain this issue? Just wondering. Thanks. – RandyB Feb 10 '16 at 20:24
  • That's because Catel automatically re-evaluates the commands when a property changes. But if no vm property changes, there is no reason for the commands to be re-evaluated (automatically). That's why your first app needs this additional code. – Geert van Horrik Feb 11 '16 at 11:42
  • Thanks! I think I understand. If I have some spare time I might work on a small example so I can comprehend this better. – RandyB Feb 11 '16 at 18:44