Skip to content

[Feature] MVVM Toolkit - Remove 'sealed' from the four Command Classes to allow extension #3609

@thomasclaudiushuber

Description

@thomasclaudiushuber

Describe the problem this feature would solve

Command classes are marked as sealed. That makes it impossible to extend these classes with custom behavior.
The issue regarding WPF's CommandManager can be handled in different ways
a) Developers have to create completely new commands for WPF if they want to use CommandManager
b) Developers can extend existing Toolkit Commands to hook up CommandManager
c) Developers can add a NuGet Package called Microsoft.Toolkit.Mvvm.Input.Wpf to get the WPF specific commands if they want to continue to use WPF's CommandManager

In either case, unless there's a specific reason to mark the Command classes as sealed, I would mark them as unsealed, as this would allow not only option a) and c), but also b). And b) could also be a more elegant way for c) without the need to re-implement the whole thing.

So, this issue is a request to make the command classes open for extension.

Describe the solution

Remove the sealed keyword from the command classes in Microsoft.Toolkit.Mvvm.Input.

Also mark at least the CanExecuteChanged event in those command classes as virtual:

public virtual event EventHandler? CanExecuteChanged

Helpful, but not required, would be a property that says whether the Command can always be executed or not. Because only in the latter case, the CommandManager's RequerySuggested event needs to be attached.

Consider implementing this property in the Command classes:

public bool CanAlwaysExecute => canExecute is null;

Describe alternatives you've considered

Alternative to the changes is option a) for WPF developers to implement commands on their own, which means they don't get the commands from the Toolkit, which reduces the value of the Toolkit for them if they want to continue using WPF's CommandManager.

Additional context

With the changes applied, there could be a WPF-specific RelayCommand class like below (Just taking this command class as an example, the other three command classes can also be extended like this with the changes of this issue applied). And this class could be implemented by a developer itself (option b)) or we deliver a separate NuGet package that is WPF-specific. But first of all, the RelayCommand of the Toolkit and the other ICommand implementations should be open for extension to write the below code:

namespace Microsoft.Toolkit.Mvvm.Input.Wpf
{
  public class RelayCommand : Microsoft.Toolkit.Mvvm.Input.RelayCommand
  {
    public RelayCommand(Action execute) : base(execute) { }

    public RelayCommand(Action execute, Func<bool> canExecute) : base(execute, canExecute) {  }

    public override event EventHandler? CanExecuteChanged
    {
      add => CommandManager.RequerySuggested += value;
      remove => CommandManager.RequerySuggested -= value;
    }
  }
}

If even the additional CanAlwaysExecute property is added, the WPF-specific command could look like this:

namespace Microsoft.Toolkit.Mvvm.Input.Wpf
{
  public class RelayCommand : Microsoft.Toolkit.Mvvm.Input.RelayCommand
  {
    public RelayCommand(Action execute) : base(execute) { }

    public RelayCommand(Action execute, Func<bool> canExecute) : base(execute, canExecute) {  }

    public override event EventHandler? CanExecuteChanged
    {
      add 
      {
        if(!CanAlwaysExecute)
        {
          CommandManager.RequerySuggested += value;
        }
      }
      remove 
      {
        if(!CanAlwaysExecute)
        {
          CommandManager.RequerySuggested -= value;
        }
      }
    }
  }
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions